Как запросить 10 самых последних элементов или элементов за последний месяц, в зависимости от того, что больше? - PullRequest
2 голосов
/ 06 декабря 2008

В своем блоге я хочу отобразить все сообщения за последний месяц. Но если это меньше 10 постов, я хочу показать десять самых последних постов (другими словами, на первой странице не должно быть меньше 10 постов). Мне интересно, есть ли способ сделать это в одном запросе?

В настоящее время я впервые запускаю этот запрос:

select count(*) from posts where timestamp > ($thirty_days_ago)
order by timestamp desc

Если это число больше или равно 10:

select * from posts where timestamp > ($thirty_days_ago)
order by timestamp desc

В противном случае:

select * from posts order by timestamp desc limit 10

Но для этого нужно, чтобы я выполнил два запроса. Есть ли более эффективный способ сделать это с помощью одного запроса? (Я использую MySQL.)

Ответы [ 7 ]

5 голосов
/ 06 декабря 2008
(SELECT * FROM posts
WHERE `timestamp` >= NOW() - INTERVAL 30 DAY)
UNION
(SELECT * FROM posts
ORDER BY `timestamp` DESC
LIMIT 10);

edit: Комментарий Re @ doofledorfer: Я запустил это в своей тестовой базе данных, и она работала нормально. Я попытался сравнить timestamp с литералом даты, а также с константным выражением, как показано в приведенном выше запросе, но это не имело никакого значения для плана оптимизации. Конечно, я использовал тривиальный объем данных, и план оптимизации может отличаться, если в нем тысячи строк.

В любом случае, ОП спрашивал, как получить правильный результат в одном запросе, , а не , как сделать план выполнения оптимальным. В конце концов, это запрос UNION, и он должен выполнять сортировку файлов.

+------+--------------+------------+------+---------------+------+---------+------+------+----------------+
| id   | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+------+--------------+------------+------+---------------+------+---------+------+------+----------------+
|  1   | PRIMARY      | posts      | ALL  | timestamp     | NULL | NULL    | NULL |   20 | Using where    | 
|  2   | UNION        | posts      | ALL  | NULL          | NULL | NULL    | NULL |   20 | Using filesort | 
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL |                | 
+------+--------------+------------+------+---------------+------+---------+------+------+----------------+
2 голосов
/ 06 декабря 2008

Просто сделайте это:

select * from posts order by timestamp desc limit 100

И отфильтруйте результаты в памяти. (предполагается, что 100 - это практический верхний предел для «сообщений в месяц», которые люди хотели бы видеть на одной странице)

Это «более эффективный одиночный запрос».

1 голос
/ 06 декабря 2008

Вы ищете одно сканирование таблицы (например, один SELECT)? Или один раз в оба конца на сервер базы данных? Ответ Билла предусматривает одно и то же обратное путешествие, но два SELECT ... так что, составляет ли это один или два «запроса», зависит от того, что вы на самом деле ищете, когда говорите «запрос».

Если ваша задержка с базой данных очень высока, лучше использовать решение Билла, потому что вы не очень ждете общения. Если сама база данных загружена и сканирование таблиц стоит дорого, ваша оригинальная реализация может быть лучше по двум причинам:

  • Вы можете кэшировать результат COUNT, поэтому он будет выполняться только один раз каждые 10 минут или около того. Теперь вы фактически амортизировали стоимость этого запроса (если за 10 минут на страницу зашло 200 посетителей, вы произвели только 201 SELECT заявлений).
  • Механизм базы данных может оптимизировать запрос COUNT, чтобы он попадал в индекс вместо полной таблицы, что делает его намного быстрее, чем попытка UNION нескольких наборов данных вместе. Я не уверен, достаточно ли сложен MySQL для этого или нет.
1 голос
/ 06 декабря 2008

Нет, более эффективного способа нет. Я бы сделал это так, как вы описываете это в своем вопросе. Ответ Билла Карвина примерно эквивалентен, если предикат будет пересмотрен, как я прокомментировал выше.

Все остальные предложения, которые я видел до сих пор, гораздо менее эффективны, даже если они каким-то образом дают правильный результат.

1 голос
/ 06 декабря 2008

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

Если ваша таблица правильно проиндексирована, то выполнение счетчика выбора (*) с последующим поисковым запросом не должно влиять на производительность. Существуют ли какие-то особые обстоятельства, которые заставили бы вас попытаться избежать второго запроса? В противном случае, я думаю, что ваше решение выше достаточно.

0 голосов
/ 06 декабря 2008

Idea1 : сделать запрос, чтобы всегда получать сообщения за этот месяц. Затем выполните цикл, подсчитывая количество полученных сообщений. Если и только если это число меньше 10, выполните второй запрос.

Идея 2 : Почему бы вам не кешировать ваш первый запрос (например, Google App Engine имеет API-интерфейсы кэширования)? Количество сообщений за этот месяц вряд ли будет часто меняться, поэтому в большинстве случаев вы бы избавились от необходимости первого запроса.

0 голосов
/ 06 декабря 2008

Я думаю, вы можете попробовать что-то вроде:

select * from posts 
where (timestamp >= (NOW() - INTERVAL 30 DAY)) or 
(post_id in (select post_id from posts order by timestamp desc limit 10))
order by timestamp desc
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...