Cómo tener más de 100 entradas en la declaración de caso como variable

11

Escribí una declaración de caso con> 100 opciones donde estoy usando la misma declaración en 4 lugares en una consulta simple.

La misma consulta dos veces con una unión entre ellos, pero también está haciendo un recuento y, por lo tanto, el grupo también contiene la declaración del caso.

Esto es para volver a etiquetar algunos nombres de compañías donde los diferentes registros para la misma compañía se escriben de manera diferente.

Traté de declarar una variable como VarChar (MAX)

declare @CaseForAccountConsolidation varchar(max)

SET @CaseForAccountConsolidation = 'CASE 
       WHEN ac.accountName like ''AIR NEW Z%'' THEN ''AIR NEW ZEALAND''
       WHEN ac.accountName LIKE ''AIR BP%'' THEN ''AIR BP''
       WHEN ac.accountName LIKE ''ADDICTION ADVICE%'' THEN ''ADDICTION ADVICE''
       WHEN ac.accountName LIKE ''AIA%'' THEN ''AIA''
       ...

Cuando fui a usarlo en mi declaración select, la consulta solo devolvió la declaración del caso como texto y no la evaluó.

Tampoco pude usarlo en el grupo: recibí este mensaje de error:

Each GROUP BY expression must contain at least one column that is not an outer reference.

Idealmente, me gustaría tener el CASO en un solo lugar, para que no haya posibilidad de actualizar una línea y no replicarla en otro lugar.

¿Hay alguna forma de hacer esto?

Estoy abierto a otras formas (como tal vez una función, pero no estoy seguro de cómo usarlas así)

Aquí hay una muestra de SELECT que estoy usando actualmente

SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END AS accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
WHERE a.datecreated = CONVERT(DATE,now())
GROUP BY
   dl.FirstDateOfMonth
   ,dl.FinancialYear
   ,dl.FirstDateOfWeek
   ,CONVERT(Date,c.date_charged)
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END

UNION

SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END AS accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
WHERE a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))
GROUP BY
   dl.FirstDateOfMonth
   ,dl.FinancialYear
   ,dl.FirstDateOfWeek
   ,CONVERT(Date,c.date_charged)
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END

El propósito de esta UNIÓN es devolver todos los datos de un período de tiempo, y TAMBIÉN devolver datos del mismo período de tiempo de 12 meses anteriores

EDITAR: se agregó un
EDIT2 "CATCH-ALL" que faltaba: se agregó una segunda mitad de la declaración UNION
EDIT3: se corrigió GROUP BY para incluir algunos otros elementos necesarios

Kiltannen
fuente
¿Cómo difieren las 2 partes de UNION? Se ven bastante similares, excepto por las condiciones ligeramente diferentes de DONDE.
ypercubeᵀᴹ
Esa es la diferencia clave. Las dos condiciones diferentes de DONDE en la fecha dan hoy y la misma fecha hace 12 meses. Esto significa que puedo comparar los números para ese día y el mismo día hace 12 meses en la capa de presentación, pero ejecutando la consulta SQL única.
kiltannen
3
¿Por qué no un solo SELECCIONAR con WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
ypercubeᵀᴹ
@ ypercubeᵀᴹ La respuesta simple es que al construir esto al principio estaba copiando de una manera que lo hice en otro lugar que usaba UNION. Lo un poco más complicado es que el limitador de fecha es en realidad bastante más complejo que hoy y la misma fecha hace 12 meses. El rango de fechas para el que estoy seleccionando es del 1 de julio a la fecha actual + del 1 de julio anterior a la fecha que es exactamente hace 12 meses. (Ejercicio financiero hasta la fecha versus último año fiscal anterior hace 12 meses; esto ofrece una comparación del crecimiento o no para el año financiero). PERO como AndryM y usted sugiere, voy a intentar menos la UNIÓN
kiltannen

Respuestas:

11

Una manera fácil de eliminar la repetición de la expresión CASE es usar CROSS APPLY de esta manera:

SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,x.accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   CROSS APPLY
   (
    SELECT 
       CASE 
           WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
           WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
           WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
           WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       END AS accountName
   ) AS x
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
GROUP BY
   dl.FirstDateOfMonth
   ,x.AccountName

