如何将case语句中的100个以上的条目作为变量


11

我编写了一个包含100多个选择的case语句,在一个简单的查询中,我在4个地方使用同一条语句。

相同的查询两次,并且它们之间有一个并集,但也进行计数,因此group by还包含case语句。

这是为了重新标记某些公司名称,其中同一公司的不同记录的拼写不同。

我试图将变量声明为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.

理想情况下,我只想将CASE放在一个地方-这样我就没有机会更新一行而不在其他地方复制它了。

有什么办法吗?

我对其他方式持开放态度(例如可能是一个函数-但我不确定如何使用它们)

这是我当前正在使用的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的目的是返回一个时间段内的所有数据,ALSO还返回之前12个月内相同时间段的数据

编辑:添加了一个丢失的“全部捕获”编辑2:添加了
UNION语句的第二个½
编辑3:更正了GROUP BY以包括一些其他必要的元素


UNION的两个部分有何不同?除了WHERE条件略有不同外,它们看起来非常相似。
ypercubeᵀᴹ

那是关键的区别。该日期的两个不同WHERE条件给出了今天和12个月前的同一日期。这意味着我可以在表示层中比较当天和12个月前同一天的数字-但运行单个SQL查询。
基尔塔嫩

3
为什么不使用单个SELECT WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))
ypercubeᵀᴹ

@ypercubeᵀᴹ简单的答案是,在最初构建此文件时,我正在复制在其他使用UNION的地方进行复制的方式。稍微复杂一点的是,日期限制器实际上比今天和12个月前的同一日期复杂得多。我选择的日期范围是从7月1日到当前日期+从之前的7月1日到恰好12个月前的日期。(截至12个月前的财政年度至今的年初至今-比较财政年度的增长或其他情况)。但是,正如AndryM和您所建议的那样,我将尝试减去UNION
kiltannen

Answers:


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的FROM-less SELECT)中定义计算列。

这与引用派生表的别名列相同–从技术上讲,此嵌套的SELECT是该列。它既是相关子查询又是派生表。作为相关子查询,它可以引用外部作用域的列,而作为派生表,它允许外部作用域引用其定义的列。

对于使用相同CASE表达式的UNION查询,您必须在每条语句中都定义它,除了使用完全不同的替换方法而不是CASE之外,没有其他解决方法。但是,在您的特定情况下,可以在不使用UNION的情况下获取结果。

两条腿仅在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。


好人安德里(Andriy)-+1!受您的启发:-),我为答案添加了另一种方法-a-我CTE不确定哪种方法最好!
Vérace

嗨,安德里,我喜欢这种解决方案的外观。我确实提到我有一个UNION-但是我很笨,没有在示例中包含它。我现在已经这样做了。我怀疑来自CROSS APPLY的x不可能在UNION的下半年可用吗?所以这意味着我仍然会被困在CASE的2个副本中,对吗?(明天上班时,我会检查出来的)
基尔塔嫩

@kiltannen删除UNION并仅datecreated在您的GROUP BY子句中包括该列(并更新WHERE子句以包括您感兴趣的两个日期)。
Scott M

@ScottM:我认为OP不需要datecreated在GROUP BY中包括该列。除此之外,我完全同意,它们可以组合WHERE子句并放弃UNION。
Andriy M

@ scott-m我明天必须尝试一下,但是我怀疑它不能很好地工作。实际上不是一天-可能要几个月。我认为我遇到的问题是长达11个月的每日数据-因此,从哪里开始和结束,然后我必须在12个月前的同一时间运行OR。我认为这最终会导致性能下降。我必须再次尝试-但我确实记得遇到了运行UNION时没有遇到的问题。当然,这确实会带来问题。就像我目前正在与之搏斗的那..
kiltannen

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在需要的地方使用。您可以VIEW根据其他建议将其合并到CTE或中。


4

我认为,如果您需要在多个地方重复使用它,那么内联表值函数将是一个不错的选择。

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:我认为andriy已经给出了一个使用交叉应用程序来编辑代码的程序。好吧,由于您在代码的其他部分重复相同的功能,因此对功能的任何更改都将反映所有内容,因此可以对此进行集中管理。


3

我会用VIEW做您想做的事情。您当然可以更正基础数据,但是经常在此站点上,那些提出问题的人(顾问/ dbas /)无权执行此操作。使用一个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

等等!

您可以在此处的小提琴中找到所有这些内容。

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-仅用于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在其他人建议的位置使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.