Frage

Wie Sie sehen können, dieser saugt große Zeit.Irgendwelche alternativen?Ich habe versucht, mit der Spaltenalias in der group by-Klausel ohne Erfolg.

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:Ich habe wirklich gedacht, Sie zu Fragen, wie ein einziger Fall, der Quelle, aber Fall änderungen sind willkommen sowieso (obwohl weniger nützlich, weil die Intervalle wird wahrscheinlich geändert werden, und könnte sogar automatisch generiert werden).

Wie schon von einigen Menschen, callDuration ist in der Tat ein float, so dass einige der aufgeführten Lösungen gelten nicht für meinen Anwendungsfall, indem Sie Werte aus der Intervalle.

Unterricht:

  • Suchen Sie nach mustern in der case-Ausdruck, um ihn zu reduzieren, wenn möglich und lohnt sich

     case
        when callDuration > 0 AND callDuration < 30 then 1
        when callDuration > 600 then 12
        else floor(callDuration/60) + 2  end
     end as duration
    
  • Verwenden Sie inline-Ansichten, um eine einzelne Quelle der Fall

    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
    
  • Oder verwenden Sie common table expressions

       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
    
  • Oder verwenden Sie eine benutzerdefinierte Funktion (kein Beispiel so weit :-) )

  • Oder verwenden Sie eine lookup-Tabelle und eine beitreten

    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
    

Vielen Dank an alle und ich habe eine sehr schwierige Zeit für eine akzeptierte Antwort, wie viele verschiedene Teile der Frage (und ich war dort, dachte, es war eine einfache Frage mit einer einfachen Antwort :-), sorry für die Verwirrung).

War es hilfreich?

Lösung

Q: wie man einen alias zu verwenden, die in der GROUP BY-Klausel

Ein Ansatz ist die Verwendung einer inline-Ansicht.[BEARBEITEN] Die Antwort von Remus Rusanu (+1!) gibt ein Beispiel für eine Common Table Expression, zu erreichen die gleiche Sache.[/EDIT]

Die inline-Ansicht bekommt man eine einfache "alias" für die komplexen Ausdruck können Sie dann die Referenz in einer GROUP BY-Klausel einer äußeren Abfrage:

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

Lassen Sie uns packen Sie Sie aus.

  • die innere (eingerückt) Abfrage aufgerufen wird und inline-Ansicht (wir erhalten einen alias d)
  • in der äußeren Abfrage, wir können auf den alias duration von d

Das sollte ausreichen, um Ihre Frage zu beantworten.Wenn Sie auf der Suche nach einem gleichwertigen Ersatz Ausdruck, der von tekBlues (+1 !) die richtige Antwort (es arbeitet auf der Grenze und für nicht-Ganzzahlen.)

Mit den Ersatz-expression von 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

(Dies sollte ausreichen, um Ihre Frage zu beantworten.)


[UPDATE:] sample Benutzer-definiert Funktion (Ersatz für inline-CASE-Ausdruck)

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)

HINWEISE: beachten Sie, dass die benutzerdefinierte Funktion hinzufügen, overhead, und (natürlich) fügen Sie eine Abhängigkeit von anderen Datenbank-Objekt.

