Holen Sie sich die oberste 1 Reihe jeder Gruppe


527

Ich habe eine Tabelle, an der ich für jede Gruppe den neuesten Eintrag erhalten möchte. Hier ist die Tabelle:

DocumentStatusLogs Tabelle

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

Die Tabelle wird DocumentIDnach DateCreatedabsteigender Reihenfolge gruppiert und sortiert . Für jeden DocumentIDmöchte ich den neuesten Status erhalten.

Meine bevorzugte Ausgabe:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • Gibt es eine Aggregatfunktion, um von jeder Gruppe nur die Spitze zu erhalten? Siehe Pseudocode GetOnlyTheTopunten:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
  • Wenn eine solche Funktion nicht vorhanden ist, kann ich auf irgendeine Weise die gewünschte Ausgabe erzielen?

  • Oder könnte dies in erster Linie durch eine nicht normalisierte Datenbank verursacht werden? Ich denke, da ich nur eine Zeile suche, sollte sich diese statusauch in der übergeordneten Tabelle befinden?

Weitere Informationen finden Sie in der übergeordneten Tabelle:

Aktuelle DocumentsTabelle

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

Sollte die übergeordnete Tabelle so sein, damit ich leicht auf ihren Status zugreifen kann?

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

UPDATE Ich habe gerade gelernt, wie man "anwenden" verwendet, was es einfacher macht, solche Probleme anzugehen.


2
Für eine detailliertere Diskussion und einen Vergleich möglicher Lösungen empfehle ich, die ähnliche Frage auf dba.se zu lesen: Abrufen von n Zeilen pro Gruppe .
Vladimir Baranov

Ich schaute auf die Post und versuchte es. Die Verwendung von group by StoreID hat einen Fehler generiert.
UltraJ

Antworten:


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

Wenn Sie 2 Einträge pro Tag erwarten, wird dies willkürlich einen auswählen. Verwenden Sie stattdessen DENSE_RANK, um beide Einträge für einen Tag abzurufen

Ob normalisiert oder nicht, es hängt davon ab, ob Sie:

  • Status an 2 Stellen beibehalten
  • Statusverlauf beibehalten
  • ...

So wie es aussieht, behalten Sie den Statusverlauf bei. Wenn Sie auch den neuesten Status in der übergeordneten Tabelle wünschen (was Denormalisierung ist), benötigen Sie einen Auslöser, um den "Status" im übergeordneten Element beizubehalten. oder löschen Sie diese Statusverlaufstabelle.


5
Und ... was ist Partition By? Withist auch neu für mich :( Ich benutze sowieso mssql 2005.
dpp

6
@domanokz: Partition By setzt die Anzahl zurück. In diesem Fall heißt es also, pro DocumentID zu zählen
gbn

1
Hm, ich mache mir Sorgen um die Leistung, ich werde Millionen von Zeilen abfragen. Beeinflusst SELECT * FROM (SELECT ...) die Leistung? Auch ist ROW_NUMBEReine Art einer Unterabfrage für jede Zeile?
dpp

1
@domanokz: nein, es ist keine Unterabfrage. Wenn Sie korrekte Indizes haben, sollten Millionen kein Problem sein. Es gibt sowieso nur zwei satzbasierte Möglichkeiten: diese und das Aggregat (Ariels Lösung). Also probieren Sie beide aus ...
gbn

1
@domanokz: Ändern Sie einfach ORDER BY DateCreated DESC in ORDER BY ID DESC
gbn

184

Ich habe gerade gelernt, wie man es benutzt cross apply. So verwenden Sie es in diesem Szenario:

 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
Das macht eigentlich keinen Unterschied, da das Problem immer noch angesprochen wird.
dpp

19
Ich habe gerade die Ergebnisse meiner Timing-Tests für alle vorgeschlagenen Lösungen veröffentlicht, und Ihre haben die Nase vorn. Ich gebe dir eine Stimme :-)
John Fairbanks

