Question

Comme vous pouvez le voir, ce suce beaucoup de temps. Toute alternative? Je l'ai essayé d'utiliser l'alias de colonne dans le groupe par article en vain.

select count(callid) ,
case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end as duration
from callmetatbl
where programid = 1001 and callDuration > 0
group by case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end

EDIT: Je voulais vraiment demander comment avoir une source unique de cas, mais les modifications de cas sont de toute façon les bienvenus (bien que moins utile, car les intervalles seront probablement modifiés et pourraient même être générés automatiquement).

Comme il a été considéré par certaines personnes, callDuration est en effet un flotteur de sorte que certaines solutions énumérées ne sont pas valables pour mon cas d'utilisation, en laissant des valeurs hors des intervalles.

Leçons:

  • Rechercher les modèles dans l'expression de cas pour le réduire si possible et utile

     case
        when callDuration > 0 AND callDuration < 30 then 1
        when callDuration > 600 then 12
        else floor(callDuration/60) + 2  end
     end as duration
    
  • Utilisez des vues inline d'avoir une source unique de l'affaire

    select count(d.callid), d.duration
    from (   
       select callid
            , case
               when callDuration > 0 AND callDuration < 30 then 1
               when callDuration > 600 then 12
               else floor(callDuration/60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
    group by d.duration
    
  • Ou utiliser des expressions de table commune

       with duration_case as (
          select callid ,
          case
            when callDuration > 0 AND callDuration < 30 then 1
            when callDuration > 600 then 12
            else floor(callDuration/60) + 2  end
          end as duration
       from callmetatbl
       where programid = 1001 and callDuration > 0 )
        select count(callid), duration
        from duration_case
        group by duration
    
  • Ou utiliser une fonction définie par l'utilisateur (pas d'exemple à ce jour :-))

  • Ou utiliser une table de consultation et une jointure

    DECLARE @t TABLE(durationFrom float, durationTo float, result INT)
    --populate table with values so the query works
    select count(callid) , COALESCE(t.result, 12)
    from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom 
    AND callDuration < t.durationTo 
    where programid = 1001 and callDuration > 0
    

Merci à tout le monde et je vais avoir une période très difficile le choix d'une réponse acceptée, autant couverts différentes parties de la question (et je pensais qu'il y avait une question simple avec une réponse :-) simple, désolé pour la confusion).

Était-ce utile?

La solution

Q: Comment obtenir un alias à utiliser dans la clause GROUP BY

Une approche consiste à utiliser une vue en ligne. [EDIT] La réponse de Remus Rusanu (+1!) Donne un exemple d'une expression de table commune pour accomplir la même chose. [/ EDIT]

La vue en ligne vous obtient un simple « alias » pour l'expression complexe que vous pouvez ensuite faire référence dans une clause GROUP BY dans une requête externe:

select count(d.callid)
     , d.duration
  from (select callid
             , case
               when callDuration >= 600 then 12
               when callDuration >= 540 then 11
               when callDuration >= 480 then 10
               when callDuration >= 420 then 9
               when callDuration >= 360 then 8
               when callDuration >= 300 then 7
               when callDuration >= 240 then 6
               when callDuration >= 180 then 5
               when callDuration >= 120 then 4
               when callDuration >=  60 then 3
               when callDuration >=  30 then 2
               when callDuration >    0 then 1
               --else null
               end as duration
             from callmetatbl
            where programid = 1001
              and callDuration > 0
       ) d
group by d.duration

Nous allons déballer que.

  • la requête interne (en retrait) est appelée et vue en ligne (nous avons donné une d alias)
  • dans la requête externe, nous pouvons référencer l'alias duration de d

devrait être suffisant que pour répondre à votre question. Si vous êtes à la recherche d'une expression de remplacement équivalent, celui de tekBlues ( +1! ) est la bonne réponse (il fonctionne à la limite et pour les non-entiers. )

Avec l'expression de remplacement de tekBlues (+1!):

select count(d.callid)
     , d.duration
  from (select callid
             , case 
               when callduration >=30 and callduration<600
                    then floor(callduration/60)+2
               when callduration>0 and callduration< 30
                    then 1 
               when callduration>=600
                    then 12
               end as duration
          from callmetatbl
         where programid = 1001
           and callDuration > 0
       ) d
 group by d.duration

