Obtenga la primera fila de cada grupo


530

Tengo una tabla en la que quiero obtener la última entrada para cada grupo. Aquí está la tabla:

DocumentStatusLogs Mesa

|ID| DocumentID | Status | DateCreated |
| 2| 1          | S1     | 7/29/2011   |
| 3| 1          | S2     | 7/30/2011   |
| 6| 1          | S1     | 8/02/2011   |
| 1| 2          | S1     | 7/28/2011   |
| 4| 2          | S2     | 7/30/2011   |
| 5| 2          | S3     | 8/01/2011   |
| 6| 3          | S1     | 8/02/2011   |

La tabla se agrupará DocumentIDy ordenará por DateCreatedorden descendente. Para cada uno DocumentID, quiero obtener el último estado.

Mi salida preferida:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • ¿Hay alguna función agregada para obtener solo la parte superior de cada grupo? Ver pseudocódigo a GetOnlyTheTopcontinuación:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
  • Si tal función no existe, ¿hay alguna forma de lograr el resultado que quiero?

  • O, en primer lugar, ¿podría ser esto causado por una base de datos no normalizada? Estoy pensando, ya que lo que estoy buscando es solo una fila, ¿debería statusestar también ubicado en la tabla principal?

Consulte la tabla principal para obtener más información:

DocumentsTabla actual

| DocumentID | Title  | Content  | DateCreated |
| 1          | TitleA | ...      | ...         |
| 2          | TitleB | ...      | ...         |
| 3          | TitleC | ...      | ...         |

¿La tabla principal debería ser así para poder acceder fácilmente a su estado?

| DocumentID | Title  | Content  | DateCreated | CurrentStatus |
| 1          | TitleA | ...      | ...         | s1            |
| 2          | TitleB | ...      | ...         | s3            |
| 3          | TitleC | ...      | ...         | s1            |

ACTUALIZACIÓN Acabo de aprender a usar "aplicar", lo que hace que sea más fácil abordar tales problemas.


2
Para una discusión más detallada y la comparación de posibles soluciones, recomiendo leer la pregunta similar en dba.se: Recuperando n filas por grupo .
Vladimir Baranov el

Miré la publicación y lo probé. El uso de group by StoreID generó un error.
UltraJ

Respuestas:


757
;WITH cte AS
(
   SELECT *,
         ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC) AS rn
   FROM DocumentStatusLogs
)
SELECT *
FROM cte
WHERE rn = 1

Si espera 2 entradas por día, esto arbitrariamente elegirá una. Para obtener ambas entradas por un día, use DENSE_RANK en su lugar

En cuanto a normalizado o no, depende si desea:

  • mantener el estado en 2 lugares
  • preservar el historial de estado
  • ...

Tal como está, conserva el historial de estado. Si también desea el último estado en la tabla principal (que es la desnormalización) necesitaría un activador para mantener el "estado" en el principal. o descartar esta tabla de historial de estado.


55
Y ... que es Partition By? Withtambién es nuevo para mí :( Estoy usando mssql 2005 de todos modos.
dpp

66
@domanokz: Partition By restablece el recuento. Entonces, en este caso, dice contar por DocumentID
gbn

1
Hm, me preocupa el rendimiento, consultaré millones de filas. ¿SELECT * FROM (SELECT ...) afecta el rendimiento? Además, ¿hay ROW_NUMBERalgún tipo de subconsulta para cada fila?
dpp

1
@domanokz: no, no es una subconsulta. Si tiene índices correctos, millones no deberían ser un problema. De todos modos, solo hay 2 formas basadas en conjuntos: esto y el agregado (la solución de Ariel). Así que pruébelos a ambos ...
gbn

1
@domanokz: simplemente cambie ORDER BY DateCreated DESC por ORDER BY ID DESC
gbn

184

Acabo de aprender a usar cross apply. Aquí se explica cómo usarlo en este escenario:

 select d.DocumentID, ds.Status, ds.DateCreated 
 from Documents as d 
 cross apply 
     (select top 1 Status, DateCreated
      from DocumentStatusLogs 
      where DocumentID = d.DocumentId
      order by DateCreated desc) as ds

2
Eso en realidad no hace ninguna diferencia, ya que el problema aún se aborda.
dpp

19
Acabo de publicar los resultados de mis pruebas de tiempo con todas las soluciones propuestas y la suya quedó en primer lugar. Dándole un voto positivo :-)
John Fairbanks

