Wie man mehr als 100 Einträge in der case-Anweisung als Variable hat

11

Ich habe eine case-Anweisung mit> 100 Auswahlmöglichkeiten geschrieben, bei der ich dieselbe Anweisung an 4 Stellen in einer einfachen Abfrage verwende.

Dieselbe Abfrage zweimal mit einer Vereinigung zwischen ihnen, aber es wird auch gezählt, und daher enthält die Gruppe von auch die case-Anweisung.

Dies dient dazu, einige Firmennamen neu zu kennzeichnen, bei denen unterschiedliche Datensätze für dieselbe Firma unterschiedlich geschrieben sind.

Ich habe versucht, eine Variable als VarChar (MAX) zu deklarieren.

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''
       ...

Als ich es in meiner select-Anweisung verwendete, gab die Abfrage die case-Anweisung nur als Text zurück und bewertete sie nicht.

Ich konnte es auch nicht in der Gruppe verwenden von - Ich habe folgende Fehlermeldung erhalten:

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

Idealerweise möchte ich den CASE nur an einem einzigen Ort haben - damit ich keine Chance habe, eine Zeile zu aktualisieren und diese nicht an anderer Stelle zu replizieren.

Gibt es eine Möglichkeit, dies zu tun?

Ich bin offen für andere Möglichkeiten (wie vielleicht eine Funktion - aber ich bin nicht sicher, wie ich sie so verwenden soll)

Hier ist ein Beispiel für das SELECT, das ich derzeit verwende

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

Der Zweck dieser UNION besteht darin, alle Daten für einen bestimmten Zeitraum zurückzugeben und AUCH 12 Monate zuvor Daten für denselben Zeitraum zurückzugeben

EDIT: Ein fehlendes "CATCH-ALL" wurde
hinzugefügt. EDIT2: Eine zweite
Hälfte der UNION-Anweisung wurde hinzugefügt. EDIT3: Die GROUP BY wurde korrigiert, um einige andere notwendige Elemente aufzunehmen

kiltannen
quelle
Wie unterscheiden sich die beiden Teile der UNION? Sie sehen ziemlich ähnlich aus, bis auf die leicht unterschiedlichen WHERE-Bedingungen.
Ypercubeᵀᴹ
Das ist der Hauptunterschied. Die zwei unterschiedlichen WHERE-Bedingungen am Datum geben heute und das gleiche Datum vor 12 Monaten an. Dies bedeutet, dass ich dann die Zahlen für diesen Tag und denselben Tag vor 12 Monaten in der Präsentationsebene vergleichen kann - aber die einzelne SQL-Abfrage ausführen kann.
Kiltannen
3
Warum nicht ein einziges SELECT mit WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
Ypercubeᵀᴹ
@ ypercubeᵀᴹ Die einfache Antwort ist, dass ich beim Erstellen dieses Dokuments zunächst so kopiert habe, wie ich es an einem anderen Ort getan habe, an dem UNION verwendet wurde. Das etwas kompliziertere ist, dass der Datumsbegrenzer tatsächlich ziemlich komplex ist als heute und das gleiche Datum vor 12 Monaten. Der Datumsbereich, für den ich mich entscheide, reicht vom 1. Juli bis zum aktuellen Datum + vom 1. Juli davor bis zu dem Datum, das genau vor 12 Monaten liegt. (Bisheriges Geschäftsjahr im Vergleich zum letzten Geschäftsjahr vor 12 Monaten - dies gibt einen Vergleich des Wachstums oder auf andere Weise für das Geschäftsjahr). ABER wie AndryM & Sie vorschlagen, ich werde es ohne die UNION versuchen
kiltannen

Antworten:

11

Eine einfache Möglichkeit, die Wiederholung des CASE-Ausdrucks zu vermeiden, besteht darin, CROSS APPLY wie folgt zu verwenden:

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

Mit Hilfe von CROSS APPLY weisen Sie Ihrem CASE-Ausdruck einen Namen zu, auf den überall in Ihrer Anweisung verwiesen werden kann. Dies funktioniert, weil Sie genau genommen die berechnete Spalte in einem verschachtelten SELECT definieren - dem FROM-less SELECT, das auf die CROSS APPLY folgt.

Dies entspricht dem Verweisen auf eine Alias-Spalte einer abgeleiteten Tabelle - was technisch gesehen dieses verschachtelte SELECT ist. Es ist sowohl eine korrelierte Unterabfrage als auch eine abgeleitete Tabelle. Als korrelierte Unterabfrage darf auf die Spalten des äußeren Bereichs verwiesen werden, und als abgeleitete Tabelle kann der äußere Bereich auf die von ihm definierten Spalten verweisen.

Für eine UNION-Abfrage, die denselben CASE-Ausdruck verwendet, müssen Sie ihn in jedem Abschnitt definieren. Es gibt keine Problemumgehung dafür, außer dass anstelle des CASE eine völlig andere Ersetzungsmethode verwendet wird. In Ihrem speziellen Fall ist es jedoch möglich, die Ergebnisse ohne UNION abzurufen.

Die beiden Beine unterscheiden sich nur im WHERE-Zustand. Man hat folgendes:

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

und der andere dies:

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

Sie können sie folgendermaßen kombinieren:

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