3
+1 für enorme Geschwindigkeitsverbesserung. Dies ist viel schneller als eine Fensterfunktion wie ROW_NUMBER (). Es wäre schön, wenn SQL ROW_NUMBER () = 1 wie Abfragen erkennen und in Applies optimieren würde. Hinweis: Ich habe OUTER APPLY verwendet, da ich Ergebnisse benötigte, auch wenn diese in der Anwendung nicht vorhanden waren.
TamusJRoyce

8
@TamusJRoyce das kann man nicht extrapolieren, nur weil es schneller war, wenn dies immer der Fall ist. Es hängt davon ab, ob. Wie hier beschrieben sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
In meinem Kommentar geht es darum, mehrere Zeilen zu haben und nur eine dieser mehreren Zeilen pro Gruppe zu wünschen. Joins sind für den Fall gedacht, dass Sie eins zu viele möchten. Dies gilt, wenn Sie eins zu viele haben, aber alle außer eins zu eins herausfiltern möchten. Szenario: Geben Sie mir für 100 Mitglieder jeweils ihre beste Telefonnummer (wobei jede mehrere Nummern haben könnte). Hier zeichnet sich Apply aus. Weniger Lesevorgänge = weniger Festplattenzugriff = bessere Leistung. Aufgrund meiner Erfahrung mit schlecht gestalteten nicht normalisierten Datenbanken.
TamusJRoyce

53

Ich habe einige Zeitangaben für die verschiedenen Empfehlungen hier gemacht, und die Ergebnisse hängen wirklich von der Größe der betreffenden Tabelle ab. Die konsistenteste Lösung ist jedoch die Verwendung der CROSS APPLY. Diese Tests wurden mit einer Tabelle mit SQL Server 2008-R2 ausgeführt 6.500 Datensätze und ein weiteres (identisches Schema) mit 137 Millionen Datensätzen. Die abgefragten Spalten sind Teil des Primärschlüssels in der Tabelle, und die Tabellenbreite ist sehr klein (ca. 30 Byte). Die Zeiten werden von SQL Server aus dem tatsächlichen Ausführungsplan gemeldet.

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

Ich denke, das wirklich Erstaunliche war, wie konstant die Zeit für die CROSS APPLY war, unabhängig von der Anzahl der beteiligten Zeilen.


8
Es hängt alles von der Datenverteilung und den verfügbaren Indizes ab. Es wurde ausführlich auf dba.se diskutiert .
Vladimir Baranov

48

Ich weiß, dass dies ein alter Thread ist, aber die TOP 1 WITH TIESLösungen sind sehr nett und könnten hilfreich sein, um die Lösungen durchzulesen.

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

Mehr zur TOP-Klausel finden Sie hier .


7
Dies ist die eleganteste Lösung imo
George Menoutis

1
vereinbart - dies repliziert am besten, was in anderen Versionen von SQL und anderen Sprachen imo sehr einfach zu tun ist
Chris Umphlett

27

Wenn Sie sich Sorgen um die Leistung machen, können Sie dies auch mit MAX () tun:

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

ROW_NUMBER () erfordert eine Art aller Zeilen in Ihrer SELECT-Anweisung, MAX nicht. Sollte Ihre Anfrage drastisch beschleunigen.


2
Können Leistungsprobleme mit ROW_NUMBER () nicht mit der richtigen Indizierung behoben werden? (Ich denke, das sollte sowieso getan werden)
Kristoffer L

8
Mit datetime können Sie nicht garantieren, dass nicht zwei Einträge am selben Datum und zur selben Uhrzeit hinzugefügt werden. Präzision ist nicht hoch genug.
TamusJRoyce

Der Einfachheit halber +1. @ TamusJRoyce ist richtig. Wie wäre es mit? 'Wählen Sie * aus DocumentStatusLog D aus, wobei ID = (wählen Sie ID aus DocumentsStatusLog aus, wobei D.DocumentID = DocumentID-Reihenfolge nach DateCreated DESC-Limit 1);'
Cibercitizen1

