使用公共相关子查询有效地拉出不同的列

and*_*ieb 3 sql sql-server sql-server-2008

我需要从子查询中提取多个列,这也需要WHERE过滤器引用FROM表的列.我有几个问题:

  1. 除了我的下面还有另一个解决这个问题的方法吗?
  2. 是否有其他解决方案甚至是必要的,或者这种解决方

例:

在下面的示例中,我正在编写一个视图来显示测试分数,尤其是发现可能需要解决或重新获得的故障.

我不能简单地使用JOIN,因为我需要首先过滤我的实际子查询(注意我的"考生"得到TOP 1,按分数或日期降序排序)

我的目标是避免重复编写(和执行)基本相同的子查询.

SELECT ExamineeID, LastName, FirstName, Email,
   (SELECT COUNT(examineeTestID)
    FROM exam.ExamineeTest tests
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2) Attempts,
   (SELECT TOP 1 ExamineeTestID
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY Score DESC) bestExamineeTestID,
   (SELECT TOP 1 Score
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY Score DESC) bestScore,
   (SELECT TOP 1 DateDue
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY Score DESC) bestDateDue,
   (SELECT TOP 1 TimeCommitted
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY Score DESC) bestTimeCommitted,
   (SELECT TOP 1 ExamineeTestID
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY DateDue DESC) currentExamineeTestID,
   (SELECT TOP 1 Score
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY DateDue DESC) currentScore,
   (SELECT TOP 1 DateDue
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY DateDue DESC) currentDateDue,
   (SELECT TOP 1 TimeCommitted
    FROM exam.ExamineeTest T
    WHERE E.ExamineeID = ExamineeID AND TestRevisionID = 3 AND TestID = 2
    ORDER BY DateDue DESC) currentTimeCommitted
FROM exam.Examinee E
Run Code Online (Sandbox Code Playgroud)

Eri*_*ikE 9

首先回答你的第二个问题,是的,更好的方法是有序的,因为你正在使用的查询很难理解,难以维护,即使现在可以接受性能,查询同一个表也是一种耻辱如果您的应用程序增长到可观的大小,那么当您不需要加上性能时,可能并不总是可以接受.

要回答你的第一个问题,我有几种方法可供你使用.除非另有说明,否则它们假设SQL 2005或更高版本

请注意,您不需要BestExamineeID和CurrentExamineeID,因为它们将始终与ExamineeID相同,除非未进行任何测试并且它们为NULL,您可以从其他列中判断为NULL.

您可以将OUTER/CROSS APPLY视为一个运算符,它允许您将相关子查询从WHERE子句移动到JOIN子句中.它们可以具有对先前命名的表的外部引用,并且可以返回多个列.这使您每个逻辑查询只能执行一次作业,而不是每列执行一次.

SELECT
   ExamineeID,
   LastName,
   FirstName,
   Email,
   B.Attempts,
   BestScore = B.Score,
   BestDateDue = B.DateDue,
   BestTimeCommitted = B.TimeCommitted,
   CurrentScore = C.Score,
   CurrentDateDue = C.DateDue,
   CurrentTimeCommitted = C.TimeCommitted
FROM
   exam.Examinee E
   OUTER APPLY ( -- change to CROSS APPLY if you only want examinees who've tested
      SELECT TOP 1
         Score, DateDue, TimeCommitted,
         Attempts = Count(*) OVER ()
      FROM exam.ExamineeTest T
      WHERE
         E.ExamineeID = T.ExamineeID
         AND T.TestRevisionID = 3
         AND T.TestID = 2
      ORDER BY Score DESC
   ) B
   OUTER APPLY ( -- change to CROSS APPLY if you only want examinees who've tested
      SELECT TOP 1
         Score, DateDue, TimeCommitted
      FROM exam.ExamineeTest T
      WHERE
         E.ExamineeID = T.ExamineeID
         AND T.TestRevisionID = 3
         AND T.TestID = 2
      ORDER BY DateDue DESC
   ) C
Run Code Online (Sandbox Code Playgroud)

您应该尝试一下,看看我Count(*) OVER ()是否比获得额外OUTER APPLY的计数要好.如果您没有从exam.Examinee表中限制Examinee ,那么在派生表中执行常规聚合可能会更好.

这是另一种方法(某种方式)并且一举获取所有数据.可以想象它可以比其他查询表现得更好,除了我的经验是窗口函数在某些情况下会变得非常昂贵,所以测试是有序的.

