Obtenha a primeira linha de cada grupo


530

Eu tenho uma tabela na qual quero obter a entrada mais recente para cada grupo. Aqui está a tabela:

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   |

A tabela será agrupada DocumentIDe classificada por DateCreatedordem decrescente. Para cada um DocumentID, quero obter o status mais recente.

Minha saída preferida:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • Existe alguma função agregada para obter apenas o topo de cada grupo? Veja pseudo-código GetOnlyTheTopabaixo:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
    
  • Se essa função não existir, existe alguma maneira de obter a saída desejada?

  • Ou, em primeiro lugar, isso poderia ser causado por banco de dados não normalizado? Estou pensando, já que o que estou procurando é apenas uma linha, isso statustambém deve estar localizado na tabela pai?

Consulte a tabela pai para obter mais informações:

DocumentsTabela Atual

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

A tabela pai deve ser assim para que eu possa acessar facilmente seu status?

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

ATUALIZAÇÃO Acabei de aprender a usar "aplicar", o que facilita a solução desses problemas.


2
Para uma discussão mais detalhada e comparação de possíveis soluções, recomendo ler a pergunta semelhante no dba.se: Recuperando n linhas por grupo .
Vladimir Baranov

Eu olhei para o post e tentei. O uso do grupo por StoreID gerou um erro.
UltraJ

Respostas:


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

Se você espera duas entradas por dia, isso selecionará arbitrariamente uma. Para obter as duas entradas de um dia, use DENSE_RANK.

Quanto à normalização ou não, depende se você deseja:

  • manter o status em 2 lugares
  • preservar o histórico de status
  • ...

Tal como está, você preserva o histórico de status. Se você também deseja o status mais recente na tabela pai (que é desnormalização), precisará de um gatilho para manter o "status" no pai. ou solte esta tabela do histórico de status.


5
E ... o que é Partition By? Withé novo para mim também :( Eu estou usando MSSQL 2005 de qualquer maneira.
DPP

6
@domanokz: Partition By redefine a contagem. Portanto, neste caso, ele diz contar por DocumentID
gbn 27/07

1
Preocupo-me com o desempenho, consultarei milhões de linhas. SELECT * FROM (SELECT ...) afeta o desempenho? Além disso, existe ROW_NUMBERalgum tipo de subconsulta para cada linha?
dpp 27/07

1
@domanokz: não, não é uma subconsulta. Se você tiver índices corretos, milhões não devem ser um problema. De qualquer maneira, existem apenas duas formas baseadas em conjuntos: this e o agregado (solução de Ariel). Então tente os dois ...
gbn 27/07

1
@domanokz: Basta alterar ORDER BY DateCreated DESC para ORDER BY ID DESC
gbn 27/07

184

Acabei de aprender a usar cross apply. Veja como usá-lo neste cenário:

 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
Na verdade, isso não faz diferença, pois o problema ainda está sendo tratado.
dpp

19
Acabei de publicar os resultados dos meus testes de cronometragem em relação a todas as soluções propostas e a sua saiu por cima. Dando-lhe um voto-se :-)
John Fairbanks

3
+1 para uma enorme melhoria de velocidade. Isso é muito mais rápido que uma função de janela, como ROW_NUMBER (). Seria bom se o SQL reconhecesse ROW_NUMBER () = 1 como consultas e as otimizasse no Applies. Nota: Usei OUTER APPLY porque precisava de resultados, mesmo que eles não existissem na aplicação.
TamusJRoyce

8
@TamusJRoyce, você não pode extrapolar isso apenas porque era mais rápido, pois esse sempre é o caso. Depende. Conforme descrito aqui sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
Meu comentário é sobre ter várias linhas e desejar apenas uma dessas várias linhas por grupo. As junções são para quando você quer um para muitos. Aplica-se a quando você tem um para muitos, mas deseja filtrar todos, exceto um para um. Cenário: para 100 membros, forneça a cada um deles o melhor número de telefone (onde cada um pode ter vários números). É aqui que o Apply se destaca. Menos leituras = menos acesso ao disco = melhor desempenho. Dada a minha experiência, é com bancos de dados não normalizados mal projetados.
TamusJRoyce

53

Fiz alguns ajustes nas várias recomendações aqui, e os resultados realmente dependem do tamanho da tabela envolvida, mas a solução mais consistente é usar o CROSS APPLY. Esses testes foram executados no SQL Server 2008-R2, usando uma tabela com 6.500 registros e outro (esquema idêntico) com 137 milhões de registros. As colunas que estão sendo consultadas fazem parte da chave primária da tabela e a largura da tabela é muito pequena (cerca de 30 bytes). Os horários são relatados pelo SQL Server a partir do plano de execução 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

Eu acho que o mais incrível foi a consistência do tempo para o CROSS APPLY, independentemente do número de linhas envolvidas.


8
Tudo depende da distribuição de dados e dos índices disponíveis. Ele foi discutida em grandes comprimentos sobre dba.se .
Vladimir Baranov

48

