【问题标题】:Select statement with MAX() aggregation in the Where Clause在 Where 子句中使用 MAX() 聚合的 Select 语句
【发布时间】:2011-11-11 03:02:10
【问题描述】:

我有一个数据库表,用于存储每年的会员续订情况。插入续订记录时,会在“expiryDate”列中写入日期(31/8/[明年])。

因此,例如,假设成员 ID = 99 的成员在 2007 年、2008 年和 2009 年续订,他将有 3 条记录(每年一条),每条记录都有一个“到期日期”。如果我做一个

SELECT MAX(YEAR(expiryDate)) as maxExpiry 
  FROM renewals 
 WHERE memberID = 99

...我会回到 2010 年。

我想做的是返回MAX(YEAR(expiryDate)) 是给定年份的所有记录。例如,

SELECT * 
  FROM renewals 
 WHERE MAX(YEAR(expiryDate)) = '2010';

这个查询不起作用,因为聚合不能在子查询之外的 where 子句中使用,但我不知道如何构造子查询......或者即使这可以做得更好比使用子查询的方式。

【问题讨论】:

    标签: sql sql-server aggregate-functions


    【解决方案1】:

    基于聚合列的谓词使用HAVING 子句,而不是WHERE

    如果您只需要 memberID,这很简单:

    SELECT memberID
      FROM renewals
      GROUP BY memberID
        HAVING MAX(YEAR(expiryDate)) = 2010
    

    如果您需要从该表中获取其他列,您也可以将其作为子查询执行,即:

    SELECT * FROM members
      WHERE memberID IN ( <<previous query>> )
    

    更新

    @OMG Ponies 指出这是正确的,如果您需要从renewals 的那一行中选择其他列,这还不够。如果需要,您可以使用:

    SELECT * FROM renewals
      WHERE memberID IN ( SELECT memberID FROM renewals
                          GROUP BY memberID HAVING MAX(YEAR(expiryDate)) = 2010 )
        AND YEAR(expiryDate) == 2010
    

    【讨论】:

    • @Jason,除了这个答案,添加一个日期等于或大于 2010 年 1 月 1 日的 WHERE 子句......这将消除所有旧的续订之前你无论如何都会扔掉.. . 不知道你的表有多大,因为性能问题。
    • 完美运行.. 感谢您提供的变化范围和选项!它实际上是一个更大的动态查询的一部分,所以已经使用 HAVING 进行子查询。谢谢大家!杰森
    【解决方案2】:

    使用分组方式

    SELECT memberID, MAX(YEAR(expiryDate))
      FROM renewals 
    GROUP BY memberID
    HAVING MAX(YEAR(expiryDate)) = 2010
    

    【讨论】:

      【解决方案3】:

      对于 SQL Server 2005+,使用:

      WITH cte AS (
        SELECT r.*,
               ROW_NUMBER() OVER (PARTITION BY r.memberid
                                      ORDER BY r.expirydate DESC) AS rnk
          FROM RENEWALS r)
      SELECT c.*
        FROM cte c
       WHERE c.rnk = 1
         AND YEAR(c.expirydate) = 2010
      

      CTE 并不是 2005+ 的真正原因——它是 ROW_NUMBER 的使用,因为它可以被重写为不使用 CTE。

      子查询的问题是获得memberid(就像您在其他答案中看到的那样)不足以加入RENEWALS 表的副本。您将获得这些成员的所有记录,但仍需要过滤掉您要查找的内容。

      【讨论】:

      • 确实如此。在我的回答中,我假设由于 memberID 在renewals 表中是重复的,所以它必须是其他地方的 FK,我假设它是 members。其他安排需要回renewals
      【解决方案4】:

      这个问题已经有几个月的历史了,并且有一个公认的答案以及另外两个有效的答案。不过,我正在添加另一个:

      SELECT *
      FROM   renewals r
      WHERE  expiryDate >= '20100101'  -- unambiguous input format with any locale!
      AND    expiryDate <  '20110101'
      AND    NOT EXISTS (
          SELECT *
          FROM   renewals r0
          WHERE  r0.memberID   = r.memberID
          AND    r0.expiryDate > r.expiryDate
          );
      

      为什么?对于大表,所有以前的答案都会很慢,因为它们无法使用 expiryDate 上的索引。 这个可以。 Aaron Bertrand(也在 SO 上很活跃)写了一篇关于主题 here 的博客 - 这与 PostgreSQL 的 what I keep preaching 有着惊人的细节。

      就性能而言,能够使用索引比这里查询样式的其他细节更重要。

      此外,此查询防止同一成员出现多行。它只返回每个成员 2010 年的 latest 行 - 如果该年应该有多个条目。根据描述不应该发生,但很容易出现异常。我认为这是需要的。 @OMG Ponies 的答案是迄今为止观察到这个细节的唯一答案。具有讽刺意味的是,直到现在,它也是唯一一个没有投票的人。

      【讨论】:

        猜你喜欢
        • 2011-09-19
        • 1970-01-01
        • 2018-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多