Obtenez la première rangée de chaque groupe

530

J'ai une table dont je veux obtenir la dernière entrée pour chaque groupe. Voici le tableau:

DocumentStatusLogs Table

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

Le tableau sera regroupé par DocumentID et trié par DateCreatedordre décroissant. Pour chacun DocumentID, je veux obtenir le dernier statut.

Ma sortie préférée:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • Existe-t-il une fonction d'agrégation pour obtenir uniquement le sommet de chaque groupe? Voir le pseudo-code GetOnlyTheTopci - dessous:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
  • Si une telle fonction n'existe pas, existe-t-il un moyen d'obtenir la sortie souhaitée?

  • Ou en premier lieu, cela pourrait-il être causé par une base de données non normalisée? Je pense, puisque ce que je recherche, c'est juste une rangée, si celastatus également être situé dans la table parent?

Veuillez consulter le tableau parent pour plus d'informations:

DocumentsTableau actuel

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

La table parent doit-elle être ainsi pour que je puisse facilement accéder à son état?

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

MISE À JOUR Je viens d'apprendre à utiliser "appliquer", ce qui facilite la résolution de ces problèmes.

dpp
la source
2
Pour une discussion plus détaillée et une comparaison des solutions possibles, je recommande de lire la question similaire sur dba.se: Récupérer n lignes par groupe .
Vladimir Baranov
J'ai regardé le post et l'ai essayé. L'utilisation du groupe par StoreID a généré une erreur.
UltraJ

Réponses:

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 vous vous attendez à 2 entrées par jour, cela en choisira une arbitrairement. Pour obtenir les deux entrées pour une journée, utilisez plutôt DENSE_RANK

Quant à normalisé ou non, cela dépend si vous souhaitez:

  • maintenir le statut à 2 endroits
  • conserver l'historique des statuts
  • ...

En l'état, vous conservez l'historique des statuts. Si vous souhaitez également le dernier état dans la table parent (qui est la dénormalisation), vous aurez besoin d'un déclencheur pour maintenir le "statut" dans le parent. ou supprimez cette table d'historique d'état.

gbn
la source
5
Et ... c'est quoi Partition By? Withest nouveau pour moi aussi :( J'utilise quand même mssql 2005.
dpp
6
@domanokz: Partition By réinitialise le décompte. Donc, dans ce cas, il dit de compter par DocumentID
gbn
1
Hm, je m'inquiète pour les performances, je vais interroger des millions de lignes. SELECT * FROM (SELECT ...) affecte-t-il les performances? En outre, existe-t-il ROW_NUMBERune sorte de sous-requête pour chaque ligne?
dpp
1
@domanokz: non, ce n'est pas une sous-requête. Si vous avez des index corrects, alors des millions ne devraient pas être un problème. Il n'y a de toute façon que 2 méthodes basées sur les ensembles: celle-ci et l'agrégat (la solution d'Ariel). Alors essayez les deux ...
gbn
1
@domanokz: remplacez simplement ORDER BY DateCreated DESC par ORDER BY ID DESC
gbn
184

Je viens d'apprendre à utiliser cross apply. Voici comment l'utiliser dans ce scénario:

 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
dpp
la source
2
Cela ne fait en fait aucune différence, car la question est toujours réglée.
dpp
19
Je viens de publier les résultats de mes tests de synchronisation par rapport à toutes les solutions proposées et la vôtre est arrivée en tête. Vous donnant un vote positif :-)
John Fairbanks
3
+1 pour une énorme amélioration de la vitesse. C'est beaucoup plus rapide qu'une fonction de fenêtrage telle que ROW_NUMBER (). Ce serait bien si SQL reconnaissait ROW_NUMBER () = 1 comme des requêtes et les optimisait dans Applies. Remarque: J'ai utilisé OUTER APPLY car j'avais besoin de résultats, même s'ils n'existaient pas dans l'application.
TamusJRoyce
8
@TamusJRoyce, vous ne pouvez pas extrapoler cela simplement parce que c'était plus rapide une fois que c'est toujours le cas. Ça dépend. Comme décrit ici sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith
2
Mon commentaire concerne le fait d'avoir plusieurs lignes et de ne désirer qu'une de ces plusieurs lignes par groupe. Les jointures sont pour quand vous en voulez un à plusieurs. Les candidatures s'appliquent lorsque vous en avez un à plusieurs, mais que vous souhaitez filtrer tout sauf un à un. Scénario: pour 100 membres, donnez-moi chacun leur meilleur numéro de téléphone (où chacun pourrait avoir plusieurs numéros). C'est là que Apply excelle. Moins de lectures = moins d'accès au disque = meilleures performances. Étant donné mon expérience avec des bases de données non normalisées mal conçues.
TamusJRoyce
53

