Simplifier (aliasing) des déclarations de T-SQL. Toute amélioration possible?
-
11-09-2019 - |
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).
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
ded
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:
- Utilisez une table dérivée (déjà Spencer, Adam et Jeremy a montré comment)
-
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