两个 SELECT 查询的 ISNULL 而不是一个标量值

ISNULL for two SELECT queries instead one scalar value

我想做这样的事情:

SELECT ISNULL_QUERY((SELECT TOP 1 A.Id, A.Value FROM A), (SELECT TOP 1 B.Id, B.Value FROM B))

它应该 return 包含 Id 和 Value 列的行,例如:

Id Value
1 TestValue

我不想 运行 两个 select 查询然后选择一个,因为它们太“重”了。

我不能使用:

    SELECT ISNULL((SELECT TOP 1 1 [Id], 'test' [Value]), (SELECT TOP 1 2 [Id], 'test2' [Value]))

因为会报错:

消息 116,级别 16,状态 1,第 1 行 当子查询不使用EXISTS引入时,select列表中只能指定一个表达式。

我想要两列而不是一列

使用基于集合的方法来执行此操作,使用 UNION 运算符,如下所示:

SELECT TOP 1 A.Id, A.Value FROM A
UNION ALL
SELECT TOP 1 B.Id, B.Value FROM B 
WHERE NOT EXIST(SELECT TOP 1 A.Id, A.Value FROM A)

补充测试....

0 - 初始设置

CREATE TABLE T_A (CA INT PRIMARY KEY);

CREATE TABLE T_B (CB INT PRIMARY KEY);

SET STATISTICS IO ON;
-- rewrited query :
WITH
T1 AS (SELECT TOP 1 CA FROM T_A ORDER BY CA),
T2 AS (SELECT TOP 1 CB FROM T_B ORDER BY CB)
SELECT * FROM T1
UNION ALL
SELECT * FROM T2 
WHERE NOT EXISTS(SELECT * FROM T1);

1 - 所有 table 都已填充

INSERT INTO T_A VALUES (1), (2), (3);
INSERT INTO T_B VALUES (9), (8), (7);

Table'T_A'。扫描计数 2,逻辑读取 4 Table 'T_B'。扫描计数 1,逻辑读取 2

tableT_A读两遍

2 - B 为空,A 为满

DELETE FROM T_B;

Table'T_A'。扫描计数 1,逻辑读取 2 Table 'T_B'。扫描计数 1,逻辑读取 2

所有table阅读一次

3 - A空,B满

INSERT INTO T_B VALUES (9), (8), (7);
DELETE FROM T_A;

Table'T_A'。扫描计数 2,逻辑读取 4 Table 'T_B'。扫描计数 1,逻辑读取 2

Table一个读两遍

现在两个 table 填充了 1,000,000 行

DELETE FROM T_A;
WITH 
TN AS (SELECT 0 AS I
       UNION ALL 
       SELECT I + 1 FROM TN
       WHERE  I < 9)
INSERT INTO T_A
SELECT T1.I + 10*T2.I + 100*T3.I + 1000*T4.I + 10000*T5.I + 100000*T6.I
FROM   TN AS T1
       CROSS JOIN TN AS T2
       CROSS JOIN TN AS T3
       CROSS JOIN TN AS T4
       CROSS JOIN TN AS T5
       CROSS JOIN TN AS T6;
DELETE FROM T_B;
INSERT INTO T_B
SELECT * FROM T_A;

4 - 两个 table 填充了 100 万行

Table'T_A'。扫描计数 2,逻辑读取 8 Table 'T_B'。扫描计数 1,逻辑读取 3

5 - table A 空,table B 100 万行

Table'T_A'。扫描计数 2,逻辑读取 6 Table 'T_B'。扫描计数 1,逻辑读取 3

6 - table B 空,table A 100 万行

Table'T_B'。扫描计数 1,逻辑读取 3 Table 'T_A'。扫描计数 1,逻辑读取 3

结论是:无论是否两次访问(扫描计数),因为只会读取很少的页面...

如果 SQL 服务器物化 CTE:

,这将有效
WITH a as (
      SELECT TOP (1) A.Id, A.Value
      FROM A
     )
SELECT id, value
FROM a
UNION ALL
SELECT TOP (1) b.id, b.value
FROM b
WHERE NOT EXISTS (SELECT 1 FROM a);

但事实并非如此。如果你真的需要控制执行(并且不能信任优化器),那么你可能需要使用临时表:

 SELECT TOP (1) A.Id, A.Value
 INTO #A;
 FROM A;

然后:

SELECT id, value
FROM #A
UNION ALL
SELECT TOP (1) b.id, b.value
FROM b
WHERE NOT EXISTS (SELECT 1 FROM #A);

甚至:

IF EXISTS (SELECT 1 FROM #A)
    SELECT id, value
    FROM #A
ELSE
    SELECT TOP (1) id, value
    FROM B;