WITH Data AS (
   SELECT
      *,
      Count(*) OVER (PARTITION BY ExamineeID) Cnt,
      Row_Number() OVER (PARTITION BY ExamineeID ORDER BY Score DESC) ScoreOrder,
      Row_Number() OVER (PARTITION BY ExamineeID ORDER BY DateDue DESC) DueOrder
   FROM
      exam.ExamineeTest
), Vals AS (
   SELECT
      ExamineeID,
      Max(Cnt) Attempts,
      Max(CASE WHEN ScoreOrder = 1 THEN Score ELSE NULL END) BestScore,
      Max(CASE WHEN ScoreOrder = 1 THEN DateDue ELSE NULL END) BestDateDue,
      Max(CASE WHEN ScoreOrder = 1 THEN TimeCommitted ELSE NULL END) BestTimeCommitted,
      Max(CASE WHEN DueOrder = 1 THEN Score ELSE NULL END) BestScore,
      Max(CASE WHEN DueOrder = 1 THEN DateDue ELSE NULL END) BestDateDue,
      Max(CASE WHEN DueOrder = 1 THEN TimeCommitted ELSE NULL END) BestTimeCommitted
   FROM Data
   GROUP BY
      ExamineeID
)
SELECT
   E.ExamineeID,
   E.LastName,
   E.FirstName,
   E.Email,
   V.Attempts,
   V.BestScore, V.BestDateDue, V.BestTimeCommitted,
   V.CurrentScore, V.CurrentDateDue, V.CurrentTimeCommitted
FROM
   exam.Examinee E
   LEFT JOIN Vals V ON E.ExamineeID = V.ExamineeID
   -- change join to INNER if you only want examinees who've tested
Run Code Online (Sandbox Code Playgroud)

最后,这是一个SQL 2000方法:

SELECT
   E.ExamineeID,
   E.LastName,
   E.FirstName,
   E.Email,
   Y.Attempts,
   Y.BestScore, Y.BestDateDue, Y.BestTimeCommitted,
   Y.CurrentScore, Y.CurrentDateDue, Y.CurrentTimeCommitted
FROM
   exam.Examinee E
   LEFT JOIN ( -- change to inner if you only want examinees who've tested
      SELECT
         X.ExamineeID,
         X.Cnt Attempts,
         Max(CASE Y.Which WHEN 1 THEN T.Score ELSE NULL END) BestScore,
         Max(CASE Y.Which WHEN 1 THEN T.DateDue ELSE NULL END) BestDateDue,
         Max(CASE Y.Which WHEN 1 THEN T.TimeCommitted ELSE NULL END) BestTimeCommitted,
         Max(CASE Y.Which WHEN 2 THEN T.Score ELSE NULL END) CurrentScore,
         Max(CASE Y.Which WHEN 2 THEN T.DateDue ELSE NULL END) CurrentDateDue,
         Max(CASE Y.Which WHEN 2 THEN T.TimeCommitted ELSE NULL END) CurrentTimeCommitted
      FROM
         (
            SELECT ExamineeID, Max(Score) MaxScore, Max(DueDate) MaxDueDate, Count(*) Cnt
            FROM exam.ExamineeTest
            WHERE
               TestRevisionID = 3
               AND TestID = 2
            GROUP BY ExamineeID
         ) X
         CROSS JOIN (SELECT 1 UNION ALL SELECT 2) Y (Which)
         INNER JOIN exam.ExamineeTest T
            ON X.ExamineeID = T.ExamineeID
            AND (
               (Y.Which = 1 AND X.MaxScore = T.MaxScore)
               OR (Y.Which = 2 AND X.MaxDueDate = T.MaxDueDate)
            )
      WHERE
         T.TestRevisionID = 3
         AND T.TestID = 2
      GROUP BY
         X.ExamineeID,
         X.Cnt
   ) Y ON E.ExamineeID = Y.ExamineeID
Run Code Online (Sandbox Code Playgroud)

如果(ExamineeID,Score)或(ExamineeID,DueDate)的组合可以返回多行,则此查询将返回意外的额外行.分数可能不太可能.如果两者都不是唯一的,那么您需要使用(或添加)一些可以授予唯一性的附加列,以便它可以用于选择一行.如果只有Score可以重复,那么获得最大分数的额外预查询,然后与最大DueDate相吻合将结合起来最近得分,这是最高分,同时获得最新数据.如果您需要更多SQL 2000帮助,请告诉我.

注意:控制CROSS APPLY或ROW_NUMBER()解决方案是否更好的最重要的事情是,您是否有正在查找的列的索引以及数据是密集还是稀疏.

  • 索引+你只拉了几个考试,每个考试都有很多考试= CROSS APPLY获胜.
  • 索引+你只需要几个测试就可以获得大量的考试= ROW_NUMBER()获胜.
  • 否index =字符串连接/值打包方法获胜(此处未显示).

我为SQL 2000提供的解决方案组可能会执行最差,但不能保证.就像我说的那样,测试是有序的.

如果我的任何疑问确实给出了性能问题,请告诉我,我会看到我可以做些什么来提供帮助.我确定我可能有拼写错误,因为我没有使用任何DDL重新创建你的表,但我没有尝试它就尽力而为.

如果性能确实变得至关重要,我将创建ExamineeTestBest和ExamineeTestCurrent表,这些表将被ExamineeTest表上的触发器推送到,该触发器将始终保持更新.然而,这是非规范化,可能不是必需的或者是一个好主意,除非你缩放得太大以至于检索结果变得无法接受.