Con la ayuda de CROSS APPLY, asigna un nombre a su expresión CASE de tal manera que se pueda hacer referencia a ella en cualquier parte de su declaración. Funciona porque, estrictamente hablando, está definiendo la columna calculada en un SELECT anidado, el SELECCIONAR SIN DE que sigue a la APLICACIÓN CRUZADA.

Esto es lo mismo que hacer referencia a una columna con alias de una tabla derivada, que técnicamente es este SELECT anidado. Es una subconsulta correlacionada y una tabla derivada. Como subconsulta correlacionada, se le permite hacer referencia a las columnas del ámbito externo y, como tabla derivada, permite que el ámbito externo haga referencia a las columnas que define.

Para una consulta UNION que use la misma expresión CASE, debe definirla en cada tramo, no hay solución para eso, excepto usar un método de reemplazo completamente diferente en lugar del CASE. Sin embargo, en su caso específico, es posible obtener los resultados sin UNION.

Las dos patas difieren solo en la condición WHERE. Uno tiene esto:

WHERE a.datecreated = CONVERT(DATE,now())

y el otro esto:

WHERE a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))

Puedes combinarlos así:

WHERE a.datecreated IN (
                        CONVERT(DATE,now()),
                        DATEADD(YEAR,-1,CONVERT(DATE,now()))
                       )

y aplíquelo al SELECT modificado al comienzo de esta respuesta.

