2009-06-04 14 views
5

Como puede ver, esto apesta a lo grande. ¿Alguna alternativa? Intenté usar el alias de columna en el grupo por cláusula inútilmente.Simplificación (aliasing) de sentencias T-SQL CASE. Cualquier mejora posible?

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: Realmente quería preguntarle cómo tener una fuente de caso único, pero el caso modificaciones son bienvenidos todos modos (aunque menos útil porque los intervalos probablemente serán modificados y podrían incluso ser generados de forma automática).

Como ha sido considerado por algunas personas, callDuration es de hecho una flotación, por lo que algunas soluciones enumeradas no son válidas para mi caso de uso, al dejar valores fuera de los intervalos.

Lecciones:

  • buscar patrones en la expresión caso para reducir, si es posible y vale la pena

    case 
        when callDuration > 0 AND callDuration < 30 then 1 
        when callDuration > 600 then 12 
        else floor(callDuration/60) + 2 end 
    end as duration 
    
  • utilizar vistas en línea para tener una sola fuente del caso

    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 
    
  • O use las expresiones comunes de la tabla

    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 
    
  • O utilizar una función definida por el usuario (sin ejemplo hasta ahora :-))

  • O utilizar una tabla de consulta y una unión

    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 
    

Gracias a todos y Me está resultando muy difícil elegir una respuesta aceptada, ya que cubrimos diferentes partes de la pregunta (y estaba allí pensando que era una pregunta simple con una respuesta directa :-), disculpe la confusión).

+0

si la pregunta es "¿cómo puedo alias de una expresión compleja para que pueda hacer referencia a ella en una cláusula GROUP BY", un enfoque consiste en utilizar una vista en línea (véase mi respuesta), o una definición de vista almacena en la base de datos). la _otra_ pregunta (todos los demás parecen estar respondiendo) es "cómo simplifico esta expresión en particular", también hay varios enfoques para eso. – spencer7593

+0

@vinko: actualicé mi respuesta para incluir una función definida por el usuario de ejemplo (solo una función escalar para reemplazar la expresión en línea). una función con valores de tabla podría usarse para devolver una tabla de búsqueda ...ese es un enfoque viable también. TENGA CUIDADO con las brechas y superposiciones utilizando tablas de búsqueda y condiciones de combinación (la posibilidad de que las filas se descarten y/o dupliquen). Considere lo que se debe PROBAR frente a la necesidad de flexibilidad. (En mi experiencia, se requiere más esfuerzo para probar el código que para escribir el código). – spencer7593

+0

@ vinko: TAMBIÉN considere especificar cada valor de "punto de interrupción" solo una vez (utilice solo un límite y la garantía de un retorno anticipado cuando se presente una condición está satisfecho.) Deduzco la especificación de que cada callDuration (> 0) debe caer en un depósito y no perderse en un espacio entre dos cubos. – spencer7593

Respuesta

9

Q: cómo conseguir un alias utilizar en la cláusula GROUP BY

un enfoque consiste en utilizar una vista inline. [EDIT] La respuesta de Remus Ruşanu (1!) Da un ejemplo de una expresión de tabla común para lograr la misma cosa. [/ EDIT]

La vista en línea que usted consigue un simple "alias" para el complejo de expresión que luego se puede hacer referencia en una cláusula GROUP BY en una consulta externa:

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 

que vamos a descomprimir.

  • la (sangría) consulta interna se llama y inline vista (que dado una alias d)
  • en la consulta externa, podemos hacer referencia a la alias duration de d

Eso debería ser suficiente para responder tu pregunta. Si lo que buscas es una expresión de reemplazo equivalente, el de tekBlues (+1!) es la respuesta correcta (funciona en el límite y para los no enteros.)

Con la expresión de sustitución 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 

(esto debería ser suficiente para responder a su pregunta.)


] [Actualización: muestra defi usuarios función Ned (un reemplazo para expresión en línea CASE)

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) 