J'ai fait quelques synchronisations sur les différentes recommandations ici, et les résultats dépendent vraiment de la taille de la table impliquée, mais la solution la plus cohérente utilise le CROSS APPLY Ces tests ont été exécutés contre SQL Server 2008-R2, en utilisant une table avec 6 500 enregistrements et un autre (schéma identique) avec 137 millions d'enregistrements. Les colonnes interrogées font partie de la clé primaire de la table et la largeur de la table est très petite (environ 30 octets). Les heures sont signalées par SQL Server à partir du plan d'exécution réel.

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

Je pense que la chose vraiment étonnante était la cohérence du temps pour le CROSS APPLY quel que soit le nombre de lignes impliquées.

John Fairbanks
la source
8
Tout dépend de la distribution des données et des index disponibles. Il a été discuté à de grandes longueurs sur dba.se .
Vladimir Baranov
48

Je sais que c'est un vieux fil de discussion, mais les TOP 1 WITH TIESsolutions sont assez agréables et pourraient être utiles à la lecture des solutions.

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

Pour en savoir plus sur la clause TOP, cliquez ici .

Josh Gilfillan
la source
7
C'est la solution la plus élégante imo
George Menoutis
1
d'accord - cela reproduit le mieux ce qui est très facile à faire dans d'autres versions de SQL et d'autres langues imo
Chris Umphlett
27

Si vous êtes préoccupé par les performances, vous pouvez également le faire avec MAX ():

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

ROW_NUMBER () nécessite une sorte de toutes les lignes de votre instruction SELECT, contrairement à MAX. Devrait accélérer considérablement votre requête.

Daniel Cotter
la source
2
Les problèmes de performances avec ROW_NUMBER () ne peuvent-ils pas être résolus avec une indexation appropriée? (Je pense que cela devrait être fait de toute façon)
Kristoffer L
8
Avec datetime, vous ne pouvez pas garantir que deux entrées ne seront pas ajoutées à la même date et heure. La précision n'est pas assez élevée.
TamusJRoyce
+1 pour plus de simplicité. @TamusJRoyce a raison. Qu'en est-il de? 'select * from DocumentStatusLog D where ID = (select ID from DocumentsStatusLog where D.DocumentID = DocumentID order by DateCreated DESC limit 1);'
cibercitizen1
SELECT * FROM EventScheduleTbl D WHERE DatesPicked = (SELECT top 1 min (DatesPicked) FROM EventScheduleTbl WHERE EventIDf = D.EventIDf et DatesPicked> = convert (date, getdate ()))
Arun Prasad ES
Il y a certainement des cas où cela surperformera row_number()même avec une indexation appropriée. Je le trouve particulièrement utile dans les scénarios d'auto-jointure. La chose à savoir cependant, c'est que cette méthode produira souvent un nombre plus élevé de lectures logiques et de décomptes, malgré le rapport d'un faible coût de sous-arbre. Vous devrez peser les coûts / avantages dans votre cas particulier pour déterminer s'il est réellement meilleur.
pimbrouwers
26
SELECT * FROM
DocumentStatusLogs JOIN (
  SELECT DocumentID, MAX(DateCreated) DateCreated
  FROM DocumentStatusLogs
  GROUP BY DocumentID
  ) max_date USING (DocumentID, DateCreated)

Quel serveur de base de données? Ce code ne fonctionne pas sur tous.

En ce qui concerne la seconde moitié de votre question, il me semble raisonnable d'inclure le statut en colonne. Tu peux partirDocumentStatusLogs un journal, mais toujours stocker les dernières informations dans le tableau principal.

