Come avere più di 100 voci nell'istruzione case come variabile


11

Ho scritto un'istruzione case con> 100 scelte in cui sto usando la stessa istruzione in 4 punti in una semplice query.

La stessa query due volte con un'unione tra loro ma sta anche facendo un conteggio e quindi il gruppo contiene anche l'istruzione case.

Questo per rietichettare alcuni nomi di società in cui diversi record per la stessa società sono scritti in modo diverso.

Ho provato a dichiarare una variabile come 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 sono andato a usarlo nella mia dichiarazione select - la query ha appena restituito l'istruzione case come testo e non l'ha valutata.

Inoltre non sono stato in grado di usarlo nel gruppo da - Ho ricevuto questo messaggio di errore:

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

Idealmente, vorrei avere il CASE in un unico posto, in modo che non ci sia alcuna possibilità di aggiornare una riga e non replicarla altrove.

C'è un modo per farlo?

Sono aperto ad altri modi (come forse una funzione - ma non sono sicuro di come usarli in questo modo)

Ecco un esempio di SELECT che sto attualmente utilizzando

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

Lo scopo di questa UNIONE è di restituire tutti i dati per un periodo di tempo e ANCHE di restituire i dati per lo stesso periodo di tempo per 12 mesi precedenti

EDIT: aggiunto un
EDIT2 "CATCH-ALL" mancante: aggiunto un secondo ½
dell'istruzione UNION EDIT3: corretto GROUP BY per includere alcuni altri elementi necessari


In cosa differiscono le 2 parti dell'UNIONE? Sembrano abbastanza simili, ad eccezione delle condizioni WHERE leggermente diverse.
ypercubeᵀᴹ

Questa è la differenza chiave. Le due diverse condizioni DOVE alla data danno oggi e la stessa data 12 mesi fa. Ciò significa che posso quindi confrontare i numeri per quel giorno e lo stesso giorno 12 mesi fa nel livello di presentazione, ma eseguendo la singola query SQL.
kiltannen,

3
Perché non un singolo SELECT con WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
ypercubeᵀᴹ

@ ypercubeᵀᴹ La semplice risposta è che quando ho creato questo all'inizio stavo copiando un modo in cui l'ho fatto da qualche altra parte che utilizzava UNION. Un po 'più complicato è che il limitatore di data è in realtà un acquisto piuttosto più complesso di oggi e la stessa data 12 mesi fa. L'intervallo di date che sto selezionando è dal 1 ° luglio alla data corrente + dal 1 ° luglio precedente alla data che è esattamente 12 mesi fa. (Esercizio finanziario fino a oggi nell'ultimo esercizio 12 mesi fa - questo fornisce un confronto tra crescita o altro per l'esercizio finanziario). MA come AndryM e tu suggerisci, proverò meno l'UNIONE
kiltannen,

Risposte:


11

Un modo semplice per eliminare la ripetizione dell'espressione CASE è usare CROSS APPLY in questo modo:

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 l'aiuto di CROSS APPLY assegni un nome alla tua espressione CASE in modo tale che possa essere referenziato ovunque nella tua dichiarazione. Funziona perché in senso stretto si sta definendo la colonna calcolata in un SELECT nidificato - il SELECT senza DA che segue CROSS APPLY.

Ciò equivale a fare riferimento a una colonna con alias di una tabella derivata, che tecnicamente è questo SELECT nidificato. È sia una sottoquery correlata che una tabella derivata. Come sottoquery correlata, è consentito fare riferimento alle colonne dell'ambito esterno e come tabella derivata consente all'ambito esterno di fare riferimento alle colonne che definisce.

Per una query UNION che utilizza la stessa espressione CASE, è necessario definirla in ogni segmento, non esiste una soluzione alternativa a tale scopo se non l'uso di un metodo di sostituzione completamente diverso anziché CASE. Tuttavia, nel tuo caso specifico è possibile recuperare i risultati senza UNION.

Le due gambe differiscono solo nella condizione WHERE. Uno ha questo:

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

e l'altro questo:

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

Puoi combinarli in questo modo:

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

e applicarlo al SELEZIONATO modificato all'inizio di questa risposta.


