S'p*_*'Kr 8 sql-server optimization t-sql subquery
我有一个从应用程序中使用的大视图。我想我已经缩小了我的性能问题,但我不确定如何解决它。视图的简化版本如下所示:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Run Code Online (Sandbox Code Playgroud)
这可能不能证明查询结构的全部原因是合理的,但可能会给你一个想法——这个视图连接了两个我无法控制的设计非常糟糕的表,并试图从中合成一些信息。
因此,由于这是应用程序中使用的视图,因此在尝试优化时,我将其包装在另一个 SELECT 中,如下所示:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
Run Code Online (Sandbox Code Playgroud)
因为应用程序正在搜索结果中的特定员工。
问题似乎是该COALESCE(pe.StaffName, se.StaffName) AS StaffName
部分,而我是从StaffName
. 如果我将其更改为pe.StaffName AS StaffName
或se.StaffName AS StaffName
,性能问题就会消失(但请参阅下面更新的 2)。但这不会发生,因为其中的一侧或另一侧FULL OUTER JOIN
可能会丢失,因此一个或另一个字段可能为 NULL。
我可以重构这个替换为COALESCE(…)
其他东西,这将被重写到子查询中吗?
其他注意事项:
COALESCE
它,它非常快。WHERE
包含包装子查询和语句,查看执行计划也不会引发任何标志。我在分析器中的总子查询成本是0.0065736
. 哼。执行需要四秒钟。pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
和执行WHERE PEStaffName = 'X' OR SEStaffName = 'X'
)到目前为止,我添加的索引如下所示:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Run Code Online (Sandbox Code Playgroud)
嗯......我尝试模拟上面的严重变化,但没有帮助。即,在) Z
上面,我添加了AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
,但性能是相同的。现在我真的不知道从哪里开始。
@ypercube 关于需要完全连接的评论让我意识到我的综合查询遗漏了一个可能重要的组件。虽然,是的,我需要完全连接,但我上面通过删除COALESCE
和测试连接的一侧是否为非空值所做的测试会使完全连接的另一侧变得无关紧要,并且优化器可能正在使用它事实上,以加快查询。此外,我已经更新了示例以表明它StaffName
实际上是连接键之一——这可能对这个问题有重大影响。我现在也倾向于他的建议,即将其分解为三向联合而不是完全连接可能是答案,并且COALESCE
无论如何都会简化我正在做的大量s。现在正在尝试。
这是相当遥远的,但由于OP说它有效,我将其添加为答案(如果您发现任何错误,请随时纠正它)。
尝试将内部查询分为三个部分(INNER JOIN
、LEFT JOIN
with WHERE IS NULL
check、RIGHT JOIN
with IS NULL
check),然后再将UNION ALL
这三个部分分成三个部分。这样做有以下优点:
FULL
与(更常见的)INNER
和连接相比,优化器可用于连接的转换选项较少LEFT
。
Z
可以从视图定义中删除派生表(无论如何您都可以这样做)。
NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
仅在连接部分需要INNER
。
微小的改进,使用COALESCE()
将是最小的(如果有的话)(我假设se.SEId
和pe.PEId
不可为空。如果更多列不可为空,您将能够删除更多COALESCE()
调用。)
更重要的是,优化器可能会下推任何条件您涉及这些列的查询(现在COALESCE()
不会阻止推送。)
上述所有内容将为优化器提供更多选项来转换/重写任何使用视图的查询,以便它可以找到可以使用基础表上的索引的执行计划。
总之,视图可以写成:
SELECT
se.SEId + '-' + pe.PEId AS Id,
se.SEId, pe.PEId,
pe.StaffName,
pe.EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
pe.Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing,
DATEADD(minute, pe.Duration, pe.EventTime) AS EventEndTime
FROM PE pe INNER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT (pe.ThisThing = 1 AND se.OtherThing = 0)
UNION ALL
SELECT
'0-0',
NULL, pe.PEId,
pe.StaffName,
pe.EventTime,
pe.EventType,
pe.Duration,
pe.Data,
pe.Field,
pe.ThisThing, NULL,
DATEADD(minute, pe.Duration, pe.EventTime) AS EventEndTime
FROM PE pe LEFT JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT (pe.ThisThing = 1)
AND se.StaffName IS NULL
UNION ALL
SELECT
'0-0',
se.SEId, NULL,
se.StaffName,
se.EventTime,
se.EventType,
se.Duration,
se.Data,
se.Field,
NULL, se.OtherThing,
DATEADD(minute, se.Duration, se.EventTime) AS EventEndTime
FROM PE pe RIGHT JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT (se.OtherThing = 0)
AND pe.StaffName IS NULL ;
Run Code Online (Sandbox Code Playgroud)