2

我正在构建一个数据仓库,我在两个表数据比较语句中发现了一个问题。我使用 EXCEPT 运算符来比较具有聚集索引的表(普通 int 字段作为键)。我的问题是,在查询执行计划中,两个聚集索引扫描后都有排序运算符。这是一个代码示例:

create table temp.table_a
(
    key_a int identity,
    some_field_a int,
    some_field2_a varchar(10)
);

insert into temp.table_a
(
    some_field_a,
    some_field2_a
)
select
    n,
    'abcd'
from meta.GENERATE_SEQUENCE(1,1000);

create clustered index cix_table_a_key_a on temp.table_a (key_a);


create table temp.table_b
(
    key_b int identity,
    some_field_b int,
    some_field2_b varchar(10)
);

insert into temp.table_b
(
    some_field_b,
    some_field2_b
)
select 
    n,
    'abcd'
from meta.GENERATE_SEQUENCE(1,1000);

create clustered index cix_table_b_key_b on temp.table_b (key_b);

(GENERATE_SEQUENCE 是一个行生成器)

现在除了查询:

select 
    key_a,
    some_field_a,
    some_field2_a
from temp.table_a

except

select 
    key_b,
    some_field_b,
    some_field2_b
from temp.table_b

这是执行计划的图像:

【执行计划】

我知道 Merge Join 需要排序的输入,但它还不够排序吗?我的意思是,我们需要的唯一排序列是 key_a/key_b。由于聚集索引,这已经完成。不需要对其他列进行排序,因为在 key_a/key_b 的每个值内只有一行 - 无需排序。

所以,我的问题是:

  1. 为什么在这种情况下聚集索引扫描后会有排序运算符?
  2. 当我想使用 EXCEPT 运算符时,如何避免这些类型?
  3. 进行表格比较的更好方法(如果有的话)是什么?

提前感谢您的回答:)

4

2 回答 2

2

回答你的问题是不是已经排序够了?- 否。对中的所有列执行比较,SELECT以便所有列都需要包含在排序中。

我提供了两种可能的解决方案,一种是将所有列添加到索引中,另一种是使用 NOT EXISTS - 请注意,如果 table_a 中有重复行,这可能会返回重复行。

1.) 将这些列包含在索引中,您将在 SQLFiddle 链接查询计划中看到在这种情况下不使用排序。

SQL小提琴

MS SQL Server 2017 架构设置

create table table_a
(
    key_a int identity,
    some_field_a int,
    some_field2_a varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_a
(
    some_field_a,
    some_field2_a
)
select
    n,
    'abcd'
from Tally;

create clustered index cix_table_a_key_a on table_a (key_a,some_field_a,
    some_field2_a);


create table table_b
(
    key_b int identity,
    some_field_b int,
    some_field2_b varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_b
(
    some_field_b,
    some_field2_b
)
select 
    n,
    'abcd'
from Tally;

create clustered index cix_table_b_key_b on table_b (key_b, some_field_b, some_field2_b);

查询 1

select 
    key_a,
    some_field_a,
    some_field2_a
from table_a

except

select 
    key_b,
    some_field_b,
    some_field2_b
from table_b

结果

2.) 另一种选择是使用NOT EXISTS代替- 应该注意的是,这些与有效执行 aEXCEPT并不完全相同。如果将 a 添加到 a,您将在较小的结果表上获得 SORT。EXCEPTDISTINCTDISTINCTNOT EXISTS

请参阅此处的 SQLFIDLLE:

SQL小提琴

MS SQL Server 2017 架构设置

create table table_a
(
    key_a int identity,
    some_field_a int,
    some_field2_a varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_a
(
    some_field_a,
    some_field2_a
)
select
    n,
    'abcd'
from Tally;

create clustered index cix_table_a_key_a on table_a (key_a);


create table table_b
(
    key_b int identity,
    some_field_b int,
    some_field2_b varchar(10)
);

WITH Tally (n) AS
(
    -- 1000 rows
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
    CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
insert into table_b
(
    some_field_b,
    some_field2_b
)
select 
    n,
    'abcd'
from Tally;

create clustered index cix_table_b_key_b on table_b (key_b);

查询 1

select 
    key_a,
    some_field_a,
    some_field2_a
from table_a
WHERE NOT EXISTS
(
  select NULL 
  from table_b
  WHERE
    key_b = key_a AND
    some_field_b = some_field_a AND
    some_field2_b = some_field2_a
)

结果

于 2019-09-02T09:56:05.410 回答
0

谢谢大家的帮助。以下是从评论和答案中收集的我的问题的完整答案:

  1. 为什么在这种情况下聚集索引扫描后会有排序运算符?

排序运算符的存在是因为索引键列 (key_a, key_b) 对于优化器来说不是唯一的。

  1. 当我想使用 EXCEPT 运算符时,如何避免这些类型?

确保您的索引键列是唯一的 - 使用 UNIQUE CLUSTERED INDEX 或对这些字段设置约束。

  1. 进行表格比较的更好方法(如果有的话)是什么?

Steve Ford (@steve-ford) 的回答中给出了考虑向索引键添加更多列或使用 NOT EXISTS 而不是 EXCEPT 的替代解决方案。

于 2019-09-02T13:47:05.920 回答