这是SQL Server 2008.我有这两个表和一个连接:
DECLARE @EmployeeCrossDay TABLE
(
EmployeeId UNIQUEIDENTIFIER,
WorkDate DATE, OtherStuff...
)
DECLARE @ET TABLE
(
EmployeeId UNIQUEIDENTIFIER,
WorkDate DATE, DifferentOtherStuff...
)
SELECT *
FROM @EmployeeCrossDay ecd
LEFT JOIN @ET et ON et.EmployeeId = ecd.EmployeeId
AND et.WorkDate = ecd.WorkDate
第一个表有5,680行(每个员工对应一个范围内的每个日期),第二个表有397(员工实际工作的每一天一个或多个)。 (因此,EmployeeId / WorkDate是第一个表中的唯一组合,但不在第二个表中。)我的查询结果是正确的(每个员工的列表,其中包含他工作的天数和一行,每天一行他没有工作),但需要大约3秒钟,我的个人资料显示了沿途的笛卡尔积(2,254,960行)。有没有办法重构此查询以阻止完整的交叉连接?
*已编辑* 按照建议添加主键后,Set Showplan_Text On给我这个:
|--Compute Scalar(DEFINE:([Expr1007]=isnull(@ET.[StartTime] as [et].[StartTime],[Expr1010]), [Expr1008]=isnull(@ET.[EndTime] as [et].[EndTime],[Expr1010])))
|--Nested Loops(Left Outer Join, OUTER REFERENCES:([et].[ServiceCallId]))
|--Compute Scalar(DEFINE:([Expr1006]=isnull(@ET.[TypeId] as [et].[TypeId],(8)), [Expr1009]=isnull(@ET.[Interrupt] as [et].[Interrupt],($0.0000))))
| |--Nested Loops(Left Outer Join, WHERE:(@ET.[EmployeeId] as [et].[EmployeeId]=@EmployeeCrossDay.[EmployeeId] as [ecd].[EmployeeId] AND @ET.[WorkDate] as [et].[WorkDate]=@EmployeeCrossDay.[WorkDate] as [ecd].[WorkDate]))
| |--Compute Scalar(DEFINE:([Expr1010]=CONVERT_IMPLICIT(datetime,@EmployeeCrossDay.[WorkDate] as [ecd].[WorkDate],0)))
| | |--Sort(ORDER BY:([ecd].[Number] ASC, [ecd].[WorkDate] ASC))
| | |--Clustered Index Scan(OBJECT:(@EmployeeCrossDay AS [ecd]))
| |--Clustered Index Scan(OBJECT:(@ET AS [et]))
|--Clustered Index Seek(OBJECT:([Snapper].[dbo].[ServiceCalls].[PK_Jobs] AS [sc]), SEEK:([sc].[ServiceCallId]=@ET.[ServiceCallId] as [et].[ServiceCallId]) ORDERED FORWARD)
我的意思是“沿途显示笛卡尔积”来自设置统计资料。它显示太多要粘贴在这里,但对于计划中的倒数第二个项目(聚集索引扫描),它在行下显示2,254,960(我的逗号),在执行时显示5680。我误解了说我有笛卡尔积?
答案 0 :(得分:1)
我得到了三个很好的答案,但他们都是评论,所以我发布了''回答这里。
Aaron建议我为每个表var添加一个主键。我添加了
EmployeeTimeId UNIQUEIDENTIFIER PRIMARY KEY
到@ET和
PRIMARY KEY (EmployeeId, WorkDate)
到@EmployeeCrossDay,因为在这两种情况下,EmployeeId都不是唯一的。
尽管EmpmloyeeTimeId没有参与连接,但仅此一项就将我的查询从3秒以上减少到1以下。但是,我的统计资料显示执行计划中的其中一个步骤运行了5680次,达到了220万以上行(5,680 * 397)。虽然响应时间是可以接受的,但我很好奇。
Martin然后建议我的@ET键需要让EmployeeId领先。所以我用
替换了密钥PRIMARY KEY( EmployeeId, EmployeeTimeId )
此时,执行计划显示前一个交叉连接减少到只能击中397行(而不是超过200万行),即使它仍在为每个@ET行(5680)执行该过程,它现在正在执行聚集索引寻找而不是全表扫描。
一路上,戈登建议添加
OPTION( HASH JOIN, MERGE JOIN )
我删除了以前应用的所有索引,并且生成的计划中的任何步骤都不会执行多次。
这三个建议都在一秒钟之内返回了相同的数据,所以我给每个人提供了分数(现在,我的谢谢)。