2009-08-25 5 views
1

Je suis aux prises avec un problème que j'ai dans TSQL, j'ai besoin d'obtenir les 10 premiers résultats pour chaque utilisateur d'une table qui pourrait contenir plus de 10 résultats.TSQL difficile jointure problème

Mon approche naturelle (et procédurale) est "pour chaque utilisateur dans la table T, sélectionnez les 10 premiers résultats classés par date".

Chaque fois que j'essaie de formuler la question dans mon esprit dans une approche basée sur un ensemble, je continue à courir dans le terme «foreach».

Est-il possible de faire quelque chose comme ceci:

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

Ou encore

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

Ou est-il une autre solution à cela en utilisant des tables de temp que je devrais penser?

EDIT:

Juste pour être parfaitement clair - je dois les 10 meilleurs résultats pour chaque utilisateur dans le tableau, par exemple 10 * NN = nombre d'utilisateurs.

EDIT:

En réponse à une suggestion faite par RBarryYoung, je vais avoir un problème, ce qui est le mieux démontré avec le code:

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 

en cours, vous pouvez voir que cette ne limite pas les résultats au TOP 1 ... Est-ce que je fais quelque chose de mal ici?

EDIT:

Il semble que mon dernier exemple fourni un peu de confusion. Voici un exemple montrant ce que je veux faire:

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 

Ce sorties:

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 

Si je distingue:

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 

Je reçois

1 
2 

I besoin

1 
1 
2 

Est-ce que quelqu'un sait si c'est possible?

EDIT:

Le code suivant fera cette

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; 

Je posté dans les commentaires, mais il n'y a pas de mise en forme de code, il ne semble pas très agréable.

+0

vous n'avez pas demandé à limiter les résultats t TOP 1, avez-vous? Vous avez demandé à imiter les résultats pour l'utilisateur TOP 1 * foreach *, ce qui est différent. Mais je suis vrai que ma réponse originale ne tenait pas compte du fait que l'utilisateur (id) avait été dupliqué dans la table source: corrigé maintenant. – RBarryYoung

+0

@RBarryYoung - Désolé mon pote. J'ai ajouté un exemple pour démontrer exactement ce que je veux. C'était un peu simpliste de moi: ( – Khanzor

Répondre

2
select userid, foo, row_number() over (partition by userid order by foo) as rownum from table where rownum <= 10 
+0

Eh bien, pour une raison quelconque, cela ne fonctionne pas avec l'exemple de tableau que j'ai donné, par ex. SELECT id, date, ROW_NUMBER() OVER (PARTITION PAR id ORDER BY date DESC) AS RowNum FROM #temp WHERE RowNum <= 2; Mais si elle est modifiée pour utiliser WITH Blah déclaration: AVEC RowTable AS ( SELECT \t id, date, ROW_NUMBER() OVER (PARTITION BY id ORDER BY Date DESC) AS ROWNUM DE #temp \t SELECT * FROM RowTable WHERE RowNum <= 2; Cela fonctionne très bien. – Khanzor

+0

J'ai essayé un problème similaire pendant quelques heures en essayant tous les moyens de le résoudre - en utilisant la méthode ROW_NUMBER(), j'ai obtenu une requête de 3 minutes à 10 secondes. – DanB

0

Cela est possible, mais l'utilisation de requêtes imbriquées sera plus lente.

Ce qui suit trouverez également les résultats que vous recherchez:

SELECT TOP 10 * 
FROM table as t1 
INNER JOIN table as t2 
    ON t1.id = t2.id 
ORDER BY date DESC 
+0

Cela ne retournera que 10 résultats, je dois sélectionner 10 pour chaque utilisateur – Khanzor

+0

Ah ok, désolé à ce sujet! Votre première requête fonctionnerait plus vite que la seconde – Russell

0

Je crois que ce SO question répondra à votre question. Il ne répond pas exactement à la même question, mais je pense que la solution fonctionnera pour vous aussi.

+0

Je veux juste sélectionner le top 10 des résultats ou moins, cette réponse semble ne sélectionner que lorsque le rang est inférieur au nombre.Il semble plutôt similaire à une phrase HAVING COUNT (*) – Khanzor

4

Oui, il y a plusieurs differet bonnes façons de le faire en 2005 et 2008. Le plus semblable à ce que vous essayez déjà est avec 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 

Cela renvoie alors les dix entrées les plus récentes dans [table] (ou autant qu'il en existe, jusqu'à 10), pour chaque [id] distinct. Asumming que [id] correspond à un utilisateur, alors cela devrait être exactement ce que vous demandez.

(edit: de légères modifications parce que je ne tenaient pas compte du fait que T1 et T2 étaient les mêmes tables et donc il y aura plusieurs t1.IDs en double correspondant à plusieurs T2.ids en double.)

+0

+1 suggestion Awesome! Je n'ai jamais vu CROSS APPLY avant.Cependant, cela ne limite pas les résultats à 10? Voir la dernière édition pour un exemple – Khanzor

+0

En haut sélectionner * mettre sélectionner n * 10 si vous voulez voir pour 3 utilisateurs utilisent select top 30, mais dans ce cas tous les l'utilisateur doit avoir au moins 10 résultats ... – THEn

+0

@THEn - ce n'est pas tout à fait exact, cela limitera simplement les résultats totaux, tout en laissant certains qui en ont plus de 10 dans le jeu de requête . – Khanzor

0

Voici un truc que je utiliser pour ce faire « top-N-par-groupe » type de requête:

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

Cela renvoie uniquement les T1.ids distinctes? – RBarryYoung

+0

Si vous avez besoin de plus de colonnes, vous devez les inclure dans la clause 'GROUP BY'. Sinon, vous pouvez utiliser ce qui précède comme une sous-requête dans un prédicat 'IN()' afin que vous puissiez obtenir le reste des données utilisateur. –