TSQL сложная проблема соединения - PullRequest
1 голос
/ 25 августа 2009

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

Мой естественный (и процедурно-ориентированный) подход - «для каждого пользователя в таблице T выберите 10 лучших результатов, упорядоченных по дате».

Каждый раз, когда я пытаюсь сформулировать вопрос в своем уме с помощью подхода, основанного на множестве, я все время сталкиваюсь с термином «foreach».

Можно ли сделать что-то вроде этого:

SELECT *
FROM table AS t1
INNER JOIN (
    SELECT TOP 10 *
    FROM table AS t2
    WHERE t2.id = t1.id
    ORDER BY date DESC
)

Или даже

SELECT (    SELECT TOP 10 *
             FROM table AS t2
             WHERE t2.id = t1.id
             ORDER BY date    )
FROM table AS t1

Или есть другое решение с использованием временных таблиц, о которых мне следует подумать?

EDIT:

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

EDIT:

В ответ на предложение RBarryYoung возникла проблема, которую лучше всего продемонстрировать с помощью кода:

CREATE TABLE #temp (id INT, date DATETIME)

INSERT INTO #temp (id, date) VALUES (1, GETDATE())
INSERT INTO #temp (id, date) VALUES (1, GETDATE())

SELECT *
FROM #temp AS t1
CROSS APPLY (
 SELECT TOP 1 *
 FROM #temp AS t2
 WHERE t2.id = t1.id
 ORDER BY t2.date DESC
) AS t2

DROP TABLE #temp

Запустив это, вы можете видеть, что это не ограничивает результаты ТОП 1 ... Я что-то здесь не так делаю?

EDIT:

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

CREATE TABLE #temp (id INT, date DATETIME)
INSERT INTO #temp (id, date) VALUES (1, GETDATE())
INSERT INTO #temp (id, date) VALUES (1, GETDATE())
INSERT INTO #temp (id, date) VALUES (1, GETDATE())
INSERT INTO #temp (id, date) VALUES (2, GETDATE())

SELECT *
FROM #temp AS t1
CROSS APPLY
(
    SELECT TOP 2 *
 FROM #temp AS t2
    WHERE t2.id = t1.id
    ORDER BY t2.date DESC
) AS t2

DROP TABLE #temp

Это выводит:

1 2009-08-26 09:05:56.570 1 2009-08-26 09:05:56.583
1 2009-08-26 09:05:56.570 1 2009-08-26 09:05:56.583
1 2009-08-26 09:05:56.583 1 2009-08-26 09:05:56.583
1 2009-08-26 09:05:56.583 1 2009-08-26 09:05:56.583
1 2009-08-26 09:05:56.583 1 2009-08-26 09:05:56.583
1 2009-08-26 09:05:56.583 1 2009-08-26 09:05:56.583
2 2009-08-26 09:05:56.583 2 2009-08-26 09:05:56.583

Если я использую разные:

SELECT DISTINCT t1.id
FROM #temp AS t1
CROSS APPLY
(
    SELECT TOP 2 *
 FROM #temp AS t2
    WHERE t2.id = t1.id
    ORDER BY t2.date DESC
) AS t2

Я получаю

1
2

мне нужно

1
1
2

Кто-нибудь знает, возможно ли это?

EDIT:

Следующий код сделает это

WITH RowTable AS
(
SELECT 
 id, date, ROW_NUMBER() OVER (PARTITION BY id ORDER BY date DESC) AS RowNum
FROM #temp 
)
SELECT *
FROM RowTable
WHERE RowNum <= 2;

Я написал в комментариях, но форматирование кода отсутствует, поэтому выглядит не очень хорошо.

Ответы [ 5 ]

4 голосов
/ 25 августа 2009

Да, есть несколько разных хороших способов сделать это в 2005 и 2008 годах. Один из наиболее похожих на то, что вы уже пробовали, с CROSS APPLY:

SELECT T2.*
FROM (
    SELECT DISTINCT ID FROM table
) AS t1
CROSS APPLY (
    SELECT TOP 10 *
    FROM table AS t2
    WHERE t2.id = t1.id
    ORDER BY date DESC
) AS t2
ORDER BY T2.id, date DESC

Затем возвращается десять самых последних записей в [таблица] (или столько, сколько существует, до 10) для каждого отдельного [id]. Если предположить, что [id] соответствует пользователю, то это должно быть именно то, что вы просите.

(правка: небольшие изменения, поскольку я не учел, что T1 и T2 - это одни и те же таблицы и, следовательно, будет несколько дубликатов t1.ID, соответствующих нескольким дублирующимся T2.ids.)

2 голосов
/ 26 августа 2009
select userid, foo, row_number() over (partition by userid order by foo)  as rownum from table where rownum <= 10
0 голосов
/ 25 августа 2009

Вот трюк, который я использую, чтобы выполнить запрос типа «топ-на-группу»:

SELECT t1.id
FROM table t1 LEFT OUTER JOIN table t2 
 ON (t1.user_id = t2.user_id AND (t1.date > t2.date
     OR t1.date = t2.date AND t1.id > t2.id))
GROUP BY t1.id
HAVING COUNT(*) < 10
ORDER BY t1.user_id, COALESCE(COUNT(*), 0);
0 голосов
/ 25 августа 2009

Я верю, что ТАК вопрос ответит на ваш вопрос. Он не отвечает точно на тот же вопрос, но я думаю, что решение будет работать и для вас.

0 голосов
/ 25 августа 2009

Возможно, однако использование вложенных запросов будет медленнее.

Следующие также найдут результаты, которые вы ищете:

SELECT TOP 10 * 
FROM table as t1
INNER JOIN table as t2 
  ON t1.id = t2.id
ORDER BY date DESC
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...