Jak mieć ponad 100 wpisów w instrukcji case jako zmiennej


11

Napisałem instrukcję przypadku z> 100 wyborami, w której używam tej samej instrukcji w 4 miejscach w prostym zapytaniu.

To samo zapytanie dwukrotnie z połączeniem między nimi, ale również wykonuje zliczanie, a zatem grupa według zawiera również instrukcję case.

Ma to na celu ponowne oznakowanie niektórych nazw firm, w których różne zapisy dla tej samej firmy są zapisane inaczej.

Próbowałem zadeklarować zmienną jako 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''
       ...

Kiedy poszedłem użyć go w mojej instrukcji select - zapytanie właśnie zwróciło instrukcję case jako tekst i jej nie oceniłem.

Nie mogłem również użyć go w grupie przez - otrzymałem ten komunikat o błędzie:

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

Idealnie chciałbym mieć CASE w jednym miejscu - aby nie było szansy, żebym zaktualizował jedną linię i nie powielałbym jej gdzie indziej.

Czy jest jakiś sposób to zrobić?

Jestem otwarty na inne sposoby (może jakaś funkcja - ale nie jestem pewien, jak ich używać)

Oto próbka WYBORU, którego obecnie używam

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

Celem tej unii jest zwrócenie wszystkich danych dla okresu czasu oraz TAKŻE zwrócenie danych dla tego samego okresu dla wcześniejszych 12 miesięcy

EDYCJA: Dodano brakujący „CATCH-ALL”
EDIT2: Dodano drugi ½ instrukcji UNION
EDIT3: Poprawiono GROUP BY, aby zawierała inne niezbędne elementy


Czym różnią się 2 części UNII? Wyglądają dość podobnie, z wyjątkiem nieco innych warunków GDZIE.
ypercubeᵀᴹ

To jest kluczowa różnica. Dwa różne warunki GDZIE w dniu podają dzisiaj i tę samą datę 12 miesięcy temu. Oznacza to, że mogę następnie porównać liczby dla tego dnia i tego samego dnia 12 miesięcy temu w warstwie prezentacji - ale uruchamiając pojedyncze zapytanie SQL.
kiltannen

3
Dlaczego nie jeden WYBIERZ z WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
ypercubeᵀᴹ

@ ypercubeᵀᴹ Prosta odpowiedź brzmi: kiedy budowałem to na początku, kopiowałem sposób, w jaki to zrobiłem w innym miejscu, w którym był UNION. Nieco bardziej skomplikowane jest to, że ogranicznik daty jest w rzeczywistości dość skomplikowany niż dzisiaj i ta sama data 12 miesięcy temu. Zakres dat, który wybieram, to od 1 lipca do bieżącej daty + od 1 lipca wcześniej do daty dokładnie 12 miesięcy temu. (Od roku obrotowego do ostatniego roku obrotowego przed rokiem 12 miesięcy temu - daje to porównanie wzrostu lub innego wyniku dla roku budżetowego). ALE jak AndryM i sugerujesz, spróbuję minus UNIA
kiltannen

Odpowiedzi:


11

Jednym łatwym sposobem na wyeliminowanie powtarzania wyrażenia CASE jest użycie aplikacji KRZYŻ APLIKUJ w następujący sposób:

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

Za pomocą aplikacji CROSS APPLY przypisujesz nazwę do wyrażenia CASE w taki sposób, że można do niego odwoływać się w dowolnym miejscu instrukcji. Działa, ponieważ ściśle mówiąc, definiujesz kolumnę obliczeniową w zagnieżdżonym WYBORZE - WYBIERZ BEZ OD, który następuje po ZASTOSOWANIU KRZYŻOWYM.

Jest to to samo, co odwołanie do kolumny aliasu tabeli pochodnej - którym technicznie jest ten zagnieżdżony WYBÓR. Jest to zarówno skorelowane podkwerenda, jak i tabela pochodna. Jako skorelowane podkwerenda można odwoływać się do kolumn zakresu zewnętrznego, a jako tabela pochodna pozwala zakresowi zewnętrznemu odwoływać się do kolumn, które definiuje.

W przypadku zapytania UNION, które korzysta z tego samego wyrażenia CASE, musisz zdefiniować je w każdej nodze, nie można tego obejść, z wyjątkiem użycia zupełnie innej metody zastępowania zamiast CASE. Jednak w twoim konkretnym przypadku możliwe jest pobranie wyników bez UNION.

Dwie nogi różnią się tylko GDZIE. Jeden ma to:

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

a drugi to:

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

Możesz połączyć je w następujący sposób:

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

