在 SELECT 中处理多个 CASE 语句的有效方法

Abh*_*bhi 4 sql-server sql-server-2016

我运行了一份报告,在该报告中,我遇到了一种情况,即基于基本上是键或 Id 的列值,我需要从映射 Id 表中获取相应的值。像下面这样:

    SELECT
     (case when [column1='A'] then (select value from Table1) 
           when [column1='B'] then (select value from Table2) 
           when [column1='C'] then (select value from Table3)

           and so on uptil 35 more 'when' conditions ...

  ELSE column1 end) Value 
from Table1
Run Code Online (Sandbox Code Playgroud)

更确切地说:

SELECT 
(case when [A.column1='1'] 
then (select value from B where B.clientId=100 and A.column1=B.Id) 
when [A.column1='2'] 
then (select value from C where C.clientId=100 and A.column1=C.Id) 
when [A.column1='3'] 
then (select value from D where D.clientId=100 and A.column1=D.Id) 
...
and so on uptil 30 more 'when' conditions 
...
ELSE column1 end) 
FROM A
Run Code Online (Sandbox Code Playgroud)

在表 B、C、D.. 等中,我们维护所有客户的数据。每个客户端都有一个特定的ClientId,这些表 B、C、D 等都有索引IdClientId列。

在 SQL Server 中是否有一种有效的方法来处理这个问题?

Joe*_*ish 7

我假设您在子查询中的表上有适当的索引。我模拟了一些快速测试数据并在 table 中放入了 1000 万行A。我不是创建 30 个表的游戏,所以我只为CASE表达式创建了 3 个。我认为 3 足以显示一般原则。

DROP TABLE IF EXISTS dbo.B;
CREATE TABLE dbo.B (
    ClientID INT NOT NULL,
    Id VARCHAR(20) NOT NULL,
    [Value] VARCHAR(100),
    PRIMARY KEY (ClientID, Id)
);

INSERT INTO B VALUES (100, '1', 'TABLE B'); 

DROP TABLE IF EXISTS dbo.C;
CREATE TABLE dbo.C (
    ClientID INT NOT NULL,
    Id VARCHAR(20) NOT NULL,
    [Value] VARCHAR(100),
    PRIMARY KEY (ClientID, Id)
);

INSERT INTO C VALUES (100, '2', 'TABLE C'); 

DROP TABLE IF EXISTS dbo.D;
CREATE TABLE dbo.D (
    ClientID INT NOT NULL,
    Id VARCHAR(20) NOT NULL,
    [Value] VARCHAR(100),
    PRIMARY KEY (ClientID, Id)
);

INSERT INTO D VALUES (100, '3', 'TABLE D'); 


DROP TABLE IF EXISTS dbo.A;
CREATE TABLE dbo.A (
    column1 VARCHAR(20) NOT NULL
);

INSERT INTO dbo.A WITH (TABLOCK)
SELECT CAST(1 + t.RN % 3 AS VARCHAR(20))
FROM
(
    SELECT TOP (5000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t;
Run Code Online (Sandbox Code Playgroud)

我禁用了结果集并在 SSMS 中运行了以下查询:

SELECT A.column1
FROM A;
Run Code Online (Sandbox Code Playgroud)

大约需要 0.723 秒。我正在做非常不科学的测试,因为我对你的数据一无所知。无论如何,对于串行查询,我们不能期望比 0.7 秒更好的结果。那是我们的基线。

编写此查询的最有效方法是根本不使用连接。关键是,如果CASE表达式找到匹配项,它只会返回 3(或 30)个唯一值。您可以将结果保存到局部变量中,然后在查询中使用它们。下面的查询在大约 1.044 秒内完成:

DECLARE @B_VALUE VARCHAR(100) = (select value from B where B.clientId=100 and B.Id = '1');
DECLARE @C_VALUE VARCHAR(100) = (select value from C where C.clientId=100 and C.Id = '2');
DECLARE @D_VALUE VARCHAR(100) = (select value from D where D.clientId=100 and D.Id = '3');

SELECT 
(case when A.column1='1' then @B_VALUE
      when A.column1='2' then @C_VALUE
      when A.column1='3' then @D_VALUE
      -- omitted other columns
else column1 end) 
FROM A;
Run Code Online (Sandbox Code Playgroud)

计划非常简单:

在此处输入图片说明

对于另一个选项,您可以使用连接编写查询(我们可以CASE使用 以更紧凑的形式重写表达式COALESCE()。这在大约 2.314 秒内完成:

SELECT 
    COALESCE(B.column1, C.column1, D.column1, -- omitted other columns  
             A.column1)
    -- (case A.column1
    --    when '1' then B.value
    --    when '2' then C.value
    --    when '3' then D.value
    --    -- omitted other columns
    -- else A.column1 end) 
FROM A
LEFT JOIN B ON B.clientId=100 and B.Id = '1'
LEFT JOIN C ON C.clientId=100 and C.Id = '2'
LEFT JOIN D ON D.clientId=100 and D.Id = '3';
Run Code Online (Sandbox Code Playgroud)

这是计划:

在此处输入图片说明

通过编写这样的查询,您可以获得几乎相同的运行时和查询计划:

SELECT 
(case A.column1
   when '1' then (select value from B where B.clientId=100 and '1'=B.Id) 
   when '2' then (select value from C where C.clientId=100 and '2'=C.Id) 
   when '3' then (select value from D where D.clientId=100 and '3'=D.Id) 
-- omitted other columns
else column1 end) 
FROM A;
Run Code Online (Sandbox Code Playgroud)

问题中的原始查询有一个问题:SQL Server 在嵌套循环连接之前进行了无用的排序。该查询在我的机器上大约 5.838 秒内完成。

在此处输入图片说明

跟踪标志 8690 消除了排序和表线轴。查询在大约 7.479 秒内运行,跟踪标志为 8690,所以我认为假脱机对这个查询有帮助。