Dapatkan 1 baris teratas dari setiap grup


530

Saya memiliki tabel yang ingin saya dapatkan entri terbaru untuk setiap grup. Ini tabelnya:

DocumentStatusLogs Meja

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

Tabel akan dikelompokkan berdasarkan DocumentIDdan diurutkan berdasarkan DateCreatedurutan menurun. Untuk masing-masing DocumentID, saya ingin mendapatkan status terbaru.

Output pilihan saya:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • Apakah ada fungsi agregat untuk mendapatkan yang teratas dari setiap grup? Lihat pseudo-code di GetOnlyTheTopbawah ini:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
  • Jika fungsi seperti itu tidak ada, apakah ada cara saya dapat mencapai output yang saya inginkan?

  • Atau pertama-tama, bisakah ini disebabkan oleh database yang tidak dinormalisasi? Saya berpikir, karena apa yang saya cari hanya satu baris, haruskah itu statusjuga terletak di tabel induk?

Silakan lihat tabel induk untuk informasi lebih lanjut:

DocumentsTabel Saat Ini

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

Haruskah tabel induk seperti ini sehingga saya dapat dengan mudah mengakses statusnya?

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

PEMBARUAN Saya baru belajar cara menggunakan "berlaku" yang membuatnya lebih mudah untuk mengatasi masalah seperti itu.


2
Untuk diskusi yang lebih rinci dan perbandingan solusi yang mungkin saya sarankan untuk membaca pertanyaan serupa di dba.se: Mengambil n baris per grup .
Vladimir Baranov

Saya melihat posting dan mencobanya. Menggunakan grup oleh StoreID menghasilkan kesalahan.
UltraJ

Jawaban:


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

Jika Anda mengharapkan 2 entri per hari, maka ini akan secara sewenang-wenang memilih satu entri. Untuk mendapatkan kedua entri selama sehari, gunakan DENSE_RANK sebagai gantinya

Adapun dinormalisasi atau tidak, itu tergantung jika Anda ingin:

  • pertahankan status di 2 tempat
  • melestarikan riwayat status
  • ...

Seperti berdiri, Anda menyimpan riwayat status. Jika Anda menginginkan status terbaru di tabel induk juga (yang merupakan denormalisasi), Anda perlu pemicu untuk mempertahankan "status" di induk. atau jatuhkan tabel riwayat status ini.


5
Dan ... Apa itu Partition By? Withjuga baru bagi saya :( Saya menggunakan mssql 2005.
dpp

6
@domanokz: Partition By me-reset hitungan. Jadi dalam hal ini, ia mengatakan untuk menghitung per DocumentID
gbn

1
Hm, saya khawatir dengan kinerjanya, saya akan menanyakan jutaan baris. Apakah SELECT * FROM (SELECT ...) memengaruhi kinerja? JugaROW_NUMBER ada semacam subquery untuk setiap baris?
dpp

1
@domanokz: tidak, ini bukan subquery. Jika Anda memiliki indeks yang benar maka jutaan seharusnya tidak menjadi masalah. Hanya ada 2 cara yang ditetapkan: ini dan agregat (solusi Ariel). Jadi coba keduanya ...
gbn

1
@domanokz: Ubah saja ORDER DENGAN DESC DateCreated menjadi ORDER DENGAN ID DESC
gbn

184

Saya baru belajar cara menggunakannya cross apply. Berikut cara menggunakannya dalam skenario ini:

 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
Itu sebenarnya tidak ada bedanya karena masalah ini masih ditangani.
dpp

19
Saya baru saja memposting hasil tes waktu saya terhadap semua solusi yang diusulkan dan Anda keluar di atas. Memberi Anda suara :-)
John Fairbanks

3
+1 untuk peningkatan kecepatan besar. Ini jauh lebih cepat daripada fungsi windowing seperti ROW_NUMBER (). Alangkah baiknya jika SQL mengenali ROW_NUMBER () = 1 menyukai kueri dan mengoptimasinya menjadi Berlaku. Catatan: Saya menggunakan OUTER BERLAKU karena saya membutuhkan hasil, bahkan jika mereka tidak ada dalam mendaftar.
TamusJRoyce