NOTAS: tenga en cuenta que el usuario función definida añadirá gastos generales, y (por supuesto) agregar una dependencia en otro objeto base de datos.

Este ejemplo función es equivalente a la expresión original. La expresión OP caso no tiene ningún lagunas, pero hace referencia a cada "punto de ruptura" dos veces, prefiero probar sólo el límite inferior. (CASE devuelve cuando se satisface una condición. Hacer las pruebas en sentido inverso permite que el caso no controlada (< = 0 o NULL) caen a través sin prueba, un ELSE NULL no es necesario, pero podría añadirse para la integridad.

DETALLES ADICIONALES

(Asegúrese de comprobar el rendimiento y el plan del optimizador, para asegurarse de que sea igual (o no significativamente peor que) el original. En el pasado, tuve problemas para obtener predicados en la vista en línea , no parece que será un problema en su caso.)

vista almacenada

Nótese que la línea vista también podría ser almacenada como definición de la vista en la base de datos. Pero no hay ninguna razón para hacer eso, aparte de "ocultar" la expresión compleja de su declaración.

la simplificación de la compleja expresión

Otra forma de hacer una expresión compleja "simple" es el uso de una función definida por el usuario. Pero una función definida por el usuario viene con su propio conjunto de problemas (incluyendo el rendimiento sea menor.)

complemento base de datos de la tabla de "búsqueda"

Algunas respuestas recomiendan agregar una tabla de "búsqueda" de la base de datos. No veo que esto sea realmente necesario. Podría hacerse, por supuesto, y podría tener sentido si desea derivar diferentes valores para duration de callDuration, sobre la marcha, sin teniendo que modificar su consulta y sin tener que ejecutar cualquier declaración DDL (por ej. para alterar una definición de vista, o modificar una función definida por el usuario).

Al unir una tabla de "búsqueda", una de las ventajas es que puede hacer que la consulta devuelva diferentes conjuntos de resultados simplemente realizando operaciones DML en la tabla "búsqueda".

Pero esa misma ventaja en realidad puede ser un inconveniente también.

Considere cuidadosamente si el beneficio realmente supera el inconveniente. Considere el impacto que la nueva tabla tendrá en las pruebas unitarias, cómo verificar que los contenidos de la tabla de búsqueda sean válidos y no modificados (¿superposiciones ?, ¿cualquier laguna?), Impacto en el mantenimiento continuo del código (debido a la complejidad adicional).

algunos supuestos BIG

Muchas de las respuestas dadas aquí parecen asumir que callDuration es un tipo de datos entero. Parece que han pasado por alto la posibilidad de que no sea un número entero, pero tal vez me perdí esa pepita en la pregunta.

Es caso de prueba bastante sencillo demostrar que:

callDuration BETWEEN 0 AND 30 

es NO equivalente a

callDuration > 0 AND callDuration < 30 
+0

Tienes razón, qué tonto de mi parte asumir que alguien con 23k rep sabe lo suficiente como para entender lo que estoy diciendo. Esta fue una buena respuesta, pero su actitud es al menos innecesaria. –

+1

@Spencer: ¿Qué hay de aquellos con 100 representantes que vienen después? –

+0

Mi sensación es que no hay esperanza para ningún desarrollador que no extrapola adecuadamente de un ejemplo. –

9

¿Hay alguna razón por la que no esté usando between? Las declaraciones de casos en sí no se ven tan mal. Si realmente lo odias, puedes tirar todo esto en una mesa y mapearlo.

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

etc ...

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

EDIT: O bien, en un caso en el que se utilizan flotadores y between vuelve muy complicada.

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

En mi humilde opinión, esta es la mejor solución – tekBlues

+0

+1 por ser conciso, o al menos no ser una novela. –

+1

Esto es feo en el caso del flotador, uno tendría que poner alto = 29.99999999 o similar. –

2

Divide callDuration por 60:

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

Nota que between es inclusivo de los límites, y Asumo Duración de la llamada será tratado como un entero.


Actualización:
combinar esto con algunas de las otras respuestas, y usted puede conseguir toda la consulta reduce a esto:

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 
+0

+1 Me gusta. He hecho algo similar: ver mi respuesta a continuación. – tom

5

el caso se puede escribir así:

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 

El tener no es necesario, reemplácelo por "donde callduration> 0"

¡Me gusta la respuesta de la tabla de traducción dada anteriormente!esa es la mejor solución

+0

Lo llamaría un lanzamiento entre la fórmula (esta respuesta) y la tabla de búsqueda. Cuál usted utiliza depende de su sitation: ¿es el patrón implementado por la fórmula constante? ¿Tiene (tiene que) tope a los 12? ¿"Ellos" querrán cambiar las bandas de informes a lo largo del tiempo (más fácil de hacer si se trata de una tabla)? –

+0

+1 esta expresión es equivalente a CASE expr en la consulta OP. – spencer7593

1
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 
1

No comprobado:

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 
4

Es necesario empujar el CASO más abajo en el árbol de consulta para que su proyección es visible para el GROUP BY. Esto se puede lograr de dos maneras:

  1. Utilizar una tabla derivada (ya Spencer, Adán y Jeremy mostraron cómo)
  2. utilizar expresiones de una mesa común

    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 
    

Ambas soluciones son equivalente en todos los aspectos. Encuentro que los CTE son más legibles, algunos prefieren las tablas derivadas como más portátiles.

+0

Buena información ... gracias –

+0

quien editó el código y lo hizo aparecer bien, ¿qué le pasó? No pude resolverlo –

+0

Faltaban algunos espacios en algunas líneas, TODAS las líneas deben sangrarse 4 espacios –

1

añadir todos los casos en una variable de tabla y hacer una combinación externa

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 
1

Aquí está mi oportunidad. Todos los componentes que necesita se pueden hacer en SQL directo.

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 
    ,(/*[email protected]*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration 
    ,callDuration 
    from 
     callmetatbl 
    where 
     programid = 1001 
    and 
     callDuration > 0 
) as foo 
group by 
    (fixedDuration/divisor) + adder 

Aquí está el SQL que utilicé para probar. (no tengo mi propia callmetatbl personal;) ​​

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 
    ,(/*[email protected]*/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 salida SQL se muestra a continuación, como 2 registros contados para cada duración (cubo) 1 a través de 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 

Aquí son los resultados de la subconsulta "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 

Cheers.

1

¿Qué tiene de malo una función definida por el usuario aquí? Podría limpiar visualmente el código y centralizar la funcionalidad de esa manera. En cuanto a rendimiento, no puedo ver que el golpe sea demasiado horrible a menos que estés haciendo algo realmente retardado dentro de dicho UDF.

+0

No hay nada "incorrecto" con una función definida por el usuario. Existe cierta sobrecarga de rendimiento frente a expresión en línea equivalente, y agrega una dependencia en otro objeto de base de datos, y efectivamente "oculta" la lógica de la instrucción. No se trata de estar equivocado, es una cuestión de concesiones, y si los beneficios de usar una función definida por el usuario superan los inconvenientes. – spencer7593

+1

Gotcha. Yo argumentaría que envolver esta funcionalidad (aparentemente compartida) en la función tiene mucho sentido desde la perspectiva de la arquitectura. La muestra provista parece indicar que tienen un requisito para mapear duraciones de llamadas a enteros específicos, y probablemente requiera la misma lógica en todas partes. –

1

Crear una tabla de consulta para duration
Usando una tabla de consulta acelerará la declaración SELECT también.

Aquí está el resultado final de cómo se verá con la tabla de búsqueda.

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 

Aquí está la tabla de consulta.

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 
Cuestiones relacionadas