LINUX.ORG.RU

MySQL: сортировка по набору суммы баллов


0

1

Такая, вот, хитрая задача у меня периодически всплывает.

Пусть есть таблица с некоторыми объектами. И есть таблица выставления оценок за эти объекты. Ну, пусть так:

objects:
    id: int
    title: string
    object_date: timestamp

votes:
    object_id: int
    score: int
    vote_date: timestamp

Нужно выбрать объекты, имеющие оценку выше определённой — это не вопрос.

SELECT object_id FROM votes WHERE SUM(score) > 10;

А вот дальше — проблемная задача. Нужно отсортировать объекты не абы как, а по дате, когда сумма превысила порог вывода (10 в нашем примере).

Сейчас я реализую параллельной таблицей best_objects, типа:

best_objects:
    object_id: int
    pin_date: timestamp

ну и вывод из неё, соответственно.

Проблема в том, что приходится вести лишнюю таблицу, фактически избыточную. И, не дай бог, меняются критерии — нужно всё пересчитывать, «имитируя» обработку голосов по мере добавления.

Есть ли у кого мысли, как этот вопрос можно решить изящнее?

Примеры из практики, если абстрактно описание непонятно: например, нужно вывести поток лучших сообщений форума, но с тем, чтобы сообщения, которые «добрали» рейтинг, оказывались сверху, даже если они старые. Попал в поток — твоё место (дата попадения) в нём фиксировано. Если сортировать по дате самого сообения, то старые сообщения, набравшие пороговую оценку, окажутся незамеченными в глубине списка. Если сортировать по дате последней оценки, то одни и те же сообщения будут всплывать по мере новых голосов за них. И т.д.

Для упрощённого варианта, когда нет поля score и мы учитываем только сами голоса, приходит в голову мысль, что нужно как-то сгрупировать votes по object_id, отсортировать по дате голоса и отрезать limit'ом по пороговому числу. Максимальное значение даты в группе и будет датой попадения в избранные. Но даже такой упрощённый вариант слабо представляю как реализовать на практике. Можно, наверное, извратиться с парой вложенных SELECT'ов, но это ужасно. Может, есть какой-то более изящный вариант?

★★★★★

Ответ на: комментарий от visual

order by max(vote_date), не?

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

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от visual

order by max(vote_date), не?

Гипотетически тут нужно что-то типа

SELECT object_id, MAX(vote_date) FROM votes GROUP BY object_id ORDER BY vote_date LIMIT_IN_GROUP 10;

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

добрали» рейтинг

тогда уточни за какой период добрали

и совсем непонятно зачем тебе ещё одна таблица если можно было pin_date заховать в objects

visual ★★★
()
Ответ на: комментарий от visual

тогда уточни за какой период добрали

За всё время. По факту набора.

и совсем непонятно зачем тебе ещё одна таблица если можно было pin_date заховать в objects

Потому что это совсем другая сущность, не имеющая отношения к объекту. Таблица объектов вообще в R/O может быть для системы, выводящей поток избранного.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

тогда как-то неверно сформулировано

Максимальное значение даты в группе и будет датой попадения в избранные.

Каждый раз при голосовании оно будет «попадать», даже если оно уже там.

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

visual ★★★
()
Ответ на: комментарий от visual

Каждый раз при голосовании оно будет «попадать», даже если оно уже там.

В группе, обрезанной лимитом. Предварительно отсортированной по дате голосов. Тогда, скажем, набралось 10 голосов — всё, последующие голоса не будут попадать в эту группу. Вот в ней, MAX(vote_date) и будет указывать на дату вывода в «поток лучших». По ней и нужно сортировать.

Собственно, сейчас обновлялка потока (отдельной таблицы) у меня, ЕМНИП, так и работает. Я кручу в цикле все объекты из таблицы голосов и по каждому делаю
[code=sql]
SELECT MAX(vote_date) FROM votes WHERE object_id = 314159 ORDER BY vote_date LIMIT 10;
[/code]

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от visual

или

Нет, мне нужна конкретная дата, когда данный объект набрал данное число голосов :) Не важно, вчера он размещён или 5 лет назад. Набралось 10 голосов — всё, он попал в лучшие. И покажется он там на самом верху. Пока не наберёт 10 голосов следующий объект. Тоже не важно, какого возраста.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

всё, последующие голоса не будут попадать в эту группу

Я тебя не понимать. этого условия я в ОП не вижу. Переформулируй задачу.

visual ★★★
()
Ответ на: комментарий от KRoN73

SELECT MAX(vote_date) FROM votes WHERE object_id = 314159 ORDER BY vote_date LIMIT 10;

Собственно, таблицу-лог можно заполнить в два запроса (за синтаксис не ручаюсь):

INSERT INTO best_objects (object_id) (SELECT DISTINCT object_id FROM votes);
UPDATE best_objects SET pin_date = (SELECT MAX(vote_date) FROM votes WHERE votes.object_id =  best_objects.object_id ORDER BY vote_date LIMIT 10);

Но это всё равно параллельная таблица. Хотя в таком виде преемлимо. Обновление получается без извращений.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от visual

Я тебя не понимать. этого условия я в ОП не вижу

«Нужно отсортировать объекты не абы как, а по дате, когда сумма превысила порог вывода (10 в нашем примере)»

«например, нужно вывести поток лучших сообщений форума, но с тем, чтобы сообщения, которые «добрали» рейтинг, оказывались сверху, даже если они старые. Попал в поток — твоё место (дата попадения) в нём фиксировано»

Переформулируй задачу

Сорри, но точнее уже некуда :)

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

а вот теперь я тебя кажется понял

тебе нужно сортировать по той дате на которой скор превысил предел(10)?

visual ★★★
()
Ответ на: комментарий от visual

тебе нужно сортировать по той дате на которой скор превысил предел(10)?

Да. Я так и говорил :)

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

Если я правильно, понял, то запрос со вложенным получается:

select object_id from votes v1 where (select count(*) from votes v2 where v1.object_id=v2.object_id and v2.timestamp<v1.timestamp)>=10 order by timestamp;

Chumka ★★★
()

Мне кажется, у тебя и так все правильно сделано. Дополнительная таблица - это не проблема. Относись к ней не как к таблице с исходными данными, а как к временной таблице, которая кеширует рассчитанные данные.

Существующую схему можно соптимизировать, добавив в таблицу votes поле cached_total_score, которое будет суммой по всем score текущего и с более ранними vote_date. Тогда дату, когда cached_total_score привысил твой порог ты сможешь найти по этой же таблице. Но эти данные нужно будет временами пересчитывать.

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

x_hash
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.