Як мати більше 100 записів у заяві case як змінної


11

Я написав випадок випадку із> 100 варіантами, де я використовую той самий вислів у 4-х місцях у простому запиті.

Один і той же запит двічі з об'єднанням між ними, але також робить підрахунок, і тому група також містить заяву справи.

Це для відновлення назви деяких компаній, де різні записи для однієї компанії написані по-різному.

Я намагався оголосити змінну 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''
       ...

Коли я пішов використовувати його у своєму операторі select - запит просто повернув випадок справи як текст і не оцінив його.

Я також не зміг його використати в групі, - отримав це повідомлення про помилку:

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

В ідеалі я хотів би мати CASE лише в одному місці - щоб не було шансів я оновлювати один рядок і не повторювати це в іншому місці.

Чи є якийсь спосіб зробити це?

Я відкритий для інших способів (як, можливо, функція - але я не впевнений, як ними користуватися)

Ось зразок вибору, який я зараз використовую

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

Метою цього Спілки є повернення всіх даних за часовий період, а також повернення даних за той самий часовий період протягом 12 місяців раніше

EDIT: Додано пропущений "CATCH-ALL"
EDIT2: Додано другу
половину заяви UNION EDIT3: Виправлено GROUP BY, щоб включити деякі інші необхідні елементи


Чим відрізняються дві частини Спілки? Вони виглядають досить схоже, за винятком дещо інших умов, де БЕЗ.
ypercubeᵀᴹ

У цьому і полягає ключова відмінність. Дві різні умови WHERE на дату дають сьогодні & ту ж дату 12 місяців тому. Це означає, що я можу порівняти числа за цей день і той самий день 12 місяців тому в шарі презентації - але запускаючи єдиний SQL-запит.
kiltannen

3
Чому не з одним SELECT WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
ypercubeᵀᴹ

@ ypercubeᵀᴹ Проста відповідь, коли спочатку будували це, я копіював так, як це робив десь в іншому місці, що використовував UNION. Трохи складніше те, що обмежувач дат насправді є набагато складнішим, ніж сьогодні, та ж дата 12 місяців тому. Діапазон дат, для яких я вибираю, - від 1 липня до поточної дати + від 1 липня до цього до дати, що рівно 12 місяців тому. (Фінансовий рік на сьогодні VS Останній рік з початку року 12 місяців тому - це дає порівняння зростання чи інакше за фінансовий рік). Але, як AndryM та ви пропонуєте, я збираюся спробувати мінус UNION
kiltannen

Відповіді:


11

Один простий спосіб усунути повторення виразу CASE - це використовувати CROSS APPLY так:

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

За допомогою CROSS APPLY ви присвоюєте ім’я виразу CASE таким чином, щоб на нього можна було посилатися будь-де у вашому виписці. Це працює, тому що суворо кажучи, ви визначаєте обчислений стовпець у вкладеному SELECT - ВІД ВІДБУДНОГО ВИБОРУ, що слідує за CROSS APPLY.

Це те саме, що посилання на псевдонім стовпця похідної таблиці - технічно це вкладений SELECT. Це і корельований підзапит, і похідна таблиця. Як корельований підзапит, можна посилатися на стовпці зовнішнього діапазону, а як похідна таблиця дозволяє зовнішньому діапазону посилатися на стовпці, які він визначає.

Для запиту UNION, який використовує той самий вираз CASE, ви повинні визначити його в кожній нозі, для цього немає вирішення, окрім того, щоб використовувати зовсім інший метод заміни замість CASE. Однак у вашому конкретному випадку результати можна отримати без UNION.

Дві ніжки відрізняються лише умовою, де БЕЗ. Один має таке:

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

а інше це:

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

Ви можете комбінувати їх так:

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

і застосувати його до модифікованого SELECT на початку цієї відповіді.


Гарний Андрій - +1! Натхненний вами :-), я додав ще один підхід до своєї відповіді - а CTE- я не впевнений, який найкращий підхід!
Vérace

Привіт, Андрію, мені подобається зовнішній вигляд цього рішення. Я згадав, що мав союз, але я був досить тупий, щоб не включити його до свого прикладу. Я це зробив зараз. Я підозрюю, що цей х від CROSS APPLY, ймовірно, не буде доступний для другої половини Союзу, чи не так? Отже, це означало б, що я все-таки застряг би з 2-ма примірниками СЛУЧА, чи не так? (Я перевірю це завтра, коли повернусь до роботи)
kiltannen

@kiltannen Опустіть UNIONі просто включіть datecreatedстовпець у свій GROUP BYпункт (і оновіть його, WHEREщоб включити обидві дати, які вас цікавлять).
Скотт М

@ScottM: Я не думаю, що ОП повинна включати datecreatedстовпчик у групу BY. Крім цього, я повністю погоджуюся, вони можуть просто поєднувати пункти WHERE і скидати СОЮЗ.
Андрій М

@ scott-m Мені доведеться спробувати це завтра, Але я підозрюю, що це не так добре. Це насправді не один день - це потенційно кілька місяців. Я думаю, що я зіткнувся з тим, що мав до 11 місяців щоденних даних - тож звідки почався і закінчився І тоді мені довелося запустити АБО за той самий період 12 місяців раніше. Я думаю, що це закінчилося хітом вистави. Мені доведеться спробувати ще раз - але я пам’ятаю, що зіткнувся з проблемами, яких у мене не було, коли я працював у UNION. Звичайно, це несе свої проблеми. Як і той, з яким я зараз
борюся

22

Помістіть дані в таблицю

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

і приєднатися до нього.

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

Таким чином ви можете уникнути оновлення даних у багатьох місцях. Просто використовуйте те, COALESCEде вам це потрібно. Ви можете включити це до CTE або VIEWs відповідно до інших пропозицій.


4

Ще один варіант, я думаю, якщо вам потрібно повторно використовувати його в декількох місцях, функція, що оцінюється в таблиці Inline, буде хорошою.

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

Ваш вибір буде таким.

  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)

Також я цього не перевіряв, і продуктивність коду також повинна визначатися.

EDIT1 : Я думаю, Андрій вже дав той, хто використовує крос застосувати, який редагує код. Ну, це може бути централізовано, оскільки будь-які зміни функції відобразяться у всіх, оскільки ви повторюєте те саме в інших частинах коду.


3

Я б використовував a, VIEWщоб робити те, що ви намагаєтеся зробити. Можна, звичайно, виправити основні дані, але часто на цьому веб-сайті ті, хто задає питання (консультанти / dbas /), не мають цього робити. Використання a VIEWможе вирішити цю проблему! Я також скористався UPPERфункцією - дешевим способом вирішення помилок у таких випадках.

Тепер ви декларуєте лише VIEWодин раз і можете використовувати його будь-де! Таким чином, у вас є лише одне місце, де зберігається і працює ваш алгоритм перетворення даних, тим самим збільшуючи надійність і надійність вашої системи.

Ви також можете використовувати CTE ( загальний вираз таблиці ) - дивіться внизу відповіді!

Щоб відповісти на ваше запитання, я зробив наступне:

Створіть зразок таблиці:

CREATE TABLE my_error (wrong VARCHAR(50));

Вставте кілька зразків записів:

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

Потім створіть, VIEWяк було запропоновано:

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;

Потім SELECT від вашого VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Результат:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Et voilà!

Ви можете знайти все це на скрипці тут .

CTEпідхід:

Те саме, що вище, за винятком того CTE, що заміщене на VIEWнаступне:

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;

Результат такий же. Потім ви можете ставитися так CTEсамо, як і до будь-якої іншої таблиці - SELECTтільки для s! Тут доступна скрипка .

В цілому, я думаю, що VIEWпідхід у цьому випадку кращий!


0

Вбудована таблиця

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 

Пропустіть союз і використовуйте ORте, де пропонують інші.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.