Como ter mais de 100 entradas na instrução case como uma variável

11

Escrevi uma declaração de caso com> 100 opções em que estou usando a mesma declaração em 4 locais em uma consulta simples.

A mesma consulta duas vezes com uma união entre eles, mas também está fazendo uma contagem e, portanto, o grupo by também contém a instrução case.

Isso é para rotular novamente alguns nomes de empresas em que registros diferentes para a mesma empresa são escritos de maneira diferente.

Tentei declarar uma variável como um 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''
       ...

Quando fui usá-lo na minha instrução select - a consulta retornou a instrução case como texto e não a avaliou.

Também não consegui usá-lo no grupo por - recebi esta mensagem de erro:

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

Idealmente, gostaria de ter o CASE em apenas um único local - para que não haja chance de eu atualizar uma linha e não replicá-la em outro lugar.

Existe alguma maneira de fazer isso?

Estou aberto a outras formas (como talvez uma função - mas não sei como usá-las dessa maneira)

Aqui está um exemplo do SELECT que estou usando atualmente

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

O objetivo deste UNION é retornar todos os dados por um período de tempo e TAMBÉM retornar dados pelo mesmo período de 12 meses

EDIT: Adicionado um "CATCH-ALL" ausente
EDIT2: Adicionado um segundo ½ da instrução UNION
EDIT3: Corrigido o GROUP BY para incluir alguns outros elementos necessários

kiltannen
fonte
Como as 2 partes da UNIÃO diferem? Eles parecem bastante semelhantes, exceto pelas condições WHERE ligeiramente diferentes.
ypercubeᵀᴹ
Essa é a principal diferença. As duas condições WHERE diferentes na data são hoje e a mesma data há 12 meses. Isso significa que posso comparar os números desse dia e do mesmo dia 12 meses atrás na camada de apresentação - mas executando a única consulta SQL.
Kiltannen
3
Por que não um único SELECT com WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
ypercubeᵀᴹ
@ ypercubeᵀᴹ A resposta simples é que, ao criar isso, eu estava copiando de uma maneira que fiz em outro lugar que usava o UNION. O mais complicado é que o limitador de datas é realmente muito mais complexo do que hoje e a mesma data há 12 meses. O período para o qual estou selecionando é de 1º de julho à data atual + do 1º de julho anterior àquela data de exatamente 12 meses atrás. (Exercício acumulado até o último ano fiscal de 12 meses atrás - isso fornece uma comparação do crescimento ou não do exercício). MAS como AndryM & você sugere, eu vou tentar menos a UNIÃO
kiltannen

Respostas:

11

Uma maneira fácil de eliminar a repetição da expressão CASE é usar CROSS APPLY assim:

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

Com a ajuda do CROSS APPLY, você atribui um nome à sua expressão CASE de forma que ela possa ser referenciada em qualquer lugar da sua declaração. Funciona porque, estritamente falando, você está definindo a coluna computada em um SELECT aninhado - o SELECT FROM-less que segue o CROSS APPLY.

É o mesmo que referenciar uma coluna com alias de uma tabela derivada - que tecnicamente é esse SELECT aninhado. É uma subconsulta correlacionada e uma tabela derivada. Como uma subconsulta correlacionada, é permitido fazer referência às colunas do escopo externo e, como uma tabela derivada, permite que o escopo externo faça referência às colunas que define.

Para uma consulta UNION que usa a mesma expressão CASE, é necessário defini-la em cada perna, não há solução alternativa para isso, exceto para usar um método de substituição completamente diferente em vez do CASE. No entanto, no seu caso específico, é possível buscar os resultados sem UNION.

As duas pernas diferem apenas na condição WHERE. Um tem o seguinte:

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

e o outro isso:

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

Você pode combiná-los assim:

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

e aplique-o ao SELECT modificado no início desta resposta.