Eu sei que esse é um tópico antigo, mas as TOP 1 WITH TIESsoluções são bastante boas e podem ser úteis para algumas leituras das soluções.

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

Mais informações sobre a cláusula TOP podem ser encontradas aqui .


7
Esta é a solução mais elegante imo
George Menoutis 16/10

1
concordaram - este melhores repetições o que é muito fácil de fazer em outras versões do SQL e outras línguas imo
Chris Umphlett

27

Se você está preocupado com o desempenho, também pode fazer isso com o MAX ():

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

ROW_NUMBER () requer uma espécie de todas as linhas na sua instrução SELECT, enquanto MAX não. Deve acelerar drasticamente sua consulta.


2
Os problemas de desempenho com ROW_NUMBER () não podem ser resolvidos com a indexação adequada? (Eu sinto que deve ser feito de qualquer maneira)
Kristoffer L

8
Com datetime, você não pode garantir que duas entradas não serão adicionadas na mesma data e hora. A precisão não é alta o suficiente.
TamusJRoyce

+1 por simplicidade. @TamusJRoyce está certo. A respeito? 'select * from DocumentStatusLog D em que ID = (selecione ID em DocumentsStatusLog em que D.DocumentID = ordem do DocumentID por DateCreated DESC limite 1);'
cibercitizen1

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

Definitivamente, há casos em que isso superará row_number()mesmo com a indexação adequada. Acho isso especialmente valioso em cenários de auto-junção. Porém, o que você deve saber é que esse método geralmente gera um número maior de leituras lógicas e contagens de varredura, apesar de relatar um baixo custo de subárvore. Você precisará pesar os custos / benefícios em seu caso específico para determinar se é realmente melhor.
Pimbrouwers

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

Qual servidor de banco de dados? Este código não funciona em todos eles.

Em relação à segunda metade da sua pergunta, parece-me razoável incluir o status como uma coluna. Você pode sair DocumentStatusLogscomo um log, mas ainda assim armazenar as informações mais recentes na tabela principal.

BTW, se você já possui a DateCreatedcoluna na tabela Documentos, você pode simplesmente ingressar DocumentStatusLogsusando isso (desde que DateCreatedseja exclusivo DocumentStatusLogs).

Edit: MsSQL não suporta USING, então mude para:

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

5
A pista estava no título: MSSQL. O SQL Server não tem USING, mas a ideia é boa.
gbn 27/07

7
@gbn Os moderadores estúpidos geralmente excluem palavras-chave importantes dos títulos, como fizeram aqui. Tornando muito difícil encontrar as respostas corretas nos resultados de pesquisa ou no Google.
NickG

2
Basta salientar que essa "solução" ainda pode lhe dar vários registros se você tiver um empate nomax(DateCreated)
MoonKnight

12

Essa é uma das perguntas mais facilmente encontradas sobre o assunto, então eu queria dar uma resposta moderna para ele (tanto para minha referência quanto para ajudar outras pessoas). Usando first_valuee overvocê pode fazer um breve trabalho na consulta acima:

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

Isso deve funcionar no Sql Server 2008 e superior. First_valuepode ser pensado como uma maneira de realizar Select Top 1ao usar uma overcláusula. Overpermite agrupar na lista de seleção e, em vez de escrever subconsultas aninhadas (como muitas das respostas existentes), isso é feito de maneira mais legível. Espero que isto ajude.


2
Isso não funciona no SQL Server 2008 R2. Acho que first_value foi introduzido em 2012!
Ufo

1
Muito rápido! Eu estava usando a solução Cross Apply oferecida pelo @dpp, mas essa é muito mais rápida.
MattSlay

11

Esse é um tópico bastante antigo, mas achei que eu jogaria meus dois centavos da mesma forma que a resposta aceita não funcionou particularmente bem para mim. Tentei a solução da gbn em um grande conjunto de dados e a achei muito lenta (> 45 segundos em mais de 5 milhões de registros no SQL Server 2012). Observando o plano de execução, é óbvio que o problema é que ele requer uma operação SORT que torna as coisas mais lentas.

Aqui está uma alternativa que levantei da estrutura da entidade que não precisa de operação SORT e faz uma pesquisa de índice não clusterizado. Isso reduz o tempo de execução para <2 segundos no conjunto de registros mencionado acima.

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]

Agora, estou assumindo algo que não está totalmente especificado na pergunta original, mas se o design da tabela for tal que sua coluna de ID seja uma ID de incremento automático e o DateCreated esteja definido para a data atual com cada inserção, sem executar a minha consulta acima, você pode obter um aumento considerável no desempenho da solução da gbn (cerca de metade do tempo de execução) apenas solicitando o ID em vez de o DateCreated, pois isso fornecerá uma ordem de classificação idêntica e é mais rápida.


5

Meu código para selecionar os 1 primeiros de cada grupo

selecione a. * em #DocumentStatusLogs a where 
 criado em (selecione os 1 principais criados em #DocumentStatusLogs b
Onde 
a.documentid = b.documentid
ordenar por desc criado por dados
)

3

Verificando a resposta impressionante e correta de Clint acima:

O desempenho entre as duas consultas abaixo é interessante. 52% sendo o primeiro. E 48% é o segundo. Uma melhoria de 4% no desempenho usando DISTINCT em vez de ORDER BY. Mas ORDER BY tem a vantagem de classificar por várias colunas.

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')

Opção 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])

Opção 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]

M $ 's Management Studio: Após destacar e executar o primeiro bloco, realce as opções 1 e 2, clique com o botão direito do mouse em -> [Exibir plano de execução estimado]. Em seguida, execute a coisa toda para ver os resultados.

Resultados da opção 1:

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

Resultados da opção 2:

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:

Costumo usar o APPLY quando quero que uma associação seja de 1 para (1 de muitas).

Uso um JOIN se quiser que o ingresso seja de um para muitos ou de muitos para muitos.

Evito CTE com ROW_NUMBER (), a menos que precise fazer algo avançado e esteja de acordo com a penalidade de desempenho de janelas.

Também evito subconsultas EXISTS / IN na cláusula WHERE ou ON, pois experimentei isso causando alguns planos de execução terríveis. Mas a milhagem varia. Revise o plano de execução e o desempenho do perfil onde e quando necessário!


3

Esta solução pode ser usada para obter as TOP N linhas mais recentes de cada partição (no exemplo, N é 1 na instrução WHERE e a partição é 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 ;

Se você deseja devolver apenas o pedido recente de documento por DateCreated, ele retornará apenas o documento principal 1 por DocumentID


2

CROSS APPLYfoi o método que usei para minha solução, pois funcionou para mim e para as necessidades de meus clientes. E pelo que li, deve fornecer o melhor desempenho geral, caso o banco de dados cresça substancialmente.


1

Aqui estão três abordagens separadas para o problema em mãos, juntamente com as melhores opções de indexação para cada uma dessas consultas (tente você mesmo os índices e veja a leitura lógica, o tempo decorrido, o plano de execução. Forneci as sugestões da minha experiência em consultas sem executar para esse problema específico).

Abordagem 1 : usando ROW_NUMBER (). Se o índice rowstore não conseguir melhorar o desempenho, você poderá experimentar o índice columnstore não clusterizado / em cluster como para consultas com agregação e agrupamento e para tabelas ordenadas por colunas diferentes o tempo todo, o índice columnstore geralmente é a melhor escolha.

;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;

Abordagem 2 : usando FIRST_VALUE. Se o índice rowstore não conseguir melhorar o desempenho, você poderá experimentar o índice columnstore não clusterizado / em cluster como para consultas com agregação e agrupamento e para tabelas ordenadas por colunas diferentes o tempo todo, o índice columnstore geralmente é a melhor escolha.

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;

Abordagem 3 : Usando CROSS APPLY. A criação do índice rowstore na tabela DocumentStatusLogs que cobre as colunas usadas na consulta deve ser suficiente para cobrir a consulta sem a necessidade de um índice columnstore.

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

Eu acredito que isso pode ser feito assim. Isso pode precisar de alguns ajustes, mas você pode apenas selecionar o máximo no grupo.

Essas respostas são um exagero.

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

Nos cenários em que você deseja evitar o uso de row_count (), você também pode usar uma junção esquerda:

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 o esquema de exemplo, você também pode usar uma "não na subconsulta", que geralmente compila a mesma saída que a junção esquerda:

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)

Observe que o padrão de subconsulta não funcionaria se a tabela não tivesse pelo menos uma chave / restrição / índice exclusivos de coluna única; nesse caso, a chave primária "Id".

Ambas as consultas tendem a ser mais "caras" que a consulta row_count () (conforme medida pelo Query Analyzer). No entanto, você pode encontrar cenários em que eles retornam resultados mais rapidamente ou ativam outras otimizações.


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

Tente o seguinte:

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]

Você sempre deve descrever sua instrução SQL como ela funcionará e resolverá a consulta do OP.
Suraj Kumar

-1

Este é o TSQL mais baunilha que eu posso criar

    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

Infelizmente, o MaxDate não é exclusivo. É possível inserir duas datas no mesmo horário exato. Portanto, isso pode resultar em duplicatas por grupo. Você pode, no entanto, usar uma coluna de identidade ou GUID. A Coluna de identidade forneceria a mais recente que foi inserida (calc de identidade padrão sendo usado, 1 ... x etapa 1).
TamusJRoyce

Bem, eu meio que concordo, mas o autor pediu a entrada mais recente - que, a menos que você inclua uma coluna de identidade com incremento automático, dois itens adicionados exatamente ao mesmo tempo são igualmente 'os mais recentes'
rich s

O registro mais recente será um registro. Então sim. Você precisa considerar a coluna de identidade de incremento automático.
TamusJRoyce

-2

É verificado no SQLite que você pode usar a seguinte consulta simples com GROUP BY

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

Aqui, o MAX ajuda a obter o máximo de DateCreated FROM de cada grupo.

Mas parece que o MYSQL não associa * -columns ao valor de max DateCreated :(

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.