SELECT * FROM EventScheduleTbl D WHERE DatesPicked = (SELECT top 1 min (DatesPicked) FROM EventScheduleTbl WHERE EventIDf = D.EventIDf und DatesPicked> = konvertieren (Datum, getdate ()))
Arun Prasad ES

Es gibt definitiv Fälle, in denen dies row_number()selbst bei ordnungsgemäßer Indizierung eine Outperformance erzielt. Ich finde es besonders wertvoll in Self-Join-Szenarien. Zu beachten ist jedoch, dass diese Methode trotz der Angabe niedriger Teilbaumkosten häufig eine höhere Anzahl von logischen Lesevorgängen und Scan-Zählungen liefert. Sie müssen die Kosten / Nutzen in Ihrem speziellen Fall abwägen, um festzustellen, ob es tatsächlich besser ist.
Pimbrouwers

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

Welcher Datenbankserver? Dieser Code funktioniert nicht bei allen.

In Bezug auf die zweite Hälfte Ihrer Frage erscheint es mir vernünftig, den Status als Spalte aufzunehmen. Du kannst gehenDocumentStatusLogs als Protokoll , aber dennoch die neuesten Informationen in der Haupttabelle speichern.

Übrigens, wenn Sie die DateCreatedSpalte bereits in der Tabelle "Dokumente" haben, können Sie sich einfach damit verbinden DocumentStatusLogs(sofern dies in DateCreatedeindeutig ist DocumentStatusLogs).

Bearbeiten: MsSQL unterstützt USING nicht. Ändern Sie es daher in:

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

5
Der Hinweis war im Titel: MSSQL. SQL Server hat kein USING, aber die Idee ist in Ordnung.
Gbn

7
@gbn Die dummen Moderatoren löschen normalerweise wichtige Schlüsselwörter aus Titeln, wie sie es hier getan haben. Dies macht es sehr schwierig, die richtigen Antworten in den Suchergebnissen oder bei Google zu finden.
NickG

2
Nur um darauf hinzuweisen, dass diese "Lösung" Ihnen immer noch mehrere Datensätze liefern kann, wenn Sie ein Unentschieden ammax(DateCreated)
MoonKnight

12

Dies ist eine der am leichtesten zu findenden Fragen zu diesem Thema, daher wollte ich eine moderne Antwort darauf geben (sowohl als Referenz als auch um anderen zu helfen). Mit first_valueund können overSie die obige Abfrage kurz bearbeiten:

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

Dies sollte in SQL Server 2008 und höher funktionieren. First_valuekann als eine Möglichkeit angesehen werden, dies Select Top 1bei Verwendung einer overKlausel zu erreichen. OverErmöglicht das Gruppieren in der Auswahlliste. Anstatt verschachtelte Unterabfragen zu schreiben (wie dies bei vielen vorhandenen Antworten der Fall ist), ist dies besser lesbar. Hoffe das hilft.


2
Dies funktioniert in SQL Server 2008 R2 nicht. Ich denke, first_value wurde 2012 eingeführt!
UFO

1
Sehr schnell! Ich habe die von @dpp angebotene Cross Apply-Lösung verwendet, aber diese ist viel schneller.
MattSlay

11

Dies ist ein ziemlich alter Thread, aber ich dachte, ich würde meine zwei Cent genauso einwerfen, wie die akzeptierte Antwort für mich nicht besonders gut funktioniert hat. Ich habe die Lösung von gbn für ein großes Dataset ausprobiert und festgestellt, dass sie sehr langsam ist (> 45 Sekunden bei mehr als 5 Millionen Datensätzen in SQL Server 2012). Wenn man sich den Ausführungsplan ansieht, ist es offensichtlich, dass das Problem darin besteht, dass eine SORT-Operation erforderlich ist, die die Dinge erheblich verlangsamt.