i zastosuj go do zmodyfikowanego WYBORU na początku tej odpowiedzi.


Niezły Andriy - +1! Zainspirowany przez ciebie :-), dodałem inne podejście do mojej odpowiedzi - a CTE- nie jestem pewien, które z nich jest najlepsze!
Vérace

Cześć Andriy, podoba mi się wygląd tego rozwiązania. Wspominałem, że mam UNIĘ - ale byłem na tyle głupi, by nie uwzględnić jej w moim przykładzie. Zrobiłem to teraz. Podejrzewam, że ten x z CROSS APPLY prawdopodobnie nie będzie dostępny w drugiej połowie UNII, prawda? Oznaczałoby to, że nadal tkwiłyby mnie 2 kopie CASE, prawda? (
Sprawdzę

@kiltannen Upuść UNIONi po prostu dołącz datecreatedkolumnę do GROUP BYklauzuli (i zaktualizuj WHEREklauzulę, aby uwzględnić obie daty, które Cię interesują).
Scott M

@ScottM: Nie sądzę, że OP musi uwzględnić datecreatedkolumnę w GROUP BY. Poza tym, całkowicie się zgadzam, mogą po prostu połączyć klauzule WHERE i porzucić UNION.
Andriy M

@ scott-m Będę musiał spróbować tego jutro, ale podejrzewam, że to nie działa tak dobrze. To właściwie nie jest jeden dzień - potencjalnie kilka miesięcy. Myślę, że to, na co wpadłem, miało do 11 miesięcy dziennych danych - więc gdzie zaczynał się i kończył ORAZ potem musiałem uruchomić salę operacyjną dla tego samego okresu 12 miesięcy wcześniej. Myślę, że skończyło się to hitem wydajności. Musiałbym spróbować jeszcze raz - ale pamiętam, że napotkałem problemy, których nie miałem podczas uruchamiania UNION. Oczywiście, że rodzi to własne problemy. Jak ten, z którym obecnie walczę ...
kiltannen

22

Umieść dane w tabeli

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

i dołącz do tego.

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

W ten sposób można uniknąć aktualizowania danych w wielu miejscach. Po prostu użyj COALESCEtam, gdzie jest to potrzebne. Możesz włączyć to do CTE lub VIEWs zgodnie z innymi sugestiami.


4

Inna opcja, myślę, że jeśli musisz ponownie użyć jej w kilku miejscach, funkcja ceniona w tabeli Inline będzie dobra.

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

Twój wybór będzie taki.

  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)

Ponadto nie testowałem tego i należy również określić wydajność kodu.

EDYCJA 1 : Myślę, że andriy już podał taki, który używa krzyżowania, który redaguje kod. Cóż, ten może być scentralizowany, ponieważ wszelkie zmiany funkcji odzwierciedlą się we wszystkich, ponieważ powtarzasz to samo w innych częściach kodu.


3

Użyłbym a, VIEWaby zrobić to, co próbujesz zrobić. Możesz oczywiście poprawić podstawowe dane, ale często na tej stronie osoby zadające pytania (konsultanci / dbas /) nie mają do tego uprawnień. Za pomocą a VIEWmożna rozwiązać ten problem! Korzystałem także z UPPERfunkcji - tani sposób rozwiązywania błędów w takich przypadkach.

Teraz deklarujesz tylko VIEWraz i możesz go używać w dowolnym miejscu! W ten sposób masz tylko jedno miejsce, w którym algorytm konwersji danych jest przechowywany i uruchamiany, zwiększając w ten sposób niezawodność i niezawodność systemu.

Możesz także użyć CTE ( Common Table Expression ) - patrz dół odpowiedzi!

Aby odpowiedzieć na twoje pytanie, wykonałem następujące czynności:

Utwórz przykładową tabelę:

CREATE TABLE my_error (wrong VARCHAR(50));

Wstaw kilka przykładowych rekordów:

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

Następnie utwórz VIEWzgodnie z sugestią:

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;

Następnie SELECT z Twojego VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Wynik:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Gotowe!

Możesz to wszystko znaleźć na skrzypcach tutaj .

CTEPodejście:

Taki sam jak powyżej, z wyjątkiem tego, że CTEzastąpiono VIEWnastępującym:

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;

Wynik jest taki sam. Możesz wtedy traktować CTEjak każdy inny stół - SELECTtylko dla s! Skrzypce dostępne tutaj .

Ogólnie uważam, że VIEWw tym przypadku podejście jest lepsze!


0

Wbudowany stół

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 

Pomiń związek i skorzystaj z polecenia, ORw którym sugerują inni.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.