Оператор Select с агрегацией MAX () в предложении Where - PullRequest
2 голосов
/ 11 ноября 2011

У меня есть таблица базы данных, в которой хранятся обновления членства каждый год.При вставке записи обновления столбец expiryDate записывается с датой (31/8 / [nextyear]).

Так, например, скажем, член с memberID = 99 обновляется в 2007 году,В 2008 и 2009 годах у него будет 3 записи (по одной на каждый год), с записью «expiryDate» в каждой.Если я сделаю

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

... я верну 2010 год.

Что я хотел бы сделать, это вернуть ВСЕ записи, где MAX(YEAR(expiryDate)) - это заданный год ... дляНапример,

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

Этот запрос не будет работать, так как агрегация не может использоваться в предложении where вне подзапроса, но я не могу понять, как структурировать подзапрос ... илидаже если это можно сделать лучше, чем использовать подзапрос.

Ответы [ 4 ]

5 голосов
/ 11 ноября 2011

Предикат, основанный на агрегированном столбце, использует предложение HAVING, а не WHERE.

Если вам нужен только memberID, это достаточно просто:

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

Вы также можете сделать это как подзапрос, если вам нужно получить другие столбцы из этой таблицы, например:

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

ОБНОВЛЕНИЕ

Это правильно, как@OMG Пони указал, что этого недостаточно, если вам нужно выбрать дополнительные столбцы из этой строки в renewals.Если это требуется, вы можете использовать:

SELECT * FROM renewals
  WHERE memberID IN ( SELECT memberID FROM renewals
                      GROUP BY memberID HAVING MAX(YEAR(expiryDate)) = 2010 )
    AND YEAR(expiryDate) == 2010
1 голос
/ 05 марта 2012

Этот вопрос уже пару месяцев, и на него есть принятый ответ, а также еще два действительных ответа. Тем не менее, я добавляю еще один:

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. Это можно. Аарон Бертран (также активный в SO) написал блог на тему здесь - в котором поразительным образом согласен с то, что я продолжаю проповедовать для PostgreSQL .

Возможность использовать индекс намного важнее, чем другие детали стиля запроса, с точки зрения производительности.

Кроме того, этот запрос запрещает несколько строк для одного и того же члена. Он возвращает только строку последний за 2010 год для каждого члена - если в этом году должно быть несколько записей. Не должно происходить в соответствии с описанием, но могут быть легко исключения. Я предполагаю, что это то, что нужно. Ответ @OMG Ponies - пока единственный ответ, который рассматривает эту деталь. По иронии судьбы это был единственный, кто не голосовал до сих пор.

1 голос
/ 11 ноября 2011

Для 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.Вы получите все записи для этих участников, и вам все равно нужно отфильтровать то, что вы ищете.

1 голос
/ 11 ноября 2011

Использовать GROUP BY

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