Выберите элементы, которые являются первыми N результатами для связанной таблицы - PullRequest
5 голосов
/ 01 июля 2010

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

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

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

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

Ответы [ 4 ]

3 голосов
/ 01 июля 2010

Если вы используете 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;
2 голосов
/ 01 июля 2010

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

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)
1 голос
/ 01 июля 2010

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

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 снова и снова.

1 голос
/ 01 июля 2010

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

SELECT * FROM Responses WHERE user_id=? and IsTopPost = 1
...