关于sql server:当存在多对多关系时,如何在内部实现TSQL Join?

How is a TSQL Join implemented internally when there is a many-to-single relationship?

如果这篇文章在其他地方发表,我很抱歉;有那么多关于加入副本的问题,我找不到答案。注意,这个问题并不是问如何删除结果中的重复行。

采用以下方案,其中两个表使用文本匹配进行联接,但其中一个表包含大量重复项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE #test (ID int PRIMARY KEY IDENTITY(1,1), textval nvarchar(250));
INSERT INTO #test (textval) VALUES (N'Luke'),(N'Han'),(N'Vader');

DECLARE @tmp TABLE (textval nvarchar(250));
INSERT INTO @tmp VALUES (N'Luke'),(N'Luke'),(N'Luke'),(N'Luke'),(N'Luke'),(N'Jabba');

-- Query 1
SELECT
    tmp.textval,
    t.ID
FROM
    @tmp tmp LEFT JOIN
    #test t ON tmp.textval = t.textval;

DROP TABLE #test;

我在这里得到想要的输出…

Correct result

…但是,这是执行此查询的有效方法吗?具体地说,我想知道TSQL是否会在表变量中的"luke"的每个实例上创建一个join,或者在内部删除重复项,因此只查找一次"luke",而不是五次查找五个实例?

我试着看一下统计数据和执行计划,但不知道什么数字表示正在发生的事情。

更新

根据Remus的回答,这里是上述查询的执行计划,显示零重绕/重绕。

以下是回放/重放图的屏幕抓图:Can I get a rewiiiiiind?


您正在询问连接是如何实现的。主要有三种策略:

  • 嵌套循环
  • 搞砸
  • 合并

它们都不会"消除"重复项,因为这样做在语义上是错误的。但是,hash和merge join都只能"访问"#test表中的值一次,而嵌套循环可以多次访问该值(对于@tmp表中的每个匹配行一次)。我说may是因为它取决于SQL优化器选择哪一侧(左边的#test或右边的@tmp作为嵌套循环的驱动程序。


SQL没有自动删除重复项的方法

如果要删除所有列中的重复项,那么只需使用DISTINCT关键字即可。

1
2
3
4
5
6
SELECT DISTINCT
    tmp.textval,
    t.ID
FROM
    @tmp tmp LEFT JOIN
    #test t ON tmp.textval = t.textval;

或者,如果需要根据某些特定列删除重复项,则可以尝试使用ROW_NUMBERDENSE_RANK函数。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;WITH CTE
AS
(
SELECT
    RN = ROW_NUMBER() OVER(PARTITION BY tmp.textval ORDER BY t.ID),
    tmp.textval,
    t.ID
FROM
    @tmp tmp LEFT JOIN
    #test t ON tmp.textval = t.textval;
)
SELECT
*
FROM CTE
WHERE RN = 1

但是,如果使用UNION运算符,SQL Server将自动删除重复项,并显示UNION ALL的重复项。