2010-07-01 3 views
5

Скажите, что у меня есть игра, в которой задан вопрос, люди публикуют ответы, которые забиты, и лучшие 10 ответов выигрывают. У меня есть база данных SQL, в которой хранится вся эта информация, поэтому у меня могут быть такие таблицы, как «Пользователи», «Вопросы и ответы». В таблице ответов есть foreign_keys user_id и question_id и атрибут total_score.Выберите элементы, которые являются верхними результатами N для связанной таблицы

Очевидно, что для конкретного вопроса я могу получить первые 10 ответов с заказом и пределом:

SELECT * FROM Responses WHERE question_id=? ORDER BY total_score DESC LIMIT 10; 

Что я ищу является способом, которым я могу определить, для конкретного пользователя, список всех их ответов, которые являются победителями (в первой десятке по их конкретному Вопросу). Проще программно пропустить каждый ответ и посмотреть, включен ли он в первую десятку для своего Вопроса, но я хотел бы оптимизировать это, поэтому я не делаю N + 1 запросов, где N - количество ответов, отправленных Пользователем ,

+1

StackOverflow 2.0? Извините, это не помогло ... –

+0

lol! Самое смешное, что я не понял, что мой пример описывает StackOverflow, пока вы не указали это. Так работает мой мозг сегодня. – MikeJ

ответ

3

Если вы используете Oracle, Microsoft SQL Server, DB2 или PostgreSQL, эти базы данных поддерживают функции окон. Присоедините ответы пользователя к другим ответам на тот же вопрос. Затем разделите вопрос и порядок по убыванию. Используйте номер строки в каждом разделе, чтобы ограничить набор теми, что находятся в верхней части 10. Также пройдите по user_id данного пользователя, чтобы вы могли выбрать их из 10 лучших, так как вас интересуют только ответы данного пользователя.

SELECT * 
FROM (
    SELECT r1.user_id AS given_user, r2.*, 
    ROW_NUMBER() OVER (PARTITION BY r2.question_id ORDER BY r2.total_score DESC) AS rownum 
    FROM Responses r1 JOIN Responses r2 ON r1.question_id = r2.question_id 
    WHERE r1.user_id = ? 
) t 
WHERE rownum <= 10 AND user_id = given_user; 

Однако, если вы используете MySQL или SQLite или другие базы данных, которые не поддерживают оконное функции, вы можете использовать это другое решение:

запросов для ответов пользователя, а также использовать объединение, чтобы соответствовать другим ответы на соответствующие вопросы с большим счетом (или ранее PK в случае связей). Группируйте вопрос и подсчитайте количество ответов, которые имеют более высокий балл. Если счетчик меньше 10, ответ пользователя входит в число 10 лучших вопросов.

SELECT r1.* 
FROM Responses r1 
LEFT OUTER JOIN Responses r2 ON r1.question_id = r2.question_id 
    AND (r1.total_score < r2.total_score 
    OR r1.total_score = r2.total_score AND r1.response_id > r2.response_id) 
WHERE r1.user_id = ? 
GROUP BY r1.question_id 
HAVING COUNT(*) < 10; 
+0

Это смехотворно умный Билл ... мой единственный вопрос (и я попробую это для себя завтра, когда у меня будет время) будет ли это быстрее, чем повторный подзапрос? Происходит много расчетов. – MikeJ

+0

Производительность сильно зависит от бренда базы данных. Каждая СУБД более эффективна при различных типах запросов. И может быть лучше для вашей конкретной коллекции данных. Я не знаю, какую базу данных вы используете, поэтому я могу посоветовать вам попробовать оба метода и посмотреть, какой из них вам больше нравится. –

+0

Я пишу приложение Rails, поэтому в основном я использую MySQL и SQLite на данный момент, но стараюсь как можно быстрее агрегировать базу данных - оптимизация базы данных может произойти позже. Мне очень нравится ваш второй пример, потому что похоже, что он будет работать практически на любых РСУБД без обходных решений. – MikeJ

2

Попробуйте встроенный оператор выбора. Сегодня у меня нет доступа к инструменту БД, поэтому я не могу подтвердить синтаксис/вывод. Просто сделайте соответствующие изменения, чтобы захватить все нужные столбцы. Вы также можете добавлять вопросы к основному запросу и присоединяться к ответам.

select * 
    from users 
    , responses 
where users.user_id=responses.user_id 
    and responses.response_id in (SELECT z.response_id 
            FROM Responses z 
            WHERE z.user_id = users.user_id 
           ORDER BY total_score DESC 
           LIMIT 10) 
+0

Это не сработает, потому что строки кандидата ответа нужно сгруппировать по question_id, прежде чем вы сможете определить, какие 10 победителей * для этого вопроса *. – Confusion

+0

вот что я имею в виду, включив вопросы. В моем примере вы получите 10 лучших победителей, а не топ-10. – northpole

+0

Он даст вам 10 самых высоких ответов для этого пользователя. Тем не менее, вполне возможно, что ни один из них не является победителем, потому что каждый вопрос, который принадлежит каждому из этих ответов, имеет 10 более высоких оценок ответов 10 других людей. – Confusion

1

Или вы можете действительно оптимизировать его, добавив другое поле типа «IsTopPost». Вы должны обновить высшие посты, когда кто-то голосует, но ваш запрос будет просто:

SELECT * FROM Responses WHERE user_id=? and IsTopPost = 1 
+0

Это сработает, но вы можете быть брошены на меч, чтобы ваш администратор базы данных делал это так. По моему опыту, администраторы баз данных предпочитают получать значения, а не хранить их, если это возможно. Например, не храните количество строк в таблице, а скорее рассчитывайте их, когда они вам понадобятся. – northpole

+0

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

+0

GalacticJello, вы абсолютно правы здесь. Бывают моменты, когда это может быть хорошо, и ваш пример - один из них. – northpole

1

Я думаю, что-то вроде этого следует сделать трюк:

SELECT 
    user_id, question_id, response_id 
FROM 
    Responses AS r1 
WHERE 
    user_id = ? 
AND 
    response_id IN (SELECT response_id 
        FROM Responses AS r2 
        WHERE r2.question_id = r1.question_id 
        ORDER BY total_score DESC LIMIT 10) 

Эффективно для каждого question_id, выполняется подзапрос, который определяет первые 10 ответов для этого question_id.

Возможно, вам захочется добавить столбец, который отмечает определенные ответы как «победители». Таким образом, вы можете просто выбрать эти строки и сохранить базу данных, чтобы вычислять верхние 10 снова и снова.

+0

Как я уже отмечал в первом ответе, единственная проблема заключается в том, что LIMIT в подзапросе не будет работать в MySQL, но отлично работает в стороне от этого. – MikeJ

Смежные вопросы