Andriy M
fonte
Nice Andriy - +1! Inspirado por você :-), adicionei outra abordagem à minha resposta - a CTE- Não tenho certeza de qual é a melhor abordagem!
Vérace
Oi Andriy, eu gosto da aparência desta solução. Mencionei que tinha um UNION - mas fui burro o suficiente para não incluí-lo no meu exemplo. Eu fiz isso agora. Eu suspeito que esse x do CROSS APPLY provavelmente não esteja disponível para a segunda metade da UNIÃO, não é? Então isso significa que eu ainda ficaria preso com 2 cópias do CASE, certo? (Vou verificar-lo para amanhã quando eu voltar ao trabalho)
kiltannen
@kiltannen Solte UNIONe inclua a datecreatedcoluna na sua GROUP BYcláusula (e atualize a WHEREcláusula para incluir as duas datas em que você está interessado).
Scott M
@ ScottM: Eu não acho que o OP precise incluir a datecreatedcoluna no GROUP BY. Fora isso, concordo plenamente, eles podem apenas combinar as cláusulas WHERE e abandonar a UNION.
Andriy M
@ scott-m Vou ter que tentar isso amanhã, mas suspeito que não funcione tão bem. Na verdade, não é um dia - são potencialmente vários meses. Eu acho que encontrei até 11 meses de dados diários - então o local tinha início e fim E então eu tive que executar uma sala de cirurgia pelo mesmo período 12 meses antes. Eu acho que isso acabou com um impacto no desempenho. Eu precisaria tentar novamente - mas lembro-me de ter problemas que não tive ao executar o UNION. Claro que isso traz problemas por si só. Como a que eu atualmente estou lutando com ..
kiltannen
22

Coloque os dados em uma tabela

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');

e junte-se a ele.

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

Dessa forma, você pode evitar manter os dados atualizados em vários locais. Basta usar o COALESCElocal que você precisar. Você pode incorporar isso ao CTE ou VIEWs, conforme as outras sugestões.

LoztInSpace
fonte
4

Outra opção, acho que, se você precisar reutilizá-lo em vários lugares, uma função com valor de tabela Inline será boa.

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

Sua seleção será assim.

  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)

Além disso, eu não testei isso e o desempenho do código também deve ser determinado.

EDIT1 : Eu acho que andriy já deu um que usa cross apply que redige o código. Bem, este pode ser centralizado, pois quaisquer alterações na função serão refletidas em todos, pois você repetirá o mesmo em outras partes do código.

Biju jose
fonte
3

Eu usaria um VIEWpara fazer o que você está tentando fazer. Obviamente, você pode corrigir os dados subjacentes, mas frequentemente neste site, aqueles que fazem perguntas (consultants / dbas /) não têm autoridade para fazer isso. Usando um VIEWpode resolver este problema! Também usei a UPPERfunção - uma maneira barata de resolver erros em casos como este.

Agora, você declara apenas VIEWuma vez e pode usá-lo em qualquer lugar! Dessa forma, você só tem um local em que seu algoritmo de conversão de dados é armazenado e executado, aumentando assim a confiabilidade e a robustez do seu sistema.

Você também pode usar um CTE ( Common Table Expression ) - veja o final da resposta!

Para responder sua pergunta, fiz o seguinte:

Crie uma tabela de amostra:

CREATE TABLE my_error (wrong VARCHAR(50));

Insira alguns registros de amostra:

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');

Em seguida, crie um VIEWcomo sugerido:

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;

Então, SELECT do seu VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Resultado:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Et voilà!

Você pode encontrar tudo isso no violino aqui .

A CTEabordagem:

O mesmo que acima, exceto que o CTEé substituído pelo VIEWseguinte:

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;

O resultado é o mesmo. Você pode então tratá- CTElo como faria com qualquer outra tabela - SELECTapenas por s! Violino disponível aqui .

No geral, acho que a VIEWabordagem é melhor neste caso!

Vérace
fonte
0

Mesa embutida

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 

Pule a união e use um ORno local, conforme sugerido por outros.

paparazzo
fonte