Bello Andriy - +1! Ispirato da te :-), ho aggiunto un altro approccio alla mia risposta - a CTE- Non sono sicuro di quale sia l'approccio migliore!
Vérace,

Ciao Andriy, mi piace l'aspetto di questa soluzione. Ho detto che avevo un'UNIONE, ma ero abbastanza stupido da non includerlo nel mio esempio. L'ho fatto ora. Ho il sospetto che questo x da CROSS APPLY non sia probabilmente disponibile per la seconda metà dell'UNION, vero? Quindi questo significherebbe che sarei ancora bloccato con 2 copie del CASO, giusto? (Lo controllerò domani quando torno al lavoro)
kiltannen,

@kiltannen Rilascia il UNIONe includi semplicemente la datecreatedcolonna nella tua GROUP BYclausola (e aggiorna la WHEREclausola per includere entrambe le date che ti interessano).
Scott M,

@ScottM: Non credo che l'OP debba includere la datecreatedcolonna in GROUP BY. A parte questo, sono completamente d'accordo, possono semplicemente combinare le clausole WHERE e abbandonare l'UNIONE.
Andriy M,

@ scott-m dovrò provarlo domani, ma sospetto che non funzioni così bene. In realtà non è un giorno - è potenzialmente diversi mesi. Penso che mi sono imbattuto in un massimo di 11 mesi di dati giornalieri, quindi dove erano iniziati e finiti E poi ho dovuto eseguire un OR per lo stesso periodo di 12 mesi prima. Penso che questo sia finito con un successo nelle prestazioni. Dovrei riprovare, ma ricordo di essermi imbattuto in problemi che non avevo durante la guida dell'UNION. Naturalmente ciò comporta problemi propri. Come quello con cui sto attualmente lottando con ...
Kiltannen,

22

Metti i dati in una tabella

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 unisciti ad esso.

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

In questo modo puoi evitare di mantenere i dati aggiornati in più punti. Usa solo COALESCEdove ne hai bisogno. Puoi incorporarlo in CTE o VIEWs secondo gli altri suggerimenti.


4

Un'altra opzione penso che se è necessario riutilizzarlo in diversi punti, una funzione con valori di tabella Inline sarà valida.

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

La tua selezione sarà così.

  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)

Inoltre, non ho testato questo e anche le prestazioni del codice dovrebbero essere determinate.

EDIT1 : Penso che Andriy ne abbia già dato uno che usa cross apply che redatta il codice. Bene, questo può essere centralizzato poiché qualsiasi cambiamento nella funzione si rifletterà in tutto poiché stai ripetendo lo stesso in altre parti del codice.


3

Vorrei usare a VIEWper fare quello che stai cercando di fare. Naturalmente, potresti correggere i dati sottostanti, ma spesso su questo sito, quelli che fanno domande (consulenti / dbas /) non hanno l'autorità per farlo. L'uso di a VIEWpuò risolvere questo problema! Ho anche fatto uso della UPPERfunzione, un modo economico per risolvere errori in casi come questo.

Ora, dichiari solo VIEWuna volta e puoi usarlo ovunque! In questo modo, hai solo un posto in cui è archiviato ed eseguito il tuo algoritmo di conversione dei dati, aumentando così l'affidabilità e la robustezza del tuo sistema.

Puoi anche usare un CTE ( Common Table Expression ) - vedi in fondo alla risposta!

Per rispondere alla tua domanda, ho fatto quanto segue:

Creare una tabella di esempio:

CREATE TABLE my_error (wrong VARCHAR(50));

Inserisci un paio di record di esempio:

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

Quindi crea un VIEWcome suggerito:

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;

Quindi, SELECT dal tuo VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Risultato:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Et voilà!

Puoi trovare tutto questo sul violino qui .

L' CTEapproccio:

Come sopra, ad eccezione di CTEè sostituito dal VIEWseguente:

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;

Il risultato è lo stesso. È quindi possibile trattare il CTEcome faresti con qualsiasi altro tavolo - SELECTsolo per s! Violino disponibile qui .

Nel complesso, penso che l' VIEWapproccio sia migliore in questo caso!


0

Tavolo incorporato

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 

Salta l'unione e usa un ORnel dove suggerito da altri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.