(Cela devrait être suffisant pour répondre à votre question.)


[UPDATE:] fonction définie utilisateur (remplacement d'expression CASE en ligne)

CREATE FUNCTION [dev].[udf_duration](@cd FLOAT)
RETURNS SMALLINT
AS
BEGIN
  DECLARE @bucket SMALLINT
  SET @bucket = 
  CASE
  WHEN @cd >= 600 THEN 12
  WHEN @cd >= 540 THEN 11
  WHEN @cd >= 480 THEN 10
  WHEN @cd >= 420 THEN 9
  WHEN @cd >= 360 THEN 8
  WHEN @cd >= 300 THEN 7
  WHEN @cd >= 240 THEN 6
  WHEN @cd >= 180 THEN 5
  WHEN @cd >= 120 THEN 4
  WHEN @cd >=  60 THEN 3
  WHEN @cd >=  30 THEN 2
  WHEN @cd >    0 THEN 1
  --ELSE NULL
  END
  RETURN @bucket
END

select count(callid)
     , [dev].[udf_duration](callDuration)
  from callmetatbl
 where programid = 1001
   and callDuration > 0
 group by [dev].[udf_duration](callDuration)

NOTES:. noter que la fonction définie par l'utilisateur ajoutera les frais généraux, et (bien sûr) ajouter une dépendance sur un autre objet de base de données

Cette fonction d'exemple est équivalent à l'expression originale. L'expression CASE OP n'a pas de lacunes, mais il fait référence à chaque « point d'arrêt » deux fois, je préfère tester que la limite inférieure. (CAS retourne lorsqu'une condition est satisfaite. Faire les essais en sens inverse permet le cas non prise en charge (<= 0 ou NULL) bascule sans test, un ELSE NULL n'est pas nécessaire, mais il pourrait être ajouté pour être complet.

Détails supplémentaires

(Assurez-vous de vérifier les performances et le plan d'optimisation, pour vous assurer qu'il est le même que celui (ou pas significativement pire que) l'original. Dans le passé, j'ai eu des problèmes pour obtenir prédicats poussé dans la vue en ligne, doesn « t ressembler à ce sera un problème dans votre cas.)

vue stored

Notez que la vue en ligne pourrait également être stocké sous forme de définition de vue dans la base de données. Mais il n'y a aucune raison de le faire, autre que de « cacher » l'expression complexe de votre déclaration.

simplification de l'expression complexe

Une autre façon de faire une expression complexe « simple » est d'utiliser une fonction définie par l'utilisateur. Mais une fonction définie par l'utilisateur est livré avec son propre ensemble de questions (y compris une dégradation des performances.)

Tableau base de données ajouter "recherche"

Quelques réponses recommande l'ajout d'une table « de recherche » à la base de données. Je ne vois pas que cela est vraiment nécessaire. Cela pourrait se faire bien sûr, et pourrait avoir un sens si vous voulez être en mesure de tirer des valeurs différentes pour duration de callDuration, à la volée, sans avoir à modifier votre requête et sans avoir à exécuter des instructions DDL (par exemple pour modifier une définition de visualisation, ou de modifier une fonction définie par l'utilisateur).

Avec une jointure à une table « de recherche », un avantage est que vous pouvez faire la requête de retour différents ensembles de résultats simplement en effectuant des opérations DML sur la table « de recherche ».

Mais ce même avantage peut effectivement être un inconvénient aussi bien.

Examiner attentivement si les avantages l'emportent en fait la baisse. Pensez à l'impact que la nouvelle table aura sur les tests unitaires, comment vérifier le contenu de la table de consultation sont valides et non modifiés (tout chevauchement? Des lacunes?), L'impact sur l'entretien continu au code (en raison de la complexité supplémentaire).

quelques hypothèses BIG

Beaucoup des réponses données ici semblent supposer que callDuration est un type de données ENTIER. Il semble qu'ils ont négligé la possibilité que ce n'est pas un entier, mais peut-être que je raté cette pépite dans le question.

Il est cas de test assez simple à démontrer que:

callDuration BETWEEN 0 AND 30

est PAS équivalent à

callDuration > 0 AND callDuration < 30

Autres conseils

Y at-il une raison quelconque vous ne l'utilisez between? Les déclarations de cas eux-mêmes ne semblent pas trop mal. Si vous détestez vraiment, vous pouvez jeter tout cela dans une table et mapper.

Durations
------------------
low   high   value
0     30     1
31    60     2

etc ...

(SELECT value FROM Durations WHERE callDuration BETWEEN low AND high) as Duration

EDIT:. Ou, dans le cas où les flotteurs sont utilisés et between devient encombrant

(SELECT value FROM Durations WHERE callDuration >= low AND callDuration <= high) as Duration

le cas peut être écrit comme ceci:

case 
when callduration >=30 and callduration<600 then floor(callduration/60)+2
when callduration>0 and callduration< 30 then 1 
when callduration>=600 then 12
end

having n'est pas nécessaire, le remplacer par un "où callduration> 0"

J'aime la réponse de la table traduire donné avant! c'est la meilleure solution

Vous devez pousser le CASE plus bas l'arbre de requête pour que sa projection est visible au GROUP BY. Cela peut être réaliser de deux façons:

  1. Utilisez une table dérivée (déjà Spencer, Adam et Jeremy a montré comment)
  2. Utilisez une des expressions de table commune

    with duration_case as (
    select callid ,
    case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
    end as duration
    from callmetatbl
    where programid = 1001 and callDuration > 0 )
       select count(callid), duration
       from duration_case
       group by duration
    

Les deux solutions sont équivalentes à tous égards. Je trouve CTEs plus lisible, certains préfèrent les tables dérivées comme plus portable.

Diviser callDuration 60:

case
        when callDuration between 1 AND 29 then 1
        when callDuration > 600 then 12
        else (callDuration /60) + 2  end
end as duration

Notez que between est compris les limites, et je suppose que callDuration sera traité comme un entier.


Mise à jour: Combinez cela avec quelques-unes des autres réponses, et vous pouvez obtenir toute requête à ceci:

select count(d.callid), d.duration
from (   
       select callid
            , case
                when callDuration between 1 AND 29 then 1
                when callDuration > 600 then 12
                else (callDuration /60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
group by d.duration
select count(callid), duration from
(
    select callid ,
    case
            when callDuration > 0 and callDuration < 30 then 1
            when callDuration >= 30 and callDuration < 60 then 2
            when callDuration >= 60 and callDuration < 120 then 3
            when callDuration >= 120 and callDuration < 180 then 4
            when callDuration >= 180 and callDuration < 240 then 5
            when callDuration >= 240 and callDuration < 300 then 6
            when callDuration >= 300 and callDuration < 360 then 7
            when callDuration >= 360 and callDuration < 420 then 8
            when callDuration >= 420 and callDuration < 480 then 9
            when callDuration >= 480 and callDuration < 540 then 10
            when callDuration >= 540 and callDuration < 600 then 11
            when callDuration >= 600 then 12
    end as duration
    from callmetatbl
    where programid = 1001 and callDuration > 0
) source
group by duration

Untested:

select  count(callid) , duracion
from
    (select 
        callid,
        case        
            when callDuration > 0 and callDuration < 30 then 1        
            when callDuration >= 30 and callDuration < 60 then 2        
            when callDuration >= 60 and callDuration < 120 then 3        
            when callDuration >= 120 and callDuration < 180 then 4        
            when callDuration >= 180 and callDuration < 240 then 5        
            when callDuration >= 240 and callDuration < 300 then 6        
            when callDuration >= 300 and callDuration < 360 then 7        
            when callDuration >= 360 and callDuration < 420 then 8        
            when callDuration >= 420 and callDuration < 480 then 9        
            when callDuration >= 480 and callDuration < 540 then 10        
            when callDuration >= 540 and callDuration < 600 then 11        
            when callDuration >= 600 then 12        
            else 0
        end as duracion
    from callmetatbl
    where programid = 1001) GRP
where duracion > 0
group by duracion

Ajoutez tous les cas dans une variable de table et faire une jointure externe

DECLARE @t TABLE(durationFrom INT, durationTo INT, result INT)
--        when callDuration > 0 and callDuration < 30 then 1
INSERT INTO @t VALUES(1, 30, 1);
--        when callDuration >= 30 and callDuration < 60 then 2
INSERT INTO @t VALUES(30, 60, 2);

select count(callid) , COALESCE(t.result, 12)
from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom AND callDuration  < t.durationTo 
where programid = 1001 and callDuration > 0

Voici mon coup à elle. Tous les composants dont vous avez besoin peut être fait dans SQL directement.

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from 
      callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

Voici le SQL j'ai utilisé pour les tests. (je n'ai pas ma propre callmetatbl personnelle;)

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from -- callmetatbl -- using test view below
      (  
       select 1001 as programid,   0 as callDuration union
       select 1001 as programid,   1 as callDuration union
       select 1001 as programid,  29 as callDuration union
       select 1001 as programid,  30 as callDuration union
       select 1001 as programid,  59 as callDuration union
       select 1001 as programid,  60 as callDuration union
       select 1001 as programid, 119 as callDuration union
       select 1001 as programid, 120 as callDuration union
       select 1001 as programid, 179 as callDuration union
       select 1001 as programid, 180 as callDuration union
       select 1001 as programid, 239 as callDuration union
       select 1001 as programid, 240 as callDuration union
       select 1001 as programid, 299 as callDuration union
       select 1001 as programid, 300 as callDuration union
       select 1001 as programid, 359 as callDuration union
       select 1001 as programid, 360 as callDuration union
       select 1001 as programid, 419 as callDuration union
       select 1001 as programid, 420 as callDuration union
       select 1001 as programid, 479 as callDuration union
       select 1001 as programid, 480 as callDuration union
       select 1001 as programid, 539 as callDuration union
       select 1001 as programid, 540 as callDuration union
       select 1001 as programid, 599 as callDuration union
       select 1001 as programid, 600 as callDuration union
       select 1001 as programid,1000 as callDuration
      ) as callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

La sortie SQL est représenté ci-dessous, 2 fiches comptés pour chaque durée (godet) 1 à 12.

total  duration
2             1
2             2
2             3
2             4
2             5
2             6
2             7
2             8
2             9
2            10
2            11
2            12

Voici les résultats de la sous-requête "foo":

divisor adder   fixedDuration  callDuration
120         1               1             1
120         1              29            29
120         2              30            30
120         2              59            59
60          2              60            60
60          2             119           119
60          2             120           120
60          2             179           179
60          2             180           180
60          2             239           239
60          2             240           240
60          2             299           299
60          2             300           300
60          2             359           359
60          2             360           360
60          2             419           419
60          2             420           420
60          2             479           479
60          2             480           480
60          2             539           539
60          2             540           540
60          2             599           599
60          2             600           600
60          2             600          1000

Vive.

Ce qui est si mal avec une fonction définie par l'utilisateur ici? Vous pouvez aussi bien nettoyer visuellement le code et centraliser la fonctionnalité de cette façon. Côté performance, je ne vois pas le coup d'être trop horrible à moins que vous faites quelque chose de vraiment retardé dans ladite UDF.

Créer une table de consultation pour duration
En utilisant une table permettra d'accélérer l'instruction SELECT ainsi.

Voici le résultat final de la façon dont il regardera avec table de recherche.

select  count(a.callid), b.ID as duration
from    callmetatbl a
        inner join DurationMap b 
         on a.callDuration >= b.Minimum
        and a.callDuration < IsNUll(b.Maximum, a.CallDuration + 1)
group by  b.ID

Voici la table de consultation.

create table DurationMap (
    ID          int identity(1,1) primary key,
    Minimum     int not null,
    Maximum     int 
)

insert  DurationMap(Minimum, Maximum) select 0,30
insert  DurationMap(Minimum, Maximum) select 30,60
insert  DurationMap(Minimum, Maximum) select 60,120
insert  DurationMap(Minimum, Maximum) select 120,180
insert  DurationMap(Minimum, Maximum) select 180,240
insert  DurationMap(Minimum, Maximum) select 240,300
insert  DurationMap(Minimum, Maximum) select 300,360
insert  DurationMap(Minimum, Maximum) select 360,420
insert  DurationMap(Minimum, Maximum) select 420,480
insert  DurationMap(Minimum, Maximum) select 480,540
insert  DurationMap(Minimum, Maximum) select 540,600
insert  DurationMap(Minimum) select 600
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top