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,在视图中重复相同的标量子查询性能的主要内容,如果未能解决你的问题,请参考以下文章

Oracle优化笔记

T-SQL基础03.子查询

MySQL的SQL语句 - 数据操作语句(13)- 子查询

Mysql优化系列之——优化器对子查询的处理

基础很重要~~04.表表达式

SQL基础教程(第2版)第5章 复杂查询:5-2 子查询