За последние 24 часа нас посетили 18518 программистов и 1606 роботов. Сейчас ищет 931 программист ...

Иерархия сортировки по дате в ORDER BY

Тема в разделе "MySQL", создана пользователем renta, 21 апр 2014.

  1. renta

    renta Новичок

    С нами с:
    21 апр 2014
    Сообщения:
    4
    Симпатии:
    0
    Здравствуйте.

    Есть таблица в базе с колонками dt (дата создания записи) типа datetime и updated_dt (on update CURRENT_TIMESTAMP).

    При выборке записей их необходимо отсортировать так:

    1) сначала идут записи за сегодняшний день с датой добавления (dt) по убыванию;
    2) записи добавленные вчера и позднее соритруются по дате добавления и обновления по убыванию (т.е. мы например, можем обновить запись, и тогда она поднимается наверх, но всё равно они идут после объявлений, добавленных сегодня - см. условие 1) ).

    Мой запрос выглядит так (комментарии оставил для ясности - оставляю запрос полностью, так как подозреваю, что просто не вижу в нём очевидного решения проблемы):

    Код (Text):
    1.  
    2. SELECT * FROM ads_table
    3. /* определённый тип (рубрика), актуальность объявления */
    4. WHERE type=1 AND  ( DATE_ADD( publish_dt, INTERVAL public_interval MONTH ) > NOW() )  
    5. /* is_spec - флаг для специальных объявлений, а вот дальше условие, которым я пытаюсь решить задачу,
    6. т.е. добавлено сегодня, то используем дату добавления, если раньше, то берём большее из добавления-обновления */
    7. ORDER BY is_spec DESC, IF( dt >= '2014-04-21 00:00:01',dt ,GREATEST(dt, updated_dt)) DESC, id DESC
    8. LIMIT 25
    Но всё равно объявления, обновлённые сегодня (но добавленные в прошлом), проскакивают наверх, рядом со свежими. Как это можно исправить?

    Если я заменяю в ORDER BY

    Код (Text):
    1. IF( dt >= '2014-04-21 00:00:01',dt ,GREATEST(dt, updated_dt)) DESC
    на
    Код (Text):
    1. dt DESC
    то получаю, то что мне нужно, но тогда дата обновления перестаёт влиять на выборку.
     
  2. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    а если склеить два запроса, "сегодня" и "всё остальное", через UNION ALL ?

    помоему условный ORDER BY это совсем хардкор. трудно для восприятия, особенно в понедельник.
     
  3. renta

    renta Новичок

    С нами с:
    21 апр 2014
    Сообщения:
    4
    Симпатии:
    0
    Получился вот такой запрос
    Код (Text):
    1.  
    2. (SELECT * FROM metr_ads
    3. WHERE TYPE =1 AND (DATE_ADD( publish_dt, INTERVAL public_interval MONTH ) > NOW( ))
    4. AND dt >=  '2014-04-21 00:00:01')
    5.  
    6. UNION
    7.  
    8. (SELECT * FROM metr_ads
    9. WHERE TYPE =1 AND (DATE_ADD( publish_dt, INTERVAL public_interval MONTH ) > NOW( ))
    10. AND dt <  '2014-04-21 00:00:01')
    11.  
    12. ORDER BY is_spec DESC , GREATEST( dt, updated_dt ) DESC , id DESC
    13. LIMIT 25
    Но это всё равно не то, так как не получается вынести LIMIT 25 в конец. Приходится выносить всё с ORDER BY, что по-моему нивелирует результат использования UNION.
     
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    1. UNION ALL, чтобы MySQL не пытался выкинуть дубликаты из полученного объединения
    2. почему не получится LIMIT ? все отлично получится.

    почему у вас в первом SELECT отсутствует ORDER BY ? и почему во втором SELECT по прежнему выражение в ORDER BY?

    вы нифига не поняли :(

    вся идея с разбиением на два запроса как раз и состоит в упрощении сотировки. вы сможете использовать быстрые выборки. а главное — запрос станет понятным и доступным для модификаций.

    Добавлено спустя 9 минут 11 секунд:
    упрощённый "до немогу" пример: есть таблица с числами. надо вывести четыре наибольших числа в обратном порядке, затем числа с 2 до 5 (но не более трех строк) в прямом порядке.
    http://sqlfiddle.com/#!2/9d1323/3

    Код (PHP):
    1. (
    2.   SELECT `n`
    3.   FROM `numbers`
    4.   ORDER BY `n` DESC
    5.   LIMIT 4
    6. )
    7.   UNION ALL
    8. (
    9.   SELECT `n`
    10.   FROM `numbers`
    11.   WHERE `n` BETWEEN 2 AND 5
    12.   ORDER BY `n` ASC
    13.   LIMIT 3
    14. )
    всё работает просто отлично. присутствует индивидуальная сортировка и LIMIT. если кликните по ссылке "View Execution Plan", то увидите, что запрос использует индекс.
     
  5. renta

    renta Новичок

    С нами с:
    21 апр 2014
    Сообщения:
    4
    Симпатии:
    0
    Я не могу написать запрос, похожий на то, что вы написали потому, что в нём используется LIMIT и OFFSET для постраничной навигации. Т.е. я не могу указать точное количество записей для каждого из подзапросов. В этой связи я и пытался вынести LIMIT в его конец.

    И в любом случае, второй подзапрос мне нужно сортировать по условию GREATEST( dt, updated_dt ) DESC, чтобы обновлённые объявления поднимались вверх.
     
  6. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    вы ведь зашли в тупик, верно? значит надо пробовать то, к чему не были готовы. не надо приспосабливать новое решение под старые рамки, потому что решение при этом исчезает.

    нужен limit на все результаты в-целом для пагинации? оберните это двойное выражение в еще один SELECT и в нем сделайте LIMIT с оффсетом.
     
  7. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    это прекрасно! значит и не надо limit во внутренних запросах ) ограничивайте только в WHERE по условию даты-времени. снаружи оборачиваете в SELECT с LIMIT

    справитесь?

    Добавлено спустя 2 минуты 25 секунд:
    Код (Text):
    1. SELECT o.*
    2. FROM ( (select…) UNION ALL (select…)) AS o
    3. LIMIT n, m
    Добавлено спустя 3 минуты 55 секунд:
    http://sqlfiddle.com/#!2/9d1323/4
    поиграйте с LIMIT самостоятельно
     
  8. renta

    renta Новичок

    С нами с:
    21 апр 2014
    Сообщения:
    4
    Симпатии:
    0
    В итоге у меня получается примерно такой запрос:

    Код (Text):
    1. SELECT o . * FROM (
    2.     (SELECT *, DATE_FORMAT(DATE_ADD( publish_dt, INTERVAL public_interval MONTH ), '%d.%m.%Y') as public_end_dt,
    3.     (DATE_ADD( publish_dt, INTERVAL public_interval MONTH ) <= NOW()) as is_expired
    4.     FROM my_table WHERE type=1 AND dt >=  '2014-04-22 00:00:01'
    5.     ORDER BY is_spec DESC , dt DESC , id DESC)
    6.  
    7.     UNION ALL
    8.  
    9.     (SELECT *, DATE_FORMAT(DATE_ADD( publish_dt, INTERVAL public_interval MONTH ), '%d.%m.%Y') as public_end_dt,
    10.     (DATE_ADD( publish_dt, INTERVAL public_interval MONTH ) <= NOW()) as is_expired
    11.     FROM my_table WHERE type=1 AND ( DATE_ADD( publish_dt, INTERVAL public_interval MONTH ) > NOW() ) AND dt <  '2014-04-22 00:00:01'
    12.     ORDER BY is_spec DESC , GREATEST( dt, updated_dt ) DESC , id DESC)
    13.  
    14. ) AS o LIMIT 25
    Если я выполняю первый селект из вложенных, то получаю, то что мне надо. Выполняю второй - тоже прекрасный результат. Но вот когда выполняю весь запрос в целом, то первая часть (где все сегодняшние объявления) идёт ОК, а вот во второй полная каша. Т.е. в ней записи не отсортированы по этому условию ORDER BY is_spec DESC , GREATEST( dt, updated_dt ) DESC , id DESC

    В Википедии по поводу UNION написано:
    Вот тут-то и проблема: мне нужно, чтобы сегодняшние записи всегда были сверху, а если я буду использовать общий ORDER BY, тогда обновлённые записи из второго запроса будут вылезать наверх.
     
  9. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    жесть...

    есть солюшн: забей на свою могучую сортировку, и сделай некое универсальное правило. Например: 10 новостей за сегодня (одним запросом) и потом все остальные новости, за исключением тех 10ти (другим запросом).
     
  10. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    renta, упрощайте, упрощайте и еще раз упрощайте. каша конечно. вы нагромоздили черте чего!
    сама идея, что надо сортировать в разные стороны уже сомнительна. трудно оценить результат когда сортировка туда-сюда.

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

    условие WHERE по начальной идее должно рассекать два запроса так, чтобы они в совокупности покрывали всё множество. то есть
    (x >= D) + (x < D)
    а что вы делаете? опять несопоставимые выражения.

    зачем у вас дата изменяется во фразе SELECT, чтобы скрыть от себя epic fail? для форматирования результатов больше приспособлен PHP куда вы всё равно затянете результат. упрощайте!

    Добавлено спустя 18 минут 37 секунд:
    согласен с igordata. вы "приплыли" — делайте всё совсем отдельными запросами, тогда возможно поймете где что происходит.