3
+1 para una gran mejora de velocidad. Esto es mucho más rápido que una función de ventanas como ROW_NUMBER (). Sería bueno que SQL reconociera ROW_NUMBER () = 1 como consultas y las optimizara en Applies. Nota: Usé OUTER APPLY ya que necesitaba resultados, incluso si no existían en la solicitud.
TamusJRoyce

8
@TamusJRoyce no puedes extrapolar eso solo porque fue más rápido una vez que este es siempre el caso. Depende. Como se describe aquí sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
Mi comentario es sobre tener varias filas y solo desear una de esas múltiples filas por grupo. Las uniones son para cuando quieres una a muchas. Las aplicaciones son para cuando tiene uno a muchos, pero desea filtrar todos excepto uno a uno. Escenario: para 100 miembros, deme cada uno su mejor número de teléfono (donde cada uno podría tener varios números). Aquí es donde Apply sobresale. Menos lecturas = menos acceso al disco = mejor rendimiento. Dada mi experiencia es con bases de datos no normalizadas mal diseñadas.
TamusJRoyce

53

He hecho algunos tiempos sobre las diversas recomendaciones aquí, y los resultados realmente dependen del tamaño de la tabla involucrada, pero la solución más consistente es usar CROSS APPLY. Estas pruebas se ejecutaron en SQL Server 2008-R2, usando una tabla con 6.500 registros, y otro (esquema idéntico) con 137 millones de registros. Las columnas que se consultan son parte de la clave primaria en la tabla, y el ancho de la tabla es muy pequeño (aproximadamente 30 bytes). SQL Server informa los tiempos del plan de ejecución real.

Query                                  Time for 6500 (ms)    Time for 137M(ms)

CROSS APPLY                                    17.9                17.9
SELECT WHERE col = (SELECT MAX(COL)…)           6.6               854.4
DENSE_RANK() OVER PARTITION                     6.6               907.1

Creo que lo realmente sorprendente fue cuán consistente fue el tiempo para la APLICACIÓN CRUZADA, independientemente de la cantidad de filas involucradas.


8
Todo depende de la distribución de datos y los índices disponibles. Se discutió en grandes longitudes en dba.se .
Vladimir Baranov el

48

Sé que este es un hilo viejo pero el TOP 1 WITH TIES soluciones son bastante buenas y podrían ser útiles para leer algunas de las soluciones.

select top 1 with ties
   DocumentID
  ,Status
  ,DateCreated
from DocumentStatusLogs
order by row_number() over (partition by DocumentID order by DateCreated desc)

Puede encontrar más información sobre la cláusula TOP aquí .


77
Esta es la solución más elegante de la OMI
George Menoutis

1
de acuerdo - esto es lo mejor para replicar lo que es muy fácil de hacer en otras versiones de SQL y otros idiomas
Chris Umphlett

27

Si le preocupa el rendimiento, también puede hacerlo con MAX ():

SELECT *
FROM DocumentStatusLogs D
WHERE DateCreated = (SELECT MAX(DateCreated) FROM DocumentStatusLogs WHERE ID = D.ID)

ROW_NUMBER () requiere una especie de todas las filas en su instrucción SELECT, mientras que MAX no. Debería acelerar drásticamente su consulta.


2
¿No se pueden abordar los problemas de rendimiento con ROW_NUMBER () con una indexación adecuada? (Creo que debería hacerse de todos modos)
Kristoffer L

8
Con datetime, no puede garantizar que no se agregarán dos entradas en la misma fecha y hora. La precisión no es lo suficientemente alta.
TamusJRoyce

+1 por simplicidad. @TamusJRoyce tiene razón. ¿Qué pasa? 'seleccione * de DocumentStatusLog D donde ID = (seleccione ID de DocumentsStatusLog donde D.DocumentID = DocumentID ordenado por DateCreated DESC límite 1);'
cibercitizen1

SELECT * FROM EventScheduleTbl D WHERE DatesPicked = (SELECT top 1 min (DatesPicked) FROM EventScheduleTbl WHERE EventIDf = D.EventIDf y DatesPicked> = convert (date, getdate ()))
Arun Prasad ES

Definitivamente, hay casos en los que esto superará row_number()incluso con una indexación adecuada. Lo encuentro especialmente valioso en escenarios de autounión. Sin embargo, lo que hay que tener en cuenta es que este método a menudo producirá un mayor número de lecturas lógicas y recuentos de escaneo, a pesar de informar un bajo costo de subárbol. Deberá sopesar el costo / beneficios en su caso particular para determinar si en realidad es mejor.
pimbrouwers