und wenden Sie es auf das geänderte SELECT am Anfang dieser Antwort an.

Andriy M.
quelle
Netter Andriy - +1! Inspiriert von dir :-) habe ich meiner Antwort einen anderen Ansatz hinzugefügt - a CTE- Ich bin mir nicht sicher, welcher der beste Ansatz ist!
Vérace
Hallo Andriy, ich mag das Aussehen dieser Lösung. Ich habe zwar erwähnt, dass ich eine UNION habe - aber ich war dumm genug, sie nicht in mein Beispiel aufzunehmen. Ich habe es jetzt getan. Ich vermute, dass dieses x von CROSS APPLY der zweiten Hälfte der UNION wahrscheinlich nicht zur Verfügung steht, oder? Das würde also bedeuten, dass ich immer noch mit 2 Kopien des CASE feststecken würde, stimmt das? (Ich werde es morgen überprüfen, wenn ich wieder zur Arbeit
komme
@kiltannen Löschen Sie die UNIONund fügen Sie einfach die datecreatedSpalte in Ihre GROUP BYKlausel ein (und aktualisieren Sie die WHEREKlausel, um beide Daten einzuschließen , an denen Sie interessiert sind).
Scott M
@ScottM: Ich denke nicht, dass das OP die datecreatedSpalte in die GROUP BY aufnehmen muss. Abgesehen davon stimme ich voll und ganz zu, sie können einfach die WHERE-Klauseln kombinieren und die UNION fallen lassen.
Andriy M
@ scott-m Ich werde es morgen versuchen müssen, aber ich vermute, dass das nicht so gut funktioniert. Es ist nicht wirklich ein Tag - es sind möglicherweise mehrere Monate. Ich glaube, ich hatte bis zu 11 Monate tägliche Daten - also wo hatte Start und Ende UND dann musste ich 12 Monate zuvor für den gleichen Zeitraum einen OP durchführen. Ich denke, das endete mit einem Performance-Hit. Ich müsste es noch einmal versuchen - aber ich erinnere mich, dass ich auf Probleme gestoßen bin, die ich beim Ausführen der UNION nicht hatte. Das bringt natürlich eigene Probleme mit sich. Wie der, mit dem ich gerade
ringe
22

Legen Sie die Daten in eine Tabelle

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

und mach mit.

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

Auf diese Weise können Sie vermeiden, die Daten an mehreren Stellen auf dem neuesten Stand zu halten. Verwenden COALESCESie einfach das, wo Sie es brauchen. Sie können dies VIEWgemäß den anderen Vorschlägen in CTE oder s integrieren.

LoztInSpace
quelle
4

Eine weitere Option, die ich denke, wenn Sie sie an mehreren Stellen wiederverwenden müssen, ist eine Inline-Tabellenwertfunktion eine gute.

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

Ihre Auswahl wird so sein.

  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)

Außerdem habe ich dies nicht getestet und die Leistung des Codes sollte ebenfalls bestimmt werden.

EDIT1 : Ich denke, andriy hat bereits eine gegeben, die cross apply verwendet und den Code redigiert. Nun, diese kann zentralisiert werden, da sich alle Änderungen in der Funktion auf alle auswirken, da Sie diese in anderen Teilen des Codes wiederholen.

Biju Jose
quelle
3

Ich würde ein verwenden VIEW , um das zu tun, was Sie versuchen zu tun. Sie können natürlich die zugrunde liegenden Daten korrigieren, aber häufig auf dieser Website haben diejenigen, die Fragen stellen (Berater / dbas /), nicht die Berechtigung, dies zu tun. Mit a VIEWkann dieses Problem gelöst werden! Ich habe auch die UPPERFunktion genutzt - eine kostengünstige Möglichkeit, Fehler in solchen Fällen zu beheben.

Jetzt deklarieren Sie nur die VIEW einmal und können es überall verwenden! Auf diese Weise haben Sie nur einen Ort, an dem Ihr Datenkonvertierungsalgorithmus gespeichert und ausgeführt wird, wodurch die Zuverlässigkeit und Robustheit Ihres Systems erhöht wird.

Sie können auch einen CTE verwenden ( Common Table Expression ) verwenden - siehe unten in der Antwort!

Um Ihre Frage zu beantworten, habe ich Folgendes getan:

Erstellen Sie eine Beispieltabelle:

CREATE TABLE my_error (wrong VARCHAR(50));

Fügen Sie einige Beispieldatensätze ein:

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

Erstellen Sie dann eine VIEWwie vorgeschlagen:

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;

Dann SELECT von Ihrem VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Ergebnis:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Et voilà!

All dies finden Sie hier auf der Geige .

Der CTEAnsatz:

Gleich wie oben, außer CTEdass das VIEWwie folgt ersetzt wird:

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;

Das Ergebnis ist das gleiche. Sie können das dann CTEwie jeden anderen Tisch behandeln - nur für SELECTs! Geige hier erhältlich .

Insgesamt denke ich, dass der VIEWAnsatz in diesem Fall besser ist!

Vérace
quelle
0

Eingebetteter Tisch

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 

Überspringen Sie die Gewerkschaft und verwenden Sie ein ORin dem von anderen vorgeschlagenen wo.

Paparazzo
quelle