In diesem Beispiel ist die Funktion entspricht dem ursprünglichen Ausdruck.Die OP-CASE-Ausdruck hat keine Lücken, aber es verweist jede "Haltepunkt" zweimal, ich bevorzuge zum testen nur die untere Grenze.(FALL zurück, wenn eine Bedingung erfüllt ist.Mit den übungen in umgekehrter können die behandelte Fall (<=0 oder NULL ist), Sie fallen durch, ohne test, eine ELSE NULL ist nicht notwendig, aber könnte Hinzugefügt werden auf Vollständigkeit.

WEITERE DETAILS

(Werden sicher zu überprüfen Sie die Leistung und die optimizer-plan, um sicherzustellen, dass es das gleiche ist wie (oder nicht wesentlich schlechter als das original.In der Vergangenheit habe ich hatte Probleme, Prädikate gedrängt in der inline-Ansicht, sieht nicht wie es sein wird, ein problem in Ihrem Fall.)

gespeichert anzeigen

Beachten Sie, dass die inline Ansicht könnte auch gespeichert werden als view-definition in der Datenbank.Aber es gibt keinen Grund, das zu tun, andere als zu "verstecken", die komplexe Ausdrücke aus der Anweisung.

Vereinfachung der komplexen Ausdruck

Ein anderer Weg, um einen komplexen Ausdruck "einfacher" ist die Verwendung einer benutzerdefinierten Funktion.Aber eine benutzerdefinierte Funktion kommt mit seinen eigenen Satz von Problemen (einschließlich verminderter Leistung.)

Datenbank hinzufügen "lookup" - Tabelle

Einige Antworten empfehlen, das hinzufügen einer "lookup" - Tabelle in der Datenbank.Ich sehe nicht, dass dies wirklich notwendig ist.Es könnte getan werden, natürlich, und könnte dann Sinn machen, wenn Sie möchten ableiten können unterschiedliche Werte für duration von callDuration, auf der fly, ohne das ändern Sie Ihre Abfrage, und ohne dass zum ausführen von DDL-Anweisungen (z.B.zu ändern die definition einer Sicht, oder ändern eine benutzerdefinierte Funktion).

Mit einem join zu einer "lookup" - Tisch, auch ein Vorteil ist, dass Sie machen konnte, die die Abfrage zurückgeben unterschiedliche Ergebnismengen nur durch ausführen von DML-Operationen auf der "lookup" - Tabelle.

Aber das gleiche Vorteil kann tatsächlich ein Nachteil, wie gut.

Sorgfältig prüfen, ob der nutzen tatsächlich größer ist als die downside.Berücksichtigen Sie die Auswirkungen, die neue Tabelle wird auf unit-Test-wie zu überprüfen Sie den Inhalt der lookup-Tabelle gültig und nicht geändert werden (jedes überschneidungen?Lücken?), Auswirkungen auf die laufende Wartung der code (aufgrund der zusätzlichen Komplexität).

einige GROßE Annahmen

Viele der hier gegebenen Antworten scheinen anzunehmen, dass callDuration ist eine ganze Zahl-Datentyp.Wie es scheint haben Sie übersehen die Möglichkeit, dass es ist nicht eine ganze Zahl, aber vielleicht verpasste ich, dass die Goldklumpen in die Frage.

Es ist ziemlich einfach Testfall, um zu demonstrieren, dass:

callDuration BETWEEN 0 AND 30

ist NICHT äquivalent zu

callDuration > 0 AND callDuration < 30

Andere Tipps

Gibt es einen Grund, Sie sind nicht mit between?Die case-Anweisungen selbst nicht zu schlecht aussieht.Wenn Sie hasse es wirklich, könnten Sie werfen alles in eine Tabelle und ordnen Sie Sie.

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

etc...

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

EDIT:Oder, in einem Fall, wo Schwimmern verwendet werden und between unhandlich.

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

der Fall kann wie folgt geschrieben werden:

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

Die having ist nicht erforderlich, ersetzen Sie es durch ein "wo callduration>0"

Ich mag die translate-Tabelle, die Antwort vor!das ist die beste Lösung

Sie müssen drücken Sie den FALL weiter unten auf die Abfrage-Struktur, so dass seine Projektionen sichtbar für das GROUP BY.Dies kann erreicht werden in zwei Möglichkeiten:

  1. Die Verwendung einer abgeleiteten Tabelle (bereits Spencer, Adam und Jeremy zeigte, wie)
  2. Verwendung einer common table expressions

    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
    

Beide Lösungen sind äquivalent in jeder Hinsicht.Ich finde CTEs mehr lesbar ist, bevorzugen einige abgeleitete Tabellen als mehr tragbar.

Teilen callDuration durch 60:

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

Beachten Sie, dass between inklusive der Grenzen, und ich gehe davon aus, callDuration behandelt werden wie ein integer.


Update:
Kombinieren Sie dies mit einigen der anderen Antworten, und Sie können die gesamte Abfrage so:

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

Ungetestet:

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

Fügen Sie alle Fälle in eine table-variable und eine äußere Verknüpfung

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

Hier ist meine Chance.Alle Komponenten, die Sie brauchen getan werden kann in direkt-SQL.

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

Hier ist die SQL, die ich zum testen verwendet. (Ich habe nicht meine eigene persönliche callmetatbl ;)

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

Die SQL-Ausgabe ist unten gezeigt, als 2 Datensätze gezählt, für jede Dauer (Eimer) 1 bis 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

Hier sind die Ergebnisse aus dem "foo" sub-Abfrage:

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

Cheers.

Was ist so falsch mit einer Benutzer-Definierten Funktion hier?Sie konnte sowohl optisch, als auch den code zu bereinigen und zentralisieren die Funktionen, die Weg.Performance-wise, kann ich nicht sehen die Treffer, die zu schrecklich es sei denn, Sie tun etwas wirklich zurückgeblieben sagte UDF.

Erstellen Sie eine Nachschlagetabelle für duration
Mit Hilfe einer look-up-Tabelle wird Geschwindigkeit bis das SELECT Anweisung als gut.

Hier ist das Endergebnis, wie es Aussehen wird mit lookup-Tabelle.

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

Hier wird die look-up-Tabelle.

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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top