26
SELECT * FROM
DocumentStatusLogs JOIN (
  SELECT DocumentID, MAX(DateCreated) DateCreated
  FROM DocumentStatusLogs
  GROUP BY DocumentID
  ) max_date USING (DocumentID, DateCreated)

¿Qué servidor de base de datos? Este código no funciona en todos ellos.

Con respecto a la segunda mitad de su pregunta, me parece razonable incluir el estado como una columna. Puedes irteDocumentStatusLogs como un registro, pero aún almacenar la información más reciente en la tabla principal.

Por cierto, si ya tiene la DateCreatedcolumna en la tabla Documentos, puede unirse DocumentStatusLogsusando eso (siempre que DateCreatedsea ​​único enDocumentStatusLogs ).

Editar: MsSQL no admite el USO, así que cámbielo a:

ON DocumentStatusLogs.DocumentID = max_date.DocumentID AND DocumentStatusLogs.DateCreated = max_date.DateCreated

55
La pista estaba en el título: MSSQL. SQL Server no tiene USING pero la idea está bien.
gbn

77
@gbn Los moderadores estúpidos generalmente eliminan palabras clave importantes de los títulos, como lo han hecho aquí. Lo que dificulta encontrar las respuestas correctas en los resultados de búsqueda o en Google.
NickG

2
Solo para señalar que esta "solución" aún puede darle múltiples registros si tiene un empate en elmax(DateCreated)
MoonKnight

12

Esta es una de las preguntas más fáciles de encontrar sobre el tema, por lo que quería dar una respuesta moderna (tanto para mi referencia como para ayudar a otros). Al usar first_valuey overpuede hacer un breve trabajo de la consulta anterior:

Select distinct DocumentID
  , first_value(status) over (partition by DocumentID order by DateCreated Desc) as Status
  , first_value(DateCreated) over (partition by DocumentID order by DateCreated Desc) as DateCreated
From DocumentStatusLogs

Esto debería funcionar en Sql Server 2008 y versiones posteriores. First_valuepuede considerarse como una forma de lograrlo Select Top 1cuando se usa una overcláusula Overpermite la agrupación en la lista de selección, por lo que en lugar de escribir subconsultas anidadas (como hacen muchas de las respuestas existentes), esto lo hace de una manera más legible. Espero que esto ayude.


2
Esto no funciona en SQL Server 2008 R2. ¡Creo que first_value se introdujo en 2012!
ovni

1
¡Muy rapido! Estaba usando la solución Cross Apply que ofrece @dpp, pero esta es muchísimo más rápida.
MattSlay

11

Este es un hilo bastante antiguo, pero pensé que arrojaría mis dos centavos de la misma manera que la respuesta aceptada no funcionó particularmente bien para mí. Probé la solución de gbn en un gran conjunto de datos y descubrí que era terriblemente lenta (> 45 segundos con más de 5 millones de registros en SQL Server 2012). Al observar el plan de ejecución, es obvio que el problema es que requiere una operación SORT que ralentiza las cosas significativamente.

Aquí hay una alternativa que saqué del marco de la entidad que no necesita operación SORT y realiza una búsqueda de índice NO agrupado. Esto reduce el tiempo de ejecución a <2 segundos en el conjunto de registros antes mencionado.

SELECT 
[Limit1].[DocumentID] AS [DocumentID], 
[Limit1].[Status] AS [Status], 
[Limit1].[DateCreated] AS [DateCreated]
FROM   (SELECT DISTINCT [Extent1].[DocumentID] AS [DocumentID] FROM [dbo].[DocumentStatusLogs] AS [Extent1]) AS [Distinct1]
OUTER APPLY  (SELECT TOP (1) [Project2].[ID] AS [ID], [Project2].[DocumentID] AS [DocumentID], [Project2].[Status] AS [Status], [Project2].[DateCreated] AS [DateCreated]
    FROM (SELECT 
        [Extent2].[ID] AS [ID], 
        [Extent2].[DocumentID] AS [DocumentID], 
        [Extent2].[Status] AS [Status], 
        [Extent2].[DateCreated] AS [DateCreated]
        FROM [dbo].[DocumentStatusLogs] AS [Extent2]
        WHERE ([Distinct1].[DocumentID] = [Extent2].[DocumentID])
    )  AS [Project2]
    ORDER BY [Project2].[ID] DESC) AS [Limit1]

Ahora estoy asumiendo algo que no está completamente especificado en la pregunta original, pero si el diseño de su tabla es tal que su columna de ID es un ID de incremento automático, y DateCreated se establece en la fecha actual con cada inserción, entonces incluso sin ejecutar mi consulta anterior, en realidad podría obtener un aumento considerable del rendimiento de la solución de gbn (aproximadamente la mitad del tiempo de ejecución) simplemente ordenando en ID en lugar de ordenar en DateCreated, ya que esto proporcionará un orden de clasificación idéntico y es una clasificación más rápida.


5

Mi código para seleccionar el top 1 de cada grupo

seleccione a. * de #DocumentStatusLogs a where 
 datecreated in (seleccione el top 1 datecreated de #DocumentStatusLogs b
dónde 
a.documentid = b.documentid
ordenar por fecha creado
)

3

Verificando la asombrosa y correcta respuesta de Clint desde arriba:

El rendimiento entre las dos consultas a continuación es interesante. 52% es el mejor. Y el 48% es el segundo. Una mejora del 4% en el rendimiento utilizando DISTINCT en lugar de ORDER BY. Pero ORDER BY tiene la ventaja de ordenar por múltiples columnas.

IF (OBJECT_ID('tempdb..#DocumentStatusLogs') IS NOT NULL) BEGIN DROP TABLE #DocumentStatusLogs END

CREATE TABLE #DocumentStatusLogs (
    [ID] int NOT NULL,
    [DocumentID] int NOT NULL,
    [Status] varchar(20),
    [DateCreated] datetime
)

INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (2, 1, 'S1', '7/29/2011 1:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (3, 1, 'S2', '7/30/2011 2:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (6, 1, 'S1', '8/02/2011 3:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (1, 2, 'S1', '7/28/2011 4:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (4, 2, 'S2', '7/30/2011 5:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (5, 2, 'S3', '8/01/2011 6:00:00')
INSERT INTO #DocumentStatusLogs([ID], [DocumentID], [Status], [DateCreated]) VALUES (6, 3, 'S1', '8/02/2011 7:00:00')

Opción 1:

    SELECT
    [Extent1].[ID], 
    [Extent1].[DocumentID],
    [Extent1].[Status], 
    [Extent1].[DateCreated]
FROM #DocumentStatusLogs AS [Extent1]
    OUTER APPLY (
        SELECT TOP 1
            [Extent2].[ID], 
            [Extent2].[DocumentID],
            [Extent2].[Status], 
            [Extent2].[DateCreated]
        FROM #DocumentStatusLogs AS [Extent2]
        WHERE [Extent1].[DocumentID] = [Extent2].[DocumentID]
        ORDER BY [Extent2].[DateCreated] DESC, [Extent2].[ID] DESC
    ) AS [Project2]
WHERE ([Project2].[ID] IS NULL OR [Project2].[ID] = [Extent1].[ID])

Opcion 2:

SELECT 
    [Limit1].[DocumentID] AS [ID], 
    [Limit1].[DocumentID] AS [DocumentID], 
    [Limit1].[Status] AS [Status], 
    [Limit1].[DateCreated] AS [DateCreated]
FROM (
    SELECT DISTINCT [Extent1].[DocumentID] AS [DocumentID] FROM #DocumentStatusLogs AS [Extent1]
) AS [Distinct1]
    OUTER APPLY  (
        SELECT TOP (1) [Project2].[ID] AS [ID], [Project2].[DocumentID] AS [DocumentID], [Project2].[Status] AS [Status], [Project2].[DateCreated] AS [DateCreated]
        FROM (
            SELECT 
                [Extent2].[ID] AS [ID], 
                [Extent2].[DocumentID] AS [DocumentID], 
                [Extent2].[Status] AS [Status], 
                [Extent2].[DateCreated] AS [DateCreated]
            FROM #DocumentStatusLogs AS [Extent2]
            WHERE [Distinct1].[DocumentID] = [Extent2].[DocumentID]
        )  AS [Project2]
        ORDER BY [Project2].[ID] DESC
    ) AS [Limit1]

Management Studio de M $: después de resaltar y ejecutar el primer bloque, resalte la opción 1 y la opción 2, haga clic con el botón derecho -> [Mostrar plan de ejecución estimado]. Luego ejecute todo para ver los resultados.

Opción 1 Resultados:

ID  DocumentID  Status  DateCreated
6   1   S1  8/2/11 3:00
5   2   S3  8/1/11 6:00
6   3   S1  8/2/11 7:00

Opción 2 Resultados:

ID  DocumentID  Status  DateCreated
6   1   S1  8/2/11 3:00
5   2   S3  8/1/11 6:00
6   3   S1  8/2/11 7:00

Nota:

Tiendo a usar APLICAR cuando quiero que una combinación sea 1 a (1 de muchas).

Utilizo un JOIN si quiero que el join sea 1-to-many o many-to-many.

Evito CTE con ROW_NUMBER () a menos que necesite hacer algo avanzado y estoy de acuerdo con la penalización de rendimiento de ventanas.

También evito las subconsultas EXISTS / IN en la cláusula WHERE u ON, ya que he experimentado que esto causa algunos planes de ejecución terribles. Pero el kilometraje varía. ¡Revise el plan de ejecución y el rendimiento del perfil donde y cuando sea necesario!


3

Esta solución se puede usar para obtener las N filas más recientes TOP para cada partición (en el ejemplo, N es 1 en la instrucción WHERE y la partición es doc_id):

SELECT doc_id, status, date_created FROM 
(
    SELECT a.*, ROW_NUMBER() OVER (PARTITION BY doc_id ORDER BY date_created DESC) AS rnk FROM doc a
)
WHERE rnk = 1;

2
SELECT o.*
FROM `DocumentStatusLogs` o                   
  LEFT JOIN `DocumentStatusLogs` b                   
  ON o.DocumentID = b.DocumentID AND o.DateCreated < b.DateCreated
 WHERE b.DocumentID is NULL ;

Si desea devolver solo el pedido de documentos recientes por DateCreated, devolverá solo el documento 1 superior por DocumentID


2

CROSS APPLYfue el método que utilicé para mi solución, ya que funcionó para mí y para las necesidades de mis clientes. Y por lo que he leído, debería proporcionar el mejor rendimiento general en caso de que su base de datos crezca sustancialmente.


1

Aquí hay 3 enfoques diferentes para el problema en cuestión junto con las mejores opciones de indexación para cada una de esas consultas (pruebe los índices y vea la lectura lógica, el tiempo transcurrido y el plan de ejecución. He proporcionado las sugerencias de mi experiencia en tales consultas sin ejecutar para este problema específico).

Enfoque 1 : Uso de ROW_NUMBER (). Si el índice de almacén de filas no puede mejorar el rendimiento, puede probar el índice de almacén de columnas no agrupado / agrupado para consultas con agregación y agrupación y para tablas que están ordenadas por columnas diferentes todo el tiempo, el índice de almacén de columnas generalmente es la mejor opción.

;WITH CTE AS
    (
       SELECT   *,
                RN = ROW_NUMBER() OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
       FROM     DocumentStatusLogs
    )
    SELECT  ID      
        ,DocumentID 
        ,Status     
        ,DateCreated
    FROM    CTE
    WHERE   RN = 1;

Enfoque 2 : Uso de FIRST_VALUE. Si el índice de almacén de filas no puede mejorar el rendimiento, puede probar el índice de almacén de columnas no agrupado / agrupado para consultas con agregación y agrupación y para tablas que están ordenadas por columnas diferentes todo el tiempo, el índice de almacén de columnas generalmente es la mejor opción.

SELECT  DISTINCT
    ID      = FIRST_VALUE(ID) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
    ,DocumentID
    ,Status     = FIRST_VALUE(Status) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
    ,DateCreated    = FIRST_VALUE(DateCreated) OVER (PARTITION BY DocumentID ORDER BY DateCreated DESC)
FROM    DocumentStatusLogs;

Enfoque 3 : Uso de CROSS APPLY. Crear un índice de almacén de filas en la tabla DocumentStatusLogs que cubra las columnas utilizadas en la consulta debería ser suficiente para cubrir la consulta sin necesidad de un índice de almacén de columnas.

SELECT  DISTINCT
    ID      = CA.ID
    ,DocumentID = D.DocumentID
    ,Status     = CA.Status 
    ,DateCreated    = CA.DateCreated
FROM    DocumentStatusLogs D
    CROSS APPLY (
            SELECT  TOP 1 I.*
            FROM    DocumentStatusLogs I
            WHERE   I.DocumentID = D.DocumentID
            ORDER   BY I.DateCreated DESC
            ) CA;

1

Creo que esto se puede hacer así. Esto puede necesitar algunos ajustes, pero puede seleccionar el máximo del grupo.

Estas respuestas son excesivas ...

SELECT
  d.DocumentID,
  MAX(d.Status),
  MAX(d1.DateCreated)
FROM DocumentStatusLogs d, DocumentStatusLogs d1
USING(DocumentID)
GROUP BY d.DocumentID
ORDER BY DateCreated DESC

0

En los escenarios en los que desea evitar el uso de row_count (), también puede usar una combinación izquierda:

select ds.DocumentID, ds.Status, ds.DateCreated 
from DocumentStatusLogs ds
left join DocumentStatusLogs filter 
    ON ds.DocumentID = filter.DocumentID
    -- Match any row that has another row that was created after it.
    AND ds.DateCreated < filter.DateCreated
-- then filter out any rows that matched 
where filter.DocumentID is null 

Para el esquema de ejemplo, también podría usar un "no en subconsulta", que generalmente se compila en la misma salida que la combinación izquierda:

select ds.DocumentID, ds.Status, ds.DateCreated 
from DocumentStatusLogs ds
WHERE ds.ID NOT IN (
    SELECT filter.ID 
    FROM DocumentStatusLogs filter
    WHERE ds.DocumentID = filter.DocumentID
        AND ds.DateCreated < filter.DateCreated)

Tenga en cuenta que el patrón de subconsulta no funcionaría si la tabla no tuviera al menos una clave / restricción / índice único de una sola columna, en este caso la clave primaria "Id".

Ambas consultas tienden a ser más "caras" que la consulta row_count () (medida por el Analizador de consultas). Sin embargo, puede encontrar escenarios en los que devuelven resultados más rápido o permiten otras optimizaciones.


0
SELECT documentid, 
       status, 
       datecreated 
FROM   documentstatuslogs dlogs 
WHERE  status = (SELECT status 
                 FROM   documentstatuslogs 
                 WHERE  documentid = dlogs.documentid 
                 ORDER  BY datecreated DESC 
                 LIMIT  1) 

0

Prueba esto:

SELECT [DocumentID]
    ,[tmpRez].value('/x[2]', 'varchar(20)') AS [Status]
    ,[tmpRez].value('/x[3]', 'datetime') AS [DateCreated]
FROM (
    SELECT [DocumentID]
        ,cast('<x>' + max(cast([ID] AS VARCHAR(10)) + '</x><x>' + [Status] + '</x><x>' + cast([DateCreated] AS VARCHAR(20))) + '</x>' AS XML) AS [tmpRez]
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ) AS [tmpQry]

Siempre debe describir su declaración SQL de cómo funcionará y resolver la consulta del OP.
Suraj Kumar

-1

Esta es la TSQL más vainilla que se me ocurre

    SELECT * FROM DocumentStatusLogs D1 JOIN
    (
      SELECT
        DocumentID,MAX(DateCreated) AS MaxDate
      FROM
        DocumentStatusLogs
      GROUP BY
        DocumentID
    ) D2
    ON
      D2.DocumentID=D1.DocumentID
    AND
      D2.MaxDate=D1.DateCreated

Lamentablemente, MaxDate no es único. Es posible tener dos fechas ingresadas al mismo tiempo exacto. Entonces esto puede resultar en duplicados por grupo. Sin embargo, puede usar una columna de identidad o GUID. La Columna de identidad le proporcionaría la última que se ha ingresado (se está utilizando el cálculo de identidad predeterminado, 1 ... x paso 1).
TamusJRoyce

Bueno, estoy de acuerdo, pero el autor solicitó la última entrada, que, a menos que incluya una columna de identidad de incremento automático, significa que dos elementos agregados exactamente al mismo tiempo son igualmente 'los últimos'
rico s

El último registro será un registro. Entonces sí. Debe considerar la columna de identidad de incremento automático.
TamusJRoyce

-2

En SQLite se marca que puede usar la siguiente consulta simple con GROUP BY

SELECT MAX(DateCreated), *
FROM DocumentStatusLogs
GROUP BY DocumentID

Aquí MAX ayuda a obtener el DateCreated máximo de cada grupo.

Pero parece que MYSQL no asocia * -columns con el valor de max DateCreated :(

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.