T-SQL,在视图中重复相同的标量子查询性能
Posted
技术标签:
【中文标题】T-SQL,在视图中重复相同的标量子查询性能【英文标题】:T-SQL, repeated same scalar subquery performance in views 【发布时间】:2021-12-30 05:08:44 【问题描述】:以下是检索学生及其考试结果的简单查询。同一个学生可以多次参加同一个考试。子查询检索每个学生的最新考试结果。如您所见,第 X 行(检索最新的考试 ID)在每一行的每个子查询中完全相同。如何存储或缓存第 X 行的结果以防止每行执行 3 次? 我不能为这个任务使用存储过程或函数,它必须是一个 VIEW 来进行额外的过滤。
SELECT S.*,
(
SELECT COUNT(*) FROM ExamAnswers WHERE
IsCorrectAnswer IS NOT NULL AND
IsCorrectAnswer = 1 AND
ExamID =
(SELECT TOP(1) ID FROM Exams E WHERE E.StudentID = S.ID ORDER BY ID DESC) --Line X
) CorrectAnswerCount,
(
SELECT COUNT(*) FROM ExamAnswers EA WHERE
EA.IsCorrectAnswer IS NOT NULL AND
EA.IsCorrectAnswer = 0 AND
EA.ExamID =
(SELECT TOP(1) ID FROM Exams E WHERE E.StudentID = S.ID ORDER BY ID DESC) --Line X
) WrongAnswerCount,
(
SELECT COUNT(*) FROM ExamAnswers WHERE
IsCorrectAnswer IS NULL AND
ExamID =
(SELECT TOP(1) ID FROM Exams E WHERE E.StudentID = S.ID ORDER BY ID DESC) --Line X
) UnansweredQuestionCount
FROM Students S
【问题讨论】:
你当然可以创建一个函数,但它仍然会执行 3 次,所以这只是为了清晰和 DRY 考虑,我怀疑这种努力是否值得你花时间,但我很想看看如果有人熟悉一种方法,那么您实际上可以在视图中执行此操作,我对此表示怀疑:)IsCorrectAnswer IS NOT NULL AND IsCorrectAnswer = 1
应该写成IsCorrectAnswer = 1
- 如果满足,则保证满足IS NOT NULL
旁注:子查询在查询中出现 3 次并不一定意味着它被执行 3 次。通常优化器应该发现它并且结果将被重用(如果它没有找到完全其他的方式)。请记住,SQL 是声明式的,而不是命令式的。如有疑问,请检查计划。
@stickybit - SQL Server 优化器几乎从不(如果有的话)进行通用子查询表达式重构以组合相似的子查询。即使它们仅在查询文本中出现一次(例如 COALESCE((sub_query), 1)
)也是如此,这将在执行计划中有两个 sub_query
树实例
【参考方案1】:
你可以这样做
SELECT S.*,
CA.*
FROM Students S
CROSS APPLY (SELECT SUM(CASE WHEN IsCorrectAnswer = 1 THEN 1 ELSE 0 END) AS CorrectAnswerCount,
SUM(CASE WHEN IsCorrectAnswer = 0 THEN 1 ELSE 0 END) AS WrongAnswerCount,
SUM(CASE WHEN IsCorrectAnswer IS NULL THEN 1 ELSE 0 END) AS UnansweredQuestionCount
FROM ExamAnswers EA
WHERE EA.ExamID = (SELECT TOP(1) ID
FROM Exams E
WHERE E.StudentID = S.ID
ORDER BY ID DESC)) CA
【讨论】:
我认为应该使用OUTER APPLY
。除非 OP 可以确认所有学生在 ExamAnswers 中都有行
@Squirrel - 不。这是一个标量聚合,因此即使表为空,也始终返回单行。见Fun with Scalar and Vector Aggregates
你是对的。没有注意到聚合【参考方案2】:
这种方法怎么样:
WITH
T AS
(
SELECT Student_id,
SUM(CASE IsCorrectAnswer WHEN 1 THEN 1 END) AS COUNT_TRUE,
SUM(CASE IsCorrectAnswer WHEN 0 THEN 1 END) AS COUNT_FALSE,
SUM(CASE WHEN IsCorrectAnswer IS NULL THEN 1 END) AS COUNT_UNKNOWN
FROM ExamAnswers AS EA
WHERE EA.ExamID = (SELECT MAX(ID)
FROM Exams E
WHERE E.StudentID = S.ID)
GROUP BY Student_id
)
SELECT S.*, COUNT_TRUE, COUNT_FALSE, COUNT_UNKNOWN
FROM Students AS S
JOIN T ON S.ID = T.Student_id
【讨论】:
这与原始查询的语义完全不同。对于每个学生,它需要为具有最高 id 的学生找到相应的考试,并从ExamAnswers
为该考试在与学生信息相同的行中返回值。您的查询没有做任何类似的事情
抱歉我更正了
它仍然无法正常工作 - 初始 CTE 有 WHERE E.StudentID = S.ID
但在此范围内没有 S
这样的别名
所有列都具有相同的名称 ID、ID、ID.... 很难回答您的需求。请使用表的 DDL 定义更正您的帖子...以上是关于T-SQL,在视图中重复相同的标量子查询性能的主要内容,如果未能解决你的问题,请参考以下文章