Andriy M
fuente
Buena Andriy - +1! Inspirado por usted :-), agregué otro enfoque a mi respuesta: a CTE- ¡No estoy seguro de cuál es el mejor enfoque!
Vérace
Hola Andriy, me gusta el aspecto de esta solución. Mencioné que tenía una UNIÓN, pero era lo suficientemente tonta como para no incluirla en mi ejemplo. Lo he hecho ahora. Sospecho que es probable que esta x de CROSS APPLY no esté disponible para la segunda mitad de UNION, ¿verdad? Entonces esto significaría que todavía estaría atascado con 2 copias del CASO, ¿no es así? (Lo
revisaré
@kiltannen Descarte UNIONe incluya la datecreatedcolumna en su GROUP BYcláusula (y actualice la WHEREcláusula para incluir las dos fechas que le interesan).
Scott M
@ScottM: No creo que el OP deba incluir la datecreatedcolumna en GROUP BY. Aparte de eso, estoy completamente de acuerdo, pueden combinar las cláusulas WHERE y deshacerse de UNION.
Andriy M
@ scott-m Tendré que probar esto mañana PERO sospecho que eso no funciona tan bien. En realidad no es un día, son potencialmente varios meses. Creo que me encontré con tener hasta 11 meses de datos diarios, por lo que dónde había comenzado y finalizado Y luego tuve que ejecutar un OR durante el mismo período 12 meses antes. Creo que esto terminó con un éxito en el rendimiento. Tendría que intentarlo de nuevo, pero recuerdo haber tenido problemas que no tuve al ejecutar UNION. Por supuesto, eso trae problemas propios. Como el que estoy luchando actualmente ..
kiltannen
22

Poner los datos en una tabla

CREATE TABLE AccountTranslate (wrong VARCHAR(50), translated(VARCHAR(50));

INSERT INTO AccountTranslate VALUES ('ADDICTION ADVICE%','ADDICTION ADVICE');
INSERT INTO AccountTranslate VALUES ('AIR BP%','AIR BP');
INSERT INTO AccountTranslate VALUES ('AIR NEW Z%', 'AIR NEW ZEALAND');

y únete a él.

SELECT ...,COALESCE(AccountTranslate.translated, ac.accountName) AS accountName
FROM
...., 
account_code ac left outer join 
AccountTranslate at on ac.accountName LIKE AccountTranslate.wrong

De esta forma, puede evitar mantener los datos actualizados en varios lugares. Solo usa el COALESCEdonde lo necesites. Puede incorporar esto en CTE o VIEWs según las otras sugerencias.

LoztInSpace
fuente
4

Otra opción, creo que si necesita reutilizarla en varios lugares, una función con valor de tabla en línea será una buena opción.

CREATE FUNCTION dbo.itvf_CaseForAccountConsolidation
    ( @au_lname VARCHAR(8000) ) 
RETURNS TABLE 
RETURN 
SELECT  
  CASE
    WHEN UPPER(@au_lname) LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
    WHEN UPPER(@au_lname) LIKE 'AIR BP%'  THEN 'AIR BP'
    WHEN UPPER(@au_lname) LIKE 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
    ELSE '****ERROR****'  -- you may or may not need this! 
                         -- If converting every record, then yes, if not, then no!
                         -- Errors should stand out on browsing and it's easy to search for!
  END AS wrong

--Copied from verace

Su selección será así.

  SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,dd.wrong AS accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
   CROSS APPLY  dbo.itvf_CaseForAccountConsolidation( ac.accountName)dd
GROUP BY
   dl.FirstDateOfMonth 
   ,dl.FirstDateOfWeek 
   ,wrong 
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged)

Además, no he probado esto y también se debe determinar el rendimiento del código.

EDIT1 : Creo que andriy ya ha dado uno que usa la aplicación cruzada que redacta el código. Bueno, este puede ser centralizado ya que cualquier cambio en la función se reflejará en todo ya que está repitiendo lo mismo en otras partes del código.

Biju jose
fuente
3

Yo usaría un VIEWpara hacer lo que estás tratando de hacer. Podría, por supuesto, corregir los datos subyacentes, pero con frecuencia en este sitio, los que hacen preguntas (consultores / dbas /) no tienen la autoridad para hacerlo. ¡Usar un VIEWpuede resolver este problema! También utilicé la UPPERfunción, una forma económica de resolver errores en casos como este.

¡Ahora, solo declaras VIEWuna vez y puedes usarla en cualquier lugar! De esta manera, solo tiene un lugar en el que se almacena y ejecuta su algoritmo de conversión de datos, lo que aumenta la fiabilidad y la solidez de su sistema.

También puede usar un CTE ( expresión de tabla común ); consulte la parte inferior de la respuesta.

Para responder a su pregunta, hice lo siguiente:

Crea una tabla de muestra:

CREATE TABLE my_error (wrong VARCHAR(50));

Inserte un par de registros de muestra:

INSERT INTO my_error VALUES ('Addiction Advice Services Ltd.');
INSERT INTO my_error VALUES ('AIR BP_and-mistake');
INSERT INTO my_error VALUES ('AIR New Zealand Airlines');

Luego cree un VIEWcomo se sugiere:

CREATE VIEW my_error_view AS 
SELECT 
  CASE
    WHEN UPPER(wrong) LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
    WHEN UPPER(wrong) LIKE 'AIR BP%'  THEN 'AIR BP'
    WHEN UPPER(wrong) LIKE 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
    ELSE '***ERROR****' -- You may or may not need this.
                        -- It's attention grabbing (report) and easy to search for (SQL)!
  END AS wrong
FROM my_error;

Entonces, SELECT de tu VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Resultado:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Et voilà!

Puedes encontrar todo esto en el violín aquí .

El CTEenfoque:

Igual que el anterior, excepto que CTEse sustituye por el VIEWsiguiente:

WITH my_cte AS
(
  SELECT 
  CASE
    WHEN UPPER(wrong) LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
    WHEN UPPER(wrong) LIKE 'AIR BP%'  THEN 'AIR BP'
    WHEN UPPER(wrong) LIKE 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
    ELSE '****ERROR****'  -- you may or may not need this! 
                         -- If converting every record, then yes, if not, then no!
                         -- Errors should stand out on browsing and it's easy to search for!
  END AS wrong
  FROM my_error
)
SELECT * FROM my_cte;

El resultado es el mismo. Luego puede tratar la CTEtabla como lo haría con cualquier otra tabla, SELECT¡solo por s! Fiddle disponible aquí .

En general, creo que el VIEWenfoque es mejor en este caso.

Vérace
fuente
0

Mesa incrustada

select id, tag, trans.val 
  from [consecutive] c
  join ( values ('AIR NEW Z%', 'AIR NEW ZEALAND'),
                ('AIR BP%',    'AIR BP')
       ) trans (lk, val)
    on c.description like trans.lk 

Omita la unión y use un ORen el lugar según lo sugerido por otros.

paparazzo
fuente