8
@TamusJRoyce Anda tidak dapat memperkirakan itu hanya karena lebih cepat setelah ini selalu terjadi. Tergantung. Seperti dijelaskan di sini sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
Komentar saya adalah memiliki beberapa baris, dan hanya menginginkan satu dari beberapa baris per grup. Bergabung adalah untuk saat Anda menginginkan satu untuk banyak. Berlaku berlaku saat Anda memiliki satu hingga banyak, tetapi ingin memfilter semua kecuali satu ke satu. Skenario: Untuk 100 anggota, beri saya masing-masing nomor telepon terbaik mereka (di mana masing-masing dapat memiliki beberapa nomor). Di sinilah Terapkan unggul. Kurang membaca = lebih sedikit akses disk = kinerja yang lebih baik. Mengingat pengalaman saya dengan database non-normal yang dirancang dengan buruk.
TamusJRoyce

53

Saya telah melakukan beberapa pengaturan waktu atas berbagai rekomendasi di sini, dan hasilnya benar-benar tergantung pada ukuran tabel yang terlibat, tetapi solusi yang paling konsisten adalah menggunakan CROSS BERLAKU Tes ini dijalankan terhadap SQL Server 2008-R2, menggunakan tabel dengan 6.500 catatan, dan satu lagi (skema identik) dengan 137 juta catatan. Kolom yang dipertanyakan adalah bagian dari kunci utama pada tabel, dan lebar tabel sangat kecil (sekitar 30 byte). Waktu dilaporkan oleh SQL Server dari rencana eksekusi yang sebenarnya.

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

Saya pikir hal yang sangat menakjubkan adalah seberapa konsisten waktu untuk CROSS BERLAKU terlepas dari jumlah baris yang terlibat.


8
Itu semua tergantung pada distribusi data dan indeks yang tersedia. Hal itu dibahas keras pada dba.se .
Vladimir Baranov

48

Saya tahu ini adalah utas lama tapi TOP 1 WITH TIESsolusinya cukup bagus dan mungkin bisa membantu untuk membaca beberapa solusi.

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

Lebih lanjut tentang klausa TOP dapat ditemukan di sini .


7
Ini adalah solusi paling elegan
George Menoutis

1
setuju - ini mereplikasi terbaik apa yang sangat mudah dilakukan dalam versi lain dari SQL dan bahasa lainnya
Chris Umphlett

27

Jika Anda khawatir tentang kinerja, Anda juga dapat melakukan ini dengan MAX ():

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

ROW_NUMBER () membutuhkan semacam semua baris dalam pernyataan SELECT Anda, sedangkan MAX tidak. Harus mempercepat permintaan Anda secara drastis.


2
Tidak dapat masalah kinerja dengan ROW_NUMBER () ditangani dengan pengindeksan yang tepat? (Saya rasa itu harus dilakukan bagaimanapun juga)
Kristoffer L

8
Dengan datetime, Anda tidak dapat menjamin dua entri tidak akan ditambahkan pada tanggal dan waktu yang sama. Presisi tidak cukup tinggi.
TamusJRoyce

+1 untuk kesederhanaan. @TamusJRoyce benar. Bagaimana dengan? 'pilih * dari DocumentStatusLog D di mana ID = (pilih ID dari DocumentsStatusLog di mana D.DocumentID = Pesanan DocumentID menurut DateCreated DESC batas 1);'
cibercitizen1

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

Pasti ada kasus di mana ini akan mengungguli row_number()bahkan dengan pengindeksan yang tepat. Saya merasa ini sangat berharga dalam skenario self-join. Yang perlu disadari, adalah bahwa metode ini akan sering menghasilkan jumlah pembacaan logis dan pemindaian yang lebih tinggi, meskipun melaporkan biaya subtree yang rendah. Anda harus mempertimbangkan biaya / manfaat dalam kasus khusus Anda untuk menentukan apakah itu sebenarnya lebih baik.
pimbrouwers

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

Server basis data apa? Kode ini tidak berfungsi pada mereka semua.

Mengenai paruh kedua pertanyaan Anda, tampaknya masuk akal bagi saya untuk memasukkan status sebagai kolom. Anda dapat meninggalkan DocumentStatusLogssebagai log, tetapi masih menyimpan info terbaru di tabel utama.

BTW, jika Anda sudah memiliki DateCreatedkolom di tabel Documents, Anda bisa bergabung DocumentStatusLogsmenggunakan itu (asalkan DateCreatedunikDocumentStatusLogs ).

Sunting: MsSQL tidak mendukung USING, jadi ubah ke:

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

5
Petunjuk itu dalam judul: MSSQL. SQL Server tidak memiliki MENGGUNAKAN tetapi idenya OK.
gbn

7
@ gbn Moderator bodoh biasanya menghapus kata kunci penting dari judul, seperti yang telah mereka lakukan di sini. Sehingga sangat sulit untuk menemukan jawaban yang benar di hasil pencarian atau Google.
NickG