Hier ist eine Alternative, die ich aus dem Entity-Framework entfernt habe, das keine SORT-Operation benötigt und eine NON-Clustered-Index-Suche durchführt. Dies reduziert die Ausführungszeit für den oben genannten Datensatz auf <2 Sekunden.

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]

Jetzt gehe ich von etwas aus, das in der ursprünglichen Frage nicht vollständig angegeben ist. Wenn Ihr Tabellendesign jedoch so ist, dass Ihre ID-Spalte eine ID mit automatischer Inkrementierung ist und DateCreated bei jeder Einfügung auf das aktuelle Datum gesetzt wird, dann sogar Ohne meine obige Abfrage auszuführen, könnten Sie tatsächlich eine beträchtliche Leistungssteigerung für die Lösung von gbn erzielen (etwa die Hälfte der Ausführungszeit), wenn Sie nur nach ID bestellen, anstatt nach DateCreated zu bestellen, da dies eine identische Sortierreihenfolge liefert und eine schnellere Sortierung ist.


5

Mein Code zur Auswahl der Top 1 aus jeder Gruppe

Wählen Sie a. * aus #DocumentStatusLogs a where 
 Datum erstellt in (wählen Sie das Top-1-Datum aus #DocumentStatusLogs aus b
wo 
a.documentid = b.documentid
Bestellung nach Erstellungsdatum absteigend
)

3

Überprüfen von Clints fantastischer und korrekter Antwort von oben:

Die Leistung zwischen den beiden folgenden Abfragen ist interessant. 52% sind die Besten. Und 48% sind die zweiten. Eine 4% ige Leistungsverbesserung mit DISTINCT anstelle von ORDER BY. ORDER BY hat jedoch den Vorteil, nach mehreren Spalten zu sortieren.

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 von M $: Nachdem Sie den ersten Block markiert und ausgeführt haben, markieren Sie Option 1 und Option 2. Klicken Sie mit der rechten Maustaste auf -> [Geschätzten Ausführungsplan anzeigen]. Führen Sie dann das gesamte Objekt aus, um die Ergebnisse anzuzeigen.

Option 1 Ergebnisse:

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

Option 2 Ergebnisse:

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

Hinweis:

Ich neige dazu, APPLY zu verwenden, wenn ein Join 1 zu 1 sein soll (1 von vielen).

Ich verwende einen JOIN, wenn der Join 1 zu viele oder viele zu viele sein soll.

Ich vermeide CTE mit ROW_NUMBER (), es sei denn, ich muss etwas Fortgeschrittenes tun und bin mit der Beeinträchtigung der Fensterleistung einverstanden.

Ich vermeide auch EXISTS / IN-Unterabfragen in der WHERE- oder ON-Klausel, da dies einige schreckliche Ausführungspläne verursacht hat. Der Kilometerstand variiert jedoch. Überprüfen Sie den Ausführungsplan und die Profilleistung, wo und wann immer dies erforderlich ist!


3

Diese Lösung kann verwendet werden, um die TOP N neuesten Zeilen für jede Partition abzurufen (im Beispiel ist N 1 in der WHERE-Anweisung und Partition 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 ;

Wenn Sie nur die letzte Dokumentbestellung von DateCreated zurückgeben möchten, wird nur das Top-1-Dokument von DocumentID zurückgegeben


2

CROSS APPLYwar die Methode, die ich für meine Lösung verwendet habe, da sie für mich und für die Bedürfnisse meiner Kunden funktioniert hat. Und nach dem, was ich gelesen habe, sollte die beste Gesamtleistung erzielt werden, sollte ihre Datenbank erheblich wachsen.


1

Hier sind 3 verschiedene Ansätze für das vorliegende Problem zusammen mit den besten Indizierungsoptionen für jede dieser Abfragen (bitte probieren Sie die Indizes selbst aus und sehen Sie sich den logischen Lesevorgang, die verstrichene Zeit und den Ausführungsplan an. Ich habe die Vorschläge aus meiner Erfahrung mit geliefert solche Abfragen ohne Ausführung für dieses spezielle Problem).

Ansatz 1 : Verwenden von ROW_NUMBER (). Wenn der Rowstore-Index die Leistung nicht verbessern kann, können Sie den nicht gruppierten / gruppierten Columnstore-Index wie für Abfragen mit Aggregation und Gruppierung sowie für Tabellen ausprobieren, die ständig in verschiedenen Spalten sortiert sind. Der Columnstore-Index ist normalerweise die beste Wahl.

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

Ansatz 2 : Verwenden von FIRST_VALUE. Wenn der Rowstore-Index die Leistung nicht verbessern kann, können Sie den nicht gruppierten / gruppierten Columnstore-Index wie für Abfragen mit Aggregation und Gruppierung sowie für Tabellen ausprobieren, die ständig in verschiedenen Spalten sortiert sind. Der Columnstore-Index ist normalerweise die beste Wahl.

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;

Ansatz 3 : CROSS APPLY verwenden. Das Erstellen eines Rowstore-Index für die DocumentStatusLogs-Tabelle, der die in der Abfrage verwendeten Spalten abdeckt, sollte ausreichen, um die Abfrage abzudecken, ohne dass ein Columnstore-Index erforderlich ist.

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

Ich glaube, das kann einfach so gemacht werden. Dies muss möglicherweise angepasst werden, aber Sie können einfach das Maximum aus der Gruppe auswählen.

Diese Antworten sind übertrieben.

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

In Szenarien, in denen Sie die Verwendung von row_count () vermeiden möchten, können Sie auch einen linken Join verwenden:

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 

Für das Beispielschema können Sie auch eine "Nicht in Unterabfrage" verwenden, die im Allgemeinen mit derselben Ausgabe wie der linke Join kompiliert wird:

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)

Beachten Sie, dass das Unterabfragemuster nicht funktionieren würde, wenn die Tabelle nicht mindestens einen einspaltigen eindeutigen Schlüssel / eine Einschränkung / einen Index hätte, in diesem Fall den Primärschlüssel "Id".

Diese beiden Abfragen sind in der Regel "teurer" als die Abfrage row_count () (gemessen mit Query Analyzer). Es kann jedoch vorkommen, dass Szenarien schneller Ergebnisse liefern oder andere Optimierungen ermöglichen.


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

Versuche dies:

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]

Sie sollten Ihre SQL-Anweisung immer beschreiben, wie sie funktioniert, und die Abfrage des OP lösen.
Suraj Kumar

-1

Dies ist die Vanille-TSQL, die ich mir vorstellen kann

    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

Leider ist MaxDate nicht eindeutig. Es ist möglich, zwei Daten genau zur gleichen Zeit einzugeben. Dies kann also zu Duplikaten pro Gruppe führen. Sie können jedoch eine Identitätsspalte oder GUID verwenden. In der Identitätsspalte erhalten Sie die zuletzt eingegebene (verwendete Standardidentitätsberechnung, 1 ... x Schritt 1).
TamusJRoyce

Nun , ich Art von einigen, aber der Autor bat um den neuesten Eintrag - was , wenn Sie ein Auto-Inkrement Identitätsspalte Mittel zwei Elemente exakt zur gleichen Zeit hinzugefügt sind gleich ‚die neuesten‘
reich s

Der letzte Datensatz ist ein Datensatz. Also ja. Sie müssen die Spalte für die automatische Inkrementierung der Identität berücksichtigen.
TamusJRoyce

-2

In SQLite wird überprüft, ob Sie die folgende einfache Abfrage mit GROUP BY verwenden können

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

Hier hilft MAX , das maximale DateCreated FROM jeder Gruppe zu erhalten.

Es scheint jedoch, dass MYSQL keine * -Spalten mit dem Wert von max DateCreated :( verknüpft

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.