Subconsulta para devolver la última entrada para cada ID de padre.
Pregunta
Tengo una tabla principal con entradas para documentos y tengo una tabla de historial que registra una entrada de auditoría cada vez que un usuario accede a uno de los documentos.
Estoy escribiendo una consulta de búsqueda para devolver una lista de documentos (filtrada por varios criterios) con la última identificación de usuario para acceder a cada documento devuelto en el conjunto de resultados.
Así para
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
Estaría buscando una devolución de mi búsqueda como
ID | NAME | LAST_USER_ID
1 | Document 1 | 12345
2 | Document 2 | 11111
3 | Document 3 | 12345
4 | Document 4 |
5 | Document 5 |
¿Puedo hacer esto fácilmente con una consulta SQL y una unión entre las dos tablas?
Solución
Revisando lo que Andy White produjo y reemplazando los corchetes (notación MS SQL Server) con DB2 (y el estándar ISO) identificadores delimitados " ;:
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
No estoy absolutamente seguro de si " marca de tiempo " o " TIMESTAMP " es correcto, probablemente este último.
La ventaja de esto es que reemplaza la subconsulta correlacionada interna en la versión de Andy por una subconsulta no correlacionada más simple, que tiene el potencial de ser (¿radicalmente?) más eficiente.
Otros consejos
No pude obtener el " HAVING MAX (TIMESTAMP) " para ejecutarse en SQL Server: supongo que tener una expresión booleana como " tener max (TIMESTAMP) > 2009-03-05 " O algo así, que no se aplica en este caso. (Podría estar haciendo algo mal ...)
Aquí hay algo que parece funcionar. Tenga en cuenta que la unión tiene 2 condiciones (no estoy seguro de si esto es bueno o no):
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
)
Esto no usa una combinación, pero para algunas consultas como esta me gusta alinear la selección para el campo. Si desea detectar la situación cuando ningún usuario ha accedido, puede envolverla con una 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>
Creo que debería ser algo como esto:
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
esto podría funcionar también:
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 */