2
Jus untuk menunjukkan bahwa "solusi" ini masih dapat memberikan Anda beberapa catatan jika Anda memiliki dasi padamax(DateCreated)
MoonKnight

12

Ini adalah salah satu pertanyaan yang paling mudah ditemukan pada topik, jadi saya ingin memberikan jawaban modern untuk itu (baik untuk referensi saya dan untuk membantu orang lain). Dengan menggunakan first_valuedan overAnda dapat membuat karya pendek dari pertanyaan di atas:

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

Ini harus bekerja di Sql Server 2008 dan lebih tinggi. First_valuedapat dianggap sebagai cara untuk mencapai Select Top 1ketika menggunakan overklausa. Overmemungkinkan pengelompokan dalam daftar pilih jadi alih-alih menulis subqueries bersarang (seperti banyak jawaban yang ada lakukan), ini melakukannya dengan cara yang lebih mudah dibaca. Semoga ini membantu.


2
Ini tidak berfungsi di SQL Server 2008 R2. Saya pikir first_value diperkenalkan pada 2012!
ufo

1
Sangat cepat! Saya menggunakan solusi Cross Apply yang ditawarkan oleh @dpp, tapi yang ini lebih cepat.
MattSlay

11

Ini adalah utas yang cukup lama, tetapi saya pikir saya akan melemparkan dua sen saya sama saja dengan jawaban yang diterima tidak bekerja dengan baik bagi saya. Saya mencoba solusi gbn pada dataset besar dan ternyata sangat lambat (> 45 detik pada 5 juta plus catatan dalam SQL Server 2012). Melihat rencana eksekusi, jelas bahwa masalahnya adalah membutuhkan operasi SORT yang memperlambat segalanya secara signifikan.

Berikut adalah alternatif yang saya angkat dari kerangka entitas yang tidak memerlukan operasi SORT dan melakukan pencarian Indeks NON-Clustered. Ini mengurangi waktu eksekusi menjadi <2 detik pada set catatan yang disebutkan di atas.

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]

Sekarang saya mengasumsikan sesuatu yang tidak sepenuhnya ditentukan dalam pertanyaan asli, tetapi jika desain tabel Anda sedemikian rupa sehingga kolom ID Anda adalah ID kenaikan-otomatis, dan DateCreated diatur ke tanggal saat ini dengan setiap sisipan, maka bahkan tanpa berjalan dengan kueri saya di atas, Anda sebenarnya bisa mendapatkan peningkatan kinerja yang cukup besar untuk solusi gbn (sekitar setengah dari waktu eksekusi) hanya dari memesan pada ID daripada memesan pada DateCreated karena ini akan memberikan urutan pengurutan yang identik dan ini merupakan pengurutan yang lebih cepat.


5

Kode saya untuk memilih 1 teratas dari setiap grup

pilih a. * dari #DocumentStatusLogs a where 
 datecreated in (pilih top 1 datecreated dari #DocumentStatusLogs b
dimana 
a.documentid = b.documentid
memesan oleh descreecreated
)

3

Memverifikasi jawaban Clint yang luar biasa dan benar dari atas:

Kinerja antara dua pertanyaan di bawah ini menarik. 52% menjadi yang teratas. Dan 48% menjadi yang kedua. Peningkatan kinerja 4% menggunakan DISTINCT bukan ORDER BY. Tetapi ORDER BY memiliki keuntungan untuk mengurutkan berdasarkan beberapa kolom.

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

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

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

Studio Manajemen M $: Setelah menyorot dan menjalankan blok pertama, sorot Opsi 1 dan Opsi 2, Klik kanan -> [Tampilkan Perkiraan Rencana Eksekusi]. Kemudian jalankan semuanya untuk melihat hasilnya.

Opsi 1 Hasil:

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

Opsi 2 Hasil:

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

catatan:

Saya cenderung menggunakan BERLAKU ketika saya ingin bergabung menjadi 1-ke-(1 dari banyak).

Saya menggunakan GABUNG jika saya ingin bergabung menjadi 1-ke-banyak, atau banyak-ke-banyak.

Saya menghindari CTE dengan ROW_NUMBER () kecuali saya perlu melakukan sesuatu yang canggih dan saya setuju dengan penalti performa windowing.

Saya juga menghindari subqueries EXISTS / IN dalam klausa WHERE atau ON, karena saya telah mengalami hal ini menyebabkan beberapa rencana eksekusi yang mengerikan. Tetapi jarak tempuh bervariasi. Tinjau rencana eksekusi dan kinerja profil di mana dan kapan diperlukan!


3

Solusi ini dapat digunakan untuk mendapatkan baris TOP N terbaru untuk setiap partisi (dalam contoh, N adalah 1 dalam pernyataan WHERE dan partisi adalah 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 ;

Jika Anda ingin mengembalikan hanya pesanan dokumen terbaru oleh DateCreated, itu hanya akan mengembalikan 1 dokumen teratas oleh DocumentID


2

CROSS APPLYadalah metode yang saya gunakan untuk solusi saya, karena itu bekerja untuk saya, dan untuk kebutuhan klien saya. Dan dari apa yang saya baca, harus memberikan kinerja keseluruhan terbaik jika database mereka tumbuh secara substansial.


1

Berikut adalah 3 pendekatan terpisah untuk masalah yang ada bersama dengan pilihan terbaik pengindeksan untuk masing-masing pertanyaan tersebut (silakan coba sendiri indeksnya dan lihat bacaan logis, waktu yang berlalu, rencana pelaksanaan. Saya telah memberikan saran dari pengalaman saya tentang pertanyaan seperti itu tanpa mengeksekusi untuk masalah khusus ini).

Pendekatan 1 : Menggunakan ROW_NUMBER (). Jika indeks rowstore tidak dapat meningkatkan kinerja, Anda dapat mencoba indeks kolomstore nonclustered / clustered untuk permintaan dengan agregasi dan pengelompokan dan untuk tabel yang dipesan oleh dalam kolom yang berbeda setiap saat, indeks columnstore biasanya merupakan pilihan terbaik.

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

Pendekatan 2 : Menggunakan FIRST_VALUE. Jika indeks rowstore tidak dapat meningkatkan kinerja, Anda dapat mencoba indeks kolomstore nonclustered / clustered untuk permintaan dengan agregasi dan pengelompokan dan untuk tabel yang dipesan oleh dalam kolom yang berbeda setiap saat, indeks columnstore biasanya merupakan pilihan terbaik.

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;

Pendekatan 3 : Menggunakan CROSS APPLY. Membuat indeks rowstore pada tabel DocumentStatusLogs yang mencakup kolom yang digunakan dalam kueri harus cukup untuk mencakup permintaan tanpa perlu indeks 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

Saya percaya ini bisa dilakukan seperti ini. Ini mungkin perlu beberapa penyesuaian tetapi Anda dapat memilih maks dari grup.

Jawaban-jawaban ini berlebihan ...

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

Dalam skenario di mana Anda ingin menghindari menggunakan row_count (), Anda juga dapat menggunakan gabungan kiri:

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 

Untuk skema contoh, Anda juga bisa menggunakan "tidak dalam subquery", yang umumnya mengkompilasi ke output yang sama dengan gabungan kiri:

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)

Catatan, pola subquery tidak akan berfungsi jika tabel tidak memiliki setidaknya satu kolom kunci unik / kendala / indeks, dalam hal ini kunci primer "Id".

Kedua kueri ini cenderung lebih "mahal" daripada kueri row_count () (seperti yang diukur oleh Query Analyzer). Namun, Anda mungkin menemukan skenario di mana mereka mengembalikan hasil lebih cepat atau mengaktifkan optimasi lainnya.


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

Coba ini:

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]

Anda harus selalu mendeskripsikan pernyataan SQL Anda tentang cara kerjanya dan menyelesaikan pertanyaan OP.
Suraj Kumar

-1

Ini adalah TSQL vanilla paling banyak yang bisa saya buat

    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

Sayangnya MaxDate tidak unik. Dimungkinkan untuk memasukkan dua tanggal pada waktu yang bersamaan. Jadi ini dapat menghasilkan duplikat per grup. Namun, Anda dapat menggunakan kolom identitas atau GUID. Kolom Identitas akan memberi Anda yang terbaru yang telah dimasukkan (kal identitas default sedang digunakan, 1 ... x langkah 1).
TamusJRoyce

Yah saya agak setuju, tetapi penulis meminta entri terbaru - yang kecuali Anda memasukkan kolom identitas kenaikan otomatis berarti dua item yang ditambahkan pada waktu yang sama persis sama 'terbaru'
kaya

Rekor terbaru akan menjadi satu catatan. Jadi iya. Anda perlu mempertimbangkan kolom identitas kenaikan-otomatis.
TamusJRoyce

-2

Itu diperiksa dalam SQLite bahwa Anda dapat menggunakan permintaan sederhana berikut dengan GROUP BY

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

Di sini MAX membantu untuk mendapatkan DateCreated maksimum DARI masing-masing kelompok.

Tapi sepertinya MYSQL tidak mengaitkan * -kolom dengan nilai max DateCreated :(

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.