BTW, si vous avez déjà la DateCreatedcolonne dans le tableau Documents, vous pouvez simplement rejoindre en DocumentStatusLogsutilisant cela (tant qu'il DateCreatedest unique dans DocumentStatusLogs).

Edit: MsSQL ne prend pas en charge USING, alors changez-le en:

ON DocumentStatusLogs.DocumentID = max_date.DocumentID AND DocumentStatusLogs.DateCreated = max_date.DateCreated
Ariel
la source
5
L'indice était dans le titre: MSSQL. SQL Server n'a pas USING mais l'idée est OK.
gbn
7
@gbn Les modérateurs stupides suppriment généralement les mots clés importants des titres, comme ils l'ont fait ici. Il est donc très difficile de trouver les bonnes réponses dans les résultats de recherche ou sur Google.
NickG
2
Jus pour souligner que cette "solution" peut encore vous donner plusieurs records si vous avez une égalité sur lemax(DateCreated)
MoonKnight
12

C'est l'une des questions les plus faciles à trouver sur le sujet, je voulais donc donner une réponse moderne à cela (à la fois pour ma référence et pour aider les autres). En utilisant first_valueet overvous pouvez utiliser rapidement la requête ci-dessus:

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

Cela devrait fonctionner dans Sql Server 2008 et versions ultérieures. First_valuepeut être considéré comme un moyen d'accomplir Select Top 1lors de l'utilisation d'une overclause. Overpermet le regroupement dans la liste de sélection, donc au lieu d'écrire des sous-requêtes imbriquées (comme le font la plupart des réponses existantes), cela le fait de manière plus lisible. J'espère que cela t'aides.

Randall
la source
2
Cela ne fonctionne pas dans SQL Server 2008 R2. Je pense que first_value a été introduit en 2012!
ovni le
1
Très vite! J'utilisais la solution Cross Apply offerte par @dpp, mais celle-ci est plus rapide.
MattSlay
11

C'est un fil assez ancien, mais je pensais que je mettrais mes deux cents de la même manière que la réponse acceptée ne fonctionnait pas particulièrement bien pour moi. J'ai essayé la solution de gbn sur un grand ensemble de données et l'ai trouvée terriblement lente (> 45 secondes sur plus de 5 millions d'enregistrements dans SQL Server 2012). En regardant le plan d'exécution, il est évident que le problème est qu'il nécessite une opération SORT qui ralentit considérablement les choses.

Voici une alternative que j'ai retirée du cadre d'entité qui n'a besoin d'aucune opération SORT et effectue une recherche d'index NON clusterisé. Cela réduit le temps d'exécution à <2 secondes sur le jeu d'enregistrements susmentionné.

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]

Maintenant, je suppose que quelque chose qui n'est pas entièrement spécifié dans la question d'origine, mais si la conception de votre table est telle que votre colonne ID est un ID d'incrémentation automatique et que DateCreated est défini sur la date actuelle à chaque insertion, puis même sans exécuter ma requête ci-dessus, vous pouvez réellement obtenir une augmentation considérable des performances de la solution de gbn (environ la moitié du temps d'exécution) simplement en commandant sur ID au lieu de commander sur DateCreated car cela fournira un ordre de tri identique et c'est un tri plus rapide.

Clint
la source
5

Mon code pour sélectionner le top 1 de chaque groupe

sélectionnez a. * dans #DocumentStatusLogs a where 
 créé dans (sélectionnez les 1 premières données créées à partir de #DocumentStatusLogs b
où 
a.documentid = b.documentid
ordre par descecréé
)
AnuPrakash
la source
3

Vérification de la réponse impressionnante et correcte de Clint ci-dessus:

Les performances entre les deux requêtes ci-dessous sont intéressantes. 52% étant le premier. Et 48% étant le deuxième. Une amélioration de 4% des performances en utilisant DISTINCT au lieu de COMMANDER PAR. Mais ORDER BY a l'avantage de trier par plusieurs colonnes.

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

Option 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])

Option 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 $: Après avoir mis en surbrillance et exécuté le premier bloc, mettez en surbrillance les options 1 et 2, clic droit -> [Afficher le plan d'exécution estimé]. Ensuite, exécutez le tout pour voir les résultats.

Résultats de l'option 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

Résultats de l'option 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

Remarque:

J'ai tendance à utiliser APPLY lorsque je souhaite qu'une jointure soit de 1 à (1 parmi plusieurs).

J'utilise un JOIN si je veux que la jointure soit 1-à-plusieurs ou plusieurs-à-plusieurs.

J'évite CTE avec ROW_NUMBER () sauf si j'ai besoin de faire quelque chose de avancé et je suis d'accord avec la pénalité de performance de fenêtrage.

J'évite également les sous-requêtes EXISTS / IN dans la clause WHERE ou ON, car cela m'a causé des plans d'exécution terribles. Mais le kilométrage varie. Passez en revue le plan d'exécution et les performances du profil où et quand vous en avez besoin!

TamusJRoyce
la source
3

Cette solution peut être utilisée pour obtenir les TOP N lignes les plus récentes pour chaque partition (dans l'exemple, N est 1 dans l'instruction WHERE et la partition est 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;
praveen
la source
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 vous souhaitez renvoyer uniquement l'ordre des documents récents par DateCreated, il ne renverra que le premier document par DocumentID

cho
la source
2

CROSS APPLYétait la méthode que j'ai utilisée pour ma solution, car elle fonctionnait pour moi et pour les besoins de mes clients. Et d'après ce que j'ai lu, devrait fournir les meilleures performances globales si leur base de données se développait considérablement.

Tony Davis-Coyle
la source
1

Voici 3 approches distinctes du problème en cours ainsi que les meilleurs choix d'indexation pour chacune de ces requêtes (veuillez essayer les index vous-même et voir la lecture logique, le temps écoulé, le plan d'exécution. J'ai fourni les suggestions de mon expérience sur de telles requêtes sans exécuter pour ce problème spécifique).

Approche 1 : utilisation de ROW_NUMBER (). Si l'index rowstore n'est pas en mesure d'améliorer les performances, vous pouvez essayer l'index columnstore non clusterisé / en cluster comme pour les requêtes avec agrégation et regroupement et pour les tables qui sont classées par différentes colonnes à tout moment, l'index columnstore est généralement le meilleur choix.

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

Approche 2 : utilisation de FIRST_VALUE. Si l'index du magasin de lignes n'est pas en mesure d'améliorer les performances, vous pouvez essayer un index de magasin de colonnes non clusterisé / en cluster comme pour les requêtes avec agrégation et regroupement et pour les tables qui sont toujours triées dans différentes colonnes, l'index de magasin de colonnes est généralement le meilleur choix.

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;

Approche 3 : Utilisation de CROSS APPLY. La création d'un index de magasin de lignes sur la table DocumentStatusLogs couvrant les colonnes utilisées dans la requête devrait être suffisante pour couvrir la requête sans avoir besoin d'un index de magasin de colonnes.

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;
san
la source
1

Je crois que cela peut être fait comme ça. Cela pourrait nécessiter quelques ajustements, mais vous pouvez simplement sélectionner le maximum dans le groupe.

Ces réponses sont exagérées ..

SELECT
  d.DocumentID,
  MAX(d.Status),
  MAX(d1.DateCreated)
FROM DocumentStatusLogs d, DocumentStatusLogs d1
USING(DocumentID)
GROUP BY d.DocumentID
ORDER BY DateCreated DESC
Statistiques d'apprentissage par l'exemple
la source
0

Dans les scénarios où vous souhaitez éviter d'utiliser row_count (), vous pouvez également utiliser une jointure gauche:

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 

Pour l'exemple de schéma, vous pouvez également utiliser un "pas dans la sous-requête", qui compile généralement vers la même sortie que la jointure gauche:

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)

Notez que le modèle de sous-requête ne fonctionnerait pas si la table n'avait pas au moins une clé / contrainte / index unique à une seule colonne, dans ce cas la clé primaire "Id".

Ces deux requêtes ont tendance à être plus "coûteuses" que la requête row_count () (telle que mesurée par l'Analyseur de requêtes). Cependant, vous pouvez rencontrer des scénarios où ils renvoient des résultats plus rapidement ou activent d'autres optimisations.

BitwiseMan
la source
0
SELECT documentid, 
       status, 
       datecreated 
FROM   documentstatuslogs dlogs 
WHERE  status = (SELECT status 
                 FROM   documentstatuslogs 
                 WHERE  documentid = dlogs.documentid 
                 ORDER  BY datecreated DESC 
                 LIMIT  1) 
Koshal Garg
la source
0

Essaye ça:

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]
gng
la source
Vous devez toujours décrire votre instruction SQL comment elle fonctionnera et résoudre la requête de l'OP.
Suraj Kumar
-1

C'est le TSQL le plus vanille que je puisse trouver

    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
riche s
la source
Malheureusement, MaxDate n'est pas unique. Il est possible de saisir deux dates en même temps. Cela peut donc entraîner des doublons par groupe. Vous pouvez cependant utiliser une colonne d'identité ou un GUID. La colonne d'identité vous obtiendrait la dernière entrée saisie (calcul d'identité par défaut utilisé, 1 ... x étape 1).
TamusJRoyce
Eh bien, je suis d'accord, mais l'auteur a demandé la dernière entrée - ce qui, à moins que vous n'incluiez une colonne d'identité à incrémentation automatique, signifie que deux éléments ajoutés exactement en même temps sont également `` les plus récents ''
riche s
Le dernier record sera un record. Donc oui. Vous devez prendre en compte la colonne d'identité à incrémentation automatique.
TamusJRoyce
-2

Il est vérifié dans SQLite que vous pouvez utiliser la requête simple suivante avec GROUP BY

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

Ici, MAX aide à obtenir la DateCreated maximale chaque groupe.

Mais il semble que MYSQL n'associe pas * -columns à la valeur de max DateCreated :(

malex
la source