Улучшение SQL-запросов - выберите с помощью max и groupby - PullRequest
2 голосов
/ 03 марта 2010

Задача


Учитывая следующие две таблицы, я хотел бы выбрать все идентификаторы для сообщений, чей последний (т.е. последний) комментарий был сделан за данный промежуток времени (например, февраль 2010 г.).

Результат запроса должен возвращать только идентификатор сообщения 1, так как самый последний комментарий для идентификатора сообщения 2 находится вне диапазона фильтра промежутка времени.

Вопрос


Я создал приведенный ниже оператор SELECT, который кажется правильным и обрабатывает все брошенные на него тесты.

Однако, чтобы продолжить совершенствовать свои навыки работы с SQL, я спрашиваю сообщество, есть ли «лучший» метод для этого сценария, какие-либо предложения по улучшению существующего оператора и / или пограничные случаи не покрыты.

Обратите внимание, что это свободный перевод реальных таблиц, измененный с целью облегчения понимания вопроса. Для чего это стоит, я использую SQL Server 2005.

Таблица


Сообщение

Id    Text     Visible
1     Post 1   1
2     Post 2   1
3     Post 3   0
.     ...
n     Post n   1

Комментарий

Id    Post_Id    Text                  CommentNumber    Timestamp
1     1          Comment 1, Post 1     1                2/3/2010
2     1          Comment 2, Post 1     2                2/4/2010
3     2          Comment 1, Post 2     1                3/1/2010
.     .          .
n     m          Comment n, Post m     x                xx/xx/xxxx


Команда SQL


SELECT [Id],[Text]
FROM [Post]  
WHERE [Id] IN (  
    SELECT comment1.[Post_Id]  
    FROM (  
        SELECT max([CommentNumber]) as maxComment,  
            [Post_id]  
        FROM [Comment]  
        GROUP BY [Post_id]  
    ) as comment2  
    INNER JOIN [Comment] as comment1 on comment1.[Post_id] = comment2.[Post_id]  
    WHERE comment1.[Timestamp] BETWEEN '2/1/2010 00:00:00.000' AND '2/28/2010 23:59:59.999'  
    AND comment1.[CommentNumber] = comment2.maxComment  
)
AND [Post].[Visible] = 1


Бонусный вопрос


Можно ли создать этот запрос с помощью NHiberate (используя Criteria API или HQL)?

Ответы [ 4 ]

4 голосов
/ 03 марта 2010
SELECT
    Post_Id
FROM
    Comment
GROUP BY
    Post_Id
HAVING
    MAX(Timestamp) >= '2/1/2010'

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

Хотя не знаю о NHibernate.

3 голосов
/ 03 марта 2010

Хорошие решения уже опубликованы, но я решил опубликовать объяснение того, как шаг за шагом можно упростить ваш запрос:

Внешний подзапрос избыточен

Самая внешняя часть подзапроса (бит SELECT [Id] FROM [Post] WHERE [Id] IN () является избыточной, поскольку вы уже возвращаете список идентификаторов).

Это оставляет нас с

SELECT comment1.[Post_Id]
FROM (  
    SELECT max([CommentNumber]) as maxComment,  
        [Post_id]  
    FROM [Comment]  
    GROUP BY [Post_id]  
) as comment2  
INNER JOIN [Comment] as comment1 on comment1.[Post_id] = comment2.[Post_id]  
WHERE comment1.[Timestamp] BETWEEN '2/1/2010 00:00:00.000' AND '2/28/2010 23:59:59.999'  
AND comment1.[CommentNumber] = comment2.maxComment  

Использование CommentNumber является избыточным

Нет необходимости использовать CommentNumber для получения самого последнего комментария, так как сообщения уже упорядочены по метке времени. Это означает, что вместо выбора метки времени комментария с самым высоким Id мы можем просто выбрать самую высокую метку времени.

Это устраняет необходимость присоединяться к комментариям снова, оставляя нам:

SELECT [Post_Id], SomeColumn, SomeOtherColumn
FROM (
    SELECT max([TimeStamp]) as maxTimeStamp,
        [Post_id],
        SomeColumn,
        SomeOtherColumn
    FROM [Comment]
    GROUP BY [Post_id]
) as GroupedComments
WHERE GroupedComments.maxTimeStamp BETWEEN '2/1/2010 00:00:00.000' AND '2/28/2010 23:59:59.999'

Подзапрос теперь избыточен

Теперь запрос несколько упрощен, и будет легко увидеть, как его можно сократить до одного из других решений, размещенных здесь с использованием синтаксиса distinct или having.

Используйте <и> = вместо МЕЖДУ

Просто маленький щелчок. Вместо того, чтобы переходить на большую дату, чтобы найти последнюю дату в феврале, разбиение BETWEEN на <и a> = делает запрос намного чище:

WHERE GroupedComments.maxTimeStamp >= '2/1/2010'
AND GroupedComments.maxTimeStamp < '3/01/2010'
0 голосов
/ 03 марта 2010

Это запрос, на который я нацеливаюсь после объединения ответов AakashM и Kragen:

SELECT [Id],[Text]
From [Post]
WHERE [Id] IN (
    SELECT Post_Id
    FROM Comment
    GROUP BY Post_Id
    HAVING MAX(Timestamp) >= '3/1/2010' AND MAX(Timestamp) < '4/1/2010'
)
AND [Post].[Visible] = 1

Вот как представить этот запрос в NHibernate с помощью Criteria API:

var subCriteria = DetachedCriteria.For<Comment>()
    .SetProjection(Projections.ProjectionList()
        .Add(Projections.GroupProperty("Post.Id")))
    .Add(Restrictions.Ge("Timestamp", new DateTime(2010, 3, 1)))
    .Add(Restrictions.Lt("Timestamp", new DateTime(2010, 4, 1)));

var criteria = session.CreateCriteria<Post>()
    .Add(Restrictions.Eq("Visible", true))
    .Add(Subqueries.PropertyIn("Id", subCriteria));
0 голосов
/ 03 марта 2010

Это должно быть немного быстрее, чем использование предложения HAVING.

select distinct Post_id from Comment
where Timestamp >= '2/1/2010';
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...