Sous-requête pour retourner la dernière entrée pour chaque ID parent
Question
J'ai une table parent avec des entrées pour les documents et une table d'historique qui enregistre une entrée d'audit chaque fois qu'un utilisateur accède à l'un des documents.
J'écris une requête de recherche pour renvoyer une liste de documents (filtrée selon divers critères) avec le dernier ID utilisateur permettant d'accéder à chaque document renvoyé dans l'ensemble de résultats.
Ainsi pour
DOCUMENTS
ID | NAME
1 | Document 1
2 | Document 2
3 | Document 3
4 | Document 4
5 | Document 5
HISTORY
DOC_ID | USER_ID | TIMESTAMP
1 | 12345 | TODAY
1 | 11111 | IN THE PAST
1 | 11111 | IN THE PAST
1 | 12345 | IN THE PAST
2 | 11111 | TODAY
2 | 12345 | IN THE PAST
3 | 12345 | IN THE PAST
Je chercherais à obtenir un résultat de ma recherche comme
ID | NAME | LAST_USER_ID
1 | Document 1 | 12345
2 | Document 2 | 11111
3 | Document 3 | 12345
4 | Document 4 |
5 | Document 5 |
Puis-je le faire facilement avec une requête SQL et une jointure entre les deux tables?
La solution
Révision des produits d’Andy White et remplacement des crochets (notation MS SQL Server) par DB2 (et ISO standard SQL) "identificateurs délimités":
SELECT d.id, d.name, h.last_user_id
FROM Documents d LEFT JOIN
(SELECT r.doc_id AS id, user_id AS last_user_id
FROM History r JOIN
(SELECT doc_id, MAX("timestamp") AS "timestamp"
FROM History
GROUP BY doc_id
) AS l
ON r."timestamp" = l."timestamp"
AND r.doc_id = l.doc_id
) AS h
ON d.id = h.id
Je ne suis pas absolument certain que " timestamp " ou "TIMESTAMP" est correct - probablement le dernier.
L’avantage de cela est qu’elle remplace la sous-requête corrélée interne dans la version d’Andy par une sous-requête plus simple, non corrélée, qui a le potentiel d’être (radicalement?) plus efficace.
Autres conseils
Je n'ai pas pu obtenir le message "AYANT MAX (TIMESTAMP)". s'exécuter dans SQL Server - Je suppose qu'avoir besoin d'une expression booléenne telle que "having max" (TIMESTAMP) > 2009-03-05 " ou quelque chose qui ne s'applique pas dans ce cas. (Je pourrais faire quelque chose de mal ...)
Voici quelque chose qui semble fonctionner - notez que la jointure a 2 conditions (vous ne savez pas si c'est bon ou pas):
select
d.ID,
d.NAME,
h."USER_ID" as "LAST_USER_ID"
from Documents d
left join History h
on d.ID = h.DOC_ID
and h."TIMESTAMP" =
(
select max("TIMESTAMP")
from "HISTORY"
where "DOC_ID" = d.ID
)
Cela n’utilise pas de jointure, mais pour certaines requêtes de ce type, j’aime insérer la sélection dans le champ. Si vous souhaitez intercepter la situation quand aucun utilisateur n'a accédé, vous pouvez l'envelopper avec un NVL ().
select a.ID, a.NAME,
(select x.user_id
from HISTORY x
where x.doc_id = a.id
and x.timestamp = (select max(x1.timestamp)
from HISTORY x1
where x1.doc_id = x.doc_id)) as LAST_USER_ID
from DOCUMENTS a
where <your criteria here>
Je pense que cela devrait ressembler à ceci:
SELECT ID, Name, b.USER_ID as LAST_USER_ID
FROM DOCUMENTS a LEFT JOIN
( SELECT DOC_ID, USER_ID
FROM HISTORY
GROUP BY DOC_ID, USER_ID
HAVING MAX( TIMESTAMP )) as b
ON a.ID = b.DOC_ID
cela pourrait également fonctionner:
SELECT ID, Name, b.USER_ID as LAST_USER_ID
FROM DOCUMENTS a
LEFT JOIN HISTORY b ON a.ID = b.DOC_ID
GROUP BY DOC_ID, USER_ID
HAVING MAX( TIMESTAMP )
Select ID, Name, User_ID
From Documents Left Outer Join
History a on ID = DOC_ID
Where ( TimeStamp = ( Select Max(TimeStamp)
From History b
Where a.DOC_ID = b.DOC_ID ) OR
TimeStamp Is NULL ) /* this accomodates the Left */