วิธีการมีมากกว่า 100 รายการในคำสั่ง case เป็นตัวแปร


11

ฉันเขียนคำสั่ง case ด้วยตัวเลือกมากกว่า 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 - แบบสอบถามเพิ่งส่งคืนคำสั่ง case เป็นข้อความและไม่ได้ประเมิน

ฉันยังไม่สามารถใช้ในกลุ่มได้ - ฉันได้รับข้อความแสดงข้อผิดพลาดนี้:

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

โดยหลักการแล้วฉันต้องการที่จะมีเคสในที่แห่งเดียว - เพื่อให้ไม่มีโอกาสที่ฉันจะอัปเดตหนึ่งบรรทัดและไม่ลอกเลียนแบบที่อื่น

มีวิธีการทำเช่นนี้?

ฉันเปิดให้ใช้วิธีอื่น (เช่นอาจเป็นฟังก์ชั่น - แต่ฉันไม่แน่ใจว่าจะใช้มันอย่างไร)

นี่คือตัวอย่างของ SELECT ที่ฉันกำลังใช้อยู่

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

จุดประสงค์สำหรับ UNION นี้คือการส่งคืนข้อมูลทั้งหมดสำหรับช่วงเวลาชั่วคราวและนอกจากนี้ยังจะส่งคืนข้อมูลช่วงเวลาเดียวกันสำหรับ 12 เดือนก่อนหน้านี้

แก้ไข: เพิ่ม "CATCH-ALL" ที่ขาดหายไปแก้ไข 2: เพิ่ม
second วินาทีของคำสั่ง UNION
EDIT3: แก้ไข GROUP BY เพื่อรวมองค์ประกอบที่จำเป็นอื่น ๆ


ส่วนที่ 2 ของ UNION แตกต่างกันอย่างไร พวกเขามีลักษณะที่คล้ายกันค่อนข้างยกเว้นเงื่อนไขที่แตกต่างกันเล็กน้อย
ypercubeᵀᴹ

นั่นคือความแตกต่างที่สำคัญ สองเงื่อนไขที่แตกต่างกันในวันที่ให้วันนี้และวันเดียวกัน 12 เดือนที่ผ่านมา ซึ่งหมายความว่าฉันสามารถเปรียบเทียบตัวเลขสำหรับวันนั้นและในวันเดียวกันเมื่อ 12 เดือนที่แล้วในเลเยอร์การนำเสนอ - แต่เรียกใช้แบบสอบถาม SQL เดียว
kiltannen

3
ทำไมไม่เลือกด้วยตัวเดียว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 ใช้เช่นนี้:

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 นำไปใช้คุณกำหนดชื่อให้กับนิพจน์กรณีของคุณในลักษณะที่สามารถอ้างอิงได้ทุกที่ในคำสั่งของคุณ มันทำงานได้เพราะพูดอย่างเคร่งครัดว่าคุณกำลังกำหนดคอลัมน์ที่คำนวณในSELECT ที่ซ้อนกัน - SELECT จากน้อยกว่าที่เป็นไปตาม CROSS นำไปใช้

นี่เหมือนกับการอ้างอิงคอลัมน์ aliased ของตารางที่ได้รับ - ซึ่งในทางเทคนิค SELECT ที่ซ้อนกันนี้คือ มันเป็นทั้งแบบสอบถามย่อยที่มีความสัมพันธ์และตารางที่ได้รับ ในฐานะที่เป็นแบบสอบถามย่อยที่มีความสัมพันธ์กันจะได้รับอนุญาตให้อ้างอิงคอลัมน์ของขอบเขตด้านนอกและเป็นตารางที่ได้รับซึ่งอนุญาตให้ขอบเขตด้านนอกอ้างอิงคอลัมน์ที่กำหนดไว้

สำหรับเคียวรี UNION ที่ใช้นิพจน์ CASE เดียวกันคุณต้องกำหนดในแต่ละเลกไม่มีวิธีแก้ปัญหายกเว้นว่าจะใช้วิธีการเปลี่ยนที่แตกต่างกันโดยสิ้นเชิงแทน CASE อย่างไรก็ตามในกรณีเฉพาะของคุณเป็นไปได้ที่จะดึงผลลัพธ์โดยไม่มียูเนี่ยน

ขาทั้งสองแตกต่างกันในสภาพ WHERE เท่านั้น หนึ่งมีสิ่งนี้:

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 ที่ถูกแก้ไขในตอนต้นของคำตอบนี้


Nice one Andriy - +1! ได้แรงบันดาลใจจากคุณ :-) ฉันได้เพิ่มวิธีการอื่นในคำตอบของฉันCTE- ฉันไม่แน่ใจว่าวิธีใดดีที่สุด!
Vérace

สวัสดี Andriy ฉันชอบรูปลักษณ์ของโซลูชันนี้ ฉันพูดถึงว่าฉันมีสหภาพ - แต่ฉันก็โง่พอที่จะไม่รวมไว้ในตัวอย่างของฉัน ฉันทำไปแล้ว ฉันสงสัยว่า x จาก CROSS นี้จะไม่สามารถใช้ได้กับครึ่งหลังของ UNION ใช่หรือไม่ ดังนั้นนี่หมายความว่าฉันจะยังคงติดอยู่กับ CASE 2 ชุดใช่ไหม? (ฉันจะตรวจสอบออกในวันพรุ่งนี้เมื่อฉันได้รับกลับไปทำงาน)
kiltannen

@kiltannen ปล่อยUNIONและเพียงแค่รวมdatecreatedคอลัมน์ในGROUP BYส่วนคำสั่งของคุณ(และอัปเดตส่วนWHEREคำสั่งเพื่อรวมทั้งวันที่ที่คุณสนใจ)
Scott M

@ScottM: ฉันไม่คิดว่า OP จำเป็นต้องรวมdatecreatedคอลัมน์ใน GROUP BY นอกเหนือจากนั้นฉันเห็นด้วยอย่างสมบูรณ์พวกเขาสามารถรวมเอาคำสั่ง WHERE และคลองยูเนี่ยน
Andriy M

@ scott-m ฉันจะต้องลองวันพรุ่งนี้ แต่ฉันคิดว่ามันใช้งานไม่ได้ ไม่ใช่วันเดียวจริง ๆ แล้วอาจมีหลายเดือน ฉันคิดว่าสิ่งที่ฉันพบคือมีข้อมูลรายวันสูงสุด 11 เดือนดังนั้นจุดเริ่มต้นและจุดสิ้นสุดจึงต้องเรียกใช้ OR ในช่วงเวลาเดียวกันเมื่อ 12 เดือนก่อน ฉันคิดว่าสิ่งนี้จบลงด้วยการแสดงยอดฮิต ฉันต้องลองอีกครั้ง - แต่ฉันจำได้ว่าพบปัญหาที่ฉันไม่ได้ทำเมื่อใช้ยูเนี่ยน แน่นอนว่าจะนำปัญหาของมันมาเอง เช่นเดียวกับที่ฉันกำลังต่อสู้กับ ..
เวลา 18.18 น

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 table ที่มีค่าจะเป็นตัวเลือกที่ดี

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)

นอกจากนี้ฉันยังไม่ได้ทดสอบสิ่งนี้และควรพิจารณาถึงประสิทธิภาพของรหัสด้วย

แก้ไข 1 : ฉันคิดว่า andriy ได้ให้หนึ่งซึ่งใช้ cross ใช้ที่ redacts รหัส ทีนี้อันนี้สามารถรวมศูนย์ได้เนื่องจากการเปลี่ยนแปลงใด ๆ ในฟังก์ชั่นนั้นจะสะท้อนให้เห็นในเมื่อคุณกำลังทำซ้ำในส่วนอื่น ๆ ของรหัส


3

ฉันจะใช้ a VIEWเพื่อทำสิ่งที่คุณพยายามจะทำ แน่นอนคุณสามารถแก้ไขข้อมูลพื้นฐาน แต่บ่อยครั้งในเว็บไซต์นี้คำถามที่ถาม (ที่ปรึกษา / dbas /) ไม่มีอำนาจในการทำเช่นนี้ การใช้ a VIEWสามารถแก้ปัญหานี้ได้! ฉันใช้ประโยชน์จากUPPERฟังก์ชั่น - วิธีที่ถูกในการแก้ไขข้อผิดพลาดในกรณีเช่นนี้

ตอนนี้คุณประกาศเพียงVIEWครั้งเดียวและสามารถใช้ได้ทุกที่! ด้วยวิธีนี้คุณจะมีที่เดียวที่อัลกอริทึมการแปลงข้อมูลของคุณถูกเก็บและรันซึ่งจะเป็นการเพิ่มความน่าเชื่อถือและความทนทานของระบบของคุณ

คุณสามารถใช้ CTE ( Common Table Expression ) - ดูคำตอบด้านล่าง!

เพื่อตอบคำถามของคุณฉันได้ทำสิ่งต่อไปนี้:

สร้างตารางตัวอย่าง:

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

และอื่น ๆ !

คุณสามารถค้นหาทั้งหมดนี้ในซอนี่

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เช่นเดียวกับตารางอื่น ๆ - สำหรับSELECTs เท่านั้น! ซอใช้ได้ที่นี่

โดยรวมแล้วฉันคิดว่าVIEWวิธีนี้ดีกว่าในกรณีนี้!


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.