รับ 1 แถวแรกของแต่ละกลุ่ม


527

ฉันมีตารางที่ฉันต้องการรับรายการล่าสุดสำหรับแต่ละกลุ่ม นี่คือตาราง:

DocumentStatusLogs โต๊ะ

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

ตารางจะถูกจัดกลุ่มตามDocumentIDและเรียงลำดับจากDateCreatedมากไปน้อย สำหรับแต่ละDocumentIDฉันต้องการได้รับสถานะล่าสุด

ผลลัพธ์ที่ฉันต้องการ:

| DocumentID | Status | DateCreated |
| 1          | S1     | 8/02/2011   |
| 2          | S3     | 8/01/2011   |
| 3          | S1     | 8/02/2011   |
  • มีฟังก์ชั่นรวมที่จะได้รับเฉพาะสูงสุดจากแต่ละกลุ่ม? ดูรหัสหลอกGetOnlyTheTopด้านล่าง:

    SELECT
      DocumentID,
      GetOnlyTheTop(Status),
      GetOnlyTheTop(DateCreated)
    FROM DocumentStatusLogs
    GROUP BY DocumentID
    ORDER BY DateCreated DESC
  • หากฟังก์ชั่นดังกล่าวไม่มีอยู่จะมีวิธีใดที่ฉันสามารถบรรลุเอาต์พุตที่ต้องการได้หรือไม่?

  • หรือในตอนแรกสิ่งนี้อาจเกิดจากฐานข้อมูลที่ผิดปกติหรือไม่? ฉันกำลังคิดเนื่องจากสิ่งที่ฉันกำลังมองหาเป็นเพียงหนึ่งแถวที่ควรstatusอยู่ในตารางหลัก

โปรดดูตารางหลักสำหรับข้อมูลเพิ่มเติม:

Documentsตารางปัจจุบัน

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

ตารางหลักควรเป็นเช่นนี้หรือไม่เพื่อให้ฉันสามารถเข้าถึงสถานะได้อย่างง่ายดาย

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

อัปเดต ฉันเพิ่งเรียนรู้วิธีใช้ "ใช้" ซึ่งทำให้ง่ายต่อการแก้ไขปัญหาดังกล่าว


2
สำหรับการอภิปรายรายละเอียดเพิ่มเติมและการเปรียบเทียบของการแก้ปัญหาที่เป็นไปได้ผมขอแนะนำให้อ่านคำถามที่คล้ายกันใน dba.se: การดึง n แถวต่อกลุ่ม
Vladimir Baranov

ฉันดูที่โพสต์และลอง การใช้กลุ่มโดย StoreIDสร้างข้อผิดพลาด
UltraJ

คำตอบ:


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

หากคุณคาดหวัง 2 รายการต่อวันสิ่งนี้จะเลือกหนึ่งรายการโดยพลการ หากต้องการรับทั้งสองรายการต่อวันให้ใช้ DENSE_RANK แทน

สำหรับการทำให้เป็นมาตรฐานหรือไม่นั้นขึ้นอยู่กับว่าคุณต้องการ:

  • รักษาสถานะใน 2 แห่ง
  • เก็บประวัติสถานะ
  • ...

ในขณะที่มันอยู่คุณรักษาประวัติสถานะ หากคุณต้องการสถานะล่าสุดในตารางผู้ปกครองด้วย (ซึ่งเป็นภาวะปกติ) คุณต้องมีทริกเกอร์เพื่อรักษา "สถานะ" ในพาเรนต์ หรือปล่อยตารางประวัติสถานะนี้


5
และ ... อะไรนะPartition By? Withยังใหม่สำหรับฉันด้วย :( ฉันใช้ mssql 2005 อยู่ดี
dpp

6
@domanokz: Partition โดยรีเซ็ตจำนวนใหม่ ดังนั้นในกรณีนี้มันบอกว่าจะนับต่อ DocumentID
gbn

1
หืมฉันกังวลเรื่องประสิทธิภาพฉันจะสอบถามแถวเป็นล้าน ๆ แถว SELECT * FROM (SELECT ... ) ส่งผลต่อประสิทธิภาพหรือไม่ นอกจากนี้ROW_NUMBERแบบสอบถามย่อยบางชนิดสำหรับแต่ละแถวมีอะไรบ้าง
dpp

1
@domanokz: ไม่ไม่ใช่แบบสอบถามย่อย หากคุณมีดัชนีที่ถูกต้องมีคนนับล้านไม่น่าจะมีปัญหา มีวิธีการตั้งค่าอยู่ 2 วิธีเท่านั้น: สิ่งนี้และผลรวม (โซลูชันของ Ariel) จึงพยายามที่พวกเขาทั้งสอง ...
GBN

1
@domanokz: เพียงแค่เปลี่ยน ORDER BY DateCreated DESC เป็น ORDER BY ID DESC
gbn

184

cross applyฉันเพิ่งได้เรียนรู้วิธีการใช้งาน นี่คือวิธีใช้ในสถานการณ์นี้:

 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
ที่จริงไม่ได้สร้างความแตกต่างตั้งแต่ปัญหายังคงอยู่
dpp

19
ฉันเพิ่งโพสต์ผลลัพธ์ของการทดสอบเวลาของฉันกับโซลูชั่นที่เสนอทั้งหมดและคุณออกมาด้านบน ให้คะแนนโหวตคุณ :-)
John Fairbanks

3
+1 สำหรับการปรับปรุงความเร็วสูง นี่คือเร็วกว่าฟังก์ชั่นหน้าต่างเช่น ROW_NUMBER () มันจะดีถ้า SQL รู้จัก ROW_NUMBER () = 1 เช่นแบบสอบถามและปรับให้เหมาะกับการใช้งาน หมายเหตุ: ฉันใช้ OUTER APPLY ตามที่ต้องการผลลัพธ์แม้ว่าจะไม่มีอยู่ในการนำไปใช้
TamusJRoyce

8
@TamusJRoyce คุณไม่สามารถคาดการณ์ได้ว่าเพียงเพราะมันเร็วขึ้นเมื่อเป็นเช่นนี้เสมอ มันขึ้นอยู่กับ. ตามที่อธิบายไว้ที่นี่sqlmag.com/database-development/optimizing-top-n-group-queries
Martin Smith

2
ความคิดเห็นของฉันเกี่ยวกับการมีหลายแถวและต้องการเพียงหนึ่งในหลายแถวต่อกลุ่ม เข้าร่วมเมื่อคุณต้องการหนึ่งถึงหลาย การใช้มีไว้สำหรับเมื่อคุณมีจำนวนหนึ่งถึงหนึ่ง แต่ต้องการกรองออกทั้งหมดยกเว้นแบบหนึ่งต่อหนึ่ง สถานการณ์จำลอง: สำหรับสมาชิก 100 คนโปรดระบุหมายเลขโทรศัพท์ที่ดีที่สุดให้กับแต่ละคน (ซึ่งแต่ละหมายเลขสามารถมีหมายเลขได้หลายหมายเลข) นี่คือที่ใช้ excels Less read = less disk access = ประสิทธิภาพที่ดีขึ้น ประสบการณ์ของฉันคือการออกแบบฐานข้อมูลที่ไม่ได้มาตรฐาน
TamusJRoyce

53

ฉันได้กำหนดเวลาผ่านคำแนะนำต่าง ๆ ที่นี่และผลลัพธ์ขึ้นอยู่กับขนาดของตารางที่เกี่ยวข้อง แต่โซลูชันที่สอดคล้องกันมากที่สุดคือการใช้ CROSS นำไปใช้การทดสอบเหล่านี้ทำงานกับ SQL Server 2008-R2 โดยใช้ตารางที่มี 6,500 บันทึกและอีกหนึ่งรายการ (สกีมาที่เหมือนกัน) กับ 137 ล้านรายการ คอลัมน์ที่ถูกสอบถามนั้นเป็นส่วนหนึ่งของคีย์หลักบนตารางและความกว้างของตารางนั้นเล็กมาก (ประมาณ 30 ไบต์) เวลารายงานโดย SQL Server จากแผนการดำเนินการตามจริง

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

ฉันคิดว่าสิ่งที่น่าทึ่งจริงๆก็คือเวลาที่สอดคล้องกันสำหรับ CROSS นำไปใช้โดยไม่คำนึงถึงจำนวนแถวที่เกี่ยวข้อง


8
ทุกอย่างขึ้นอยู่กับการกระจายข้อมูลและดัชนีที่มี มันได้รับการกล่าวถึงในช่วงที่ดีในdba.se
Vladimir Baranov

48

ฉันรู้ว่านี่เป็นเธรดเก่า แต่การTOP 1 WITH TIESแก้ปัญหาค่อนข้างดีและอาจมีประโยชน์สำหรับการอ่านผ่านการแก้ปัญหา

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

เพิ่มเติมเกี่ยวกับข้อ TOP สามารถพบได้ที่นี่


7
นี่เป็นวิธีการแก้ปัญหาที่หรูหราที่สุด
George Menoutis

1
เห็นด้วย - นี่เป็นการทำซ้ำที่ดีที่สุดสิ่งที่ง่ายมากที่จะทำใน SQL เวอร์ชันอื่นและภาษาอื่น ๆ
Chris Umphlett

27

หากคุณกังวลเกี่ยวกับประสิทธิภาพคุณสามารถทำได้ด้วย MAX ():

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

ROW_NUMBER () ต้องการแถวทั้งหมดในคำสั่ง SELECT ของคุณในขณะที่ MAX ไม่ได้ ควรเร่งความเร็วการค้นหาของคุณอย่างมาก


2
ไม่สามารถแก้ไขปัญหาประสิทธิภาพการทำงานของ ROW_NUMBER () ด้วยการจัดทำดัชนีที่เหมาะสมหรือไม่ (ฉันรู้สึกว่าควรจะทำอย่างใด)
Kristoffer L

8
ด้วย datetime คุณไม่สามารถรับประกันได้ว่าทั้งสองรายการจะไม่ถูกเพิ่มในวันและเวลาเดียวกัน ความแม่นยำไม่สูงพอ
TamusJRoyce

+1 สำหรับความเรียบง่าย @TamusJRoyce ถูกต้อง เกี่ยวกับอะไร 'select * จาก DocumentStatusLog D โดยที่ ID = (เลือก ID จาก DocumentsStatusLog โดยที่ D.DocumentID = ลำดับ DocumentID ตาม DateCreated DESC ขีด จำกัด 1);'
cibercitizen1

SELECT * จาก EventScheduleTbl D WHERE DatesPicked = (เลือกด้านบน 1 นาที (DatesPicked) จาก EventScheduleTbl WHERE EventIDf = D.EventIDf และ DatesPicked> = แปลง (วันที่ getdate ())
Arun Prasad ES

มีบางกรณีที่สิ่งนี้จะมีประสิทธิภาพสูงกว่าrow_number()แม้จะมีการจัดทำดัชนีที่เหมาะสม ฉันพบว่ามันมีคุณค่าเป็นอย่างยิ่งในสถานการณ์ที่เข้าร่วมด้วยตนเอง สิ่งที่ต้องรู้ทัน แต่เป็นว่าวิธีการนี้มักจะให้จำนวนที่สูงขึ้นของทั้งสองตรรกะอ่านและนับสแกนแม้จะมีการรายงานค่าใช้จ่ายในทรีย่อยต่ำ คุณจะต้องชั่งน้ำหนักต้นทุน / ผลประโยชน์ในกรณีเฉพาะของคุณเพื่อตรวจสอบว่าจริง ๆ แล้วดีกว่า
pimbrouwers

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

เซิร์ฟเวอร์ฐานข้อมูลใด รหัสนี้ใช้ไม่ได้กับทุกคน

เกี่ยวกับคำถามครึ่งหลังของคุณดูเหมือนว่าฉันมีเหตุผลที่จะรวมสถานะเป็นคอลัมน์ คุณสามารถออกจากDocumentStatusLogsบันทึกได้ แต่ยังคงเก็บข้อมูลล่าสุดไว้ในตารางหลัก

BTW หากคุณมีDateCreatedคอลัมน์ในตารางเอกสารคุณสามารถเข้าร่วมได้DocumentStatusLogsโดยใช้ (ตราบเท่าที่DateCreatedไม่ซ้ำกันDocumentStatusLogs)

แก้ไข: MsSQL ไม่รองรับการใช้งานดังนั้นเปลี่ยนเป็น:

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

5
เบาะแสอยู่ในชื่อ: MSSQL SQL Server ไม่ได้ใช้ แต่ความคิดก็โอเค
gbn

7
@gbn ผู้ดำเนินการที่โง่มักจะลบคำหลักที่สำคัญออกจากชื่อตามที่ได้ทำไว้ที่นี่ ทำให้ยากมากในการค้นหาคำตอบที่ถูกต้องในผลการค้นหาหรือ Google
NickG

2
Jus จะชี้ให้เห็นว่า "การแก้ปัญหา" ยังคงสามารถให้คุณหลายระเบียนถ้าคุณมีผูกที่max(DateCreated)
moonknight

12

นี่เป็นหนึ่งในคำถามที่พบได้ง่ายที่สุดในหัวข้อดังนั้นฉันจึงต้องการให้คำตอบที่ทันสมัยกับมัน (ทั้งสำหรับการอ้างอิงของฉันและเพื่อช่วยเหลือผู้อื่น) โดยการใช้first_valueและoverคุณสามารถทำให้การทำงานสั้นของแบบสอบถามด้านบน:

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

สิ่งนี้จะทำงานได้ใน Sql Server 2008 ขึ้นไป First_valueสามารถคิดได้ว่าเป็นวิธีที่จะทำให้สำเร็จSelect Top 1เมื่อใช้ส่วนoverคำสั่ง Overช่วยให้การจัดกลุ่มในรายการเลือกเพื่อแทนการเขียน subqueries ซ้อนกัน (เช่นเดียวกับหลายคำตอบที่มีอยู่ทำ) นี้ไม่ได้ในแฟชั่นอ่านได้มากขึ้น หวังว่านี่จะช่วยได้


2
สิ่งนี้ไม่ทำงานใน SQL Server 2008 R2 ฉันคิดว่า first_value เปิดตัวในปี 2012!
ยูเอฟโอ

1
เร็วมาก! ผมใช้วิธีการแก้ปัญหาข้ามที่นำเสนอโดย @dpp สมัคร แต่คนนี้เป็น waaaay ได้เร็วขึ้น
MattSlay

11

นี่เป็นหัวข้อเก่า ๆ แต่ฉันคิดว่าฉันจะโยนสองเซ็นต์ของฉันในแบบเดียวกับที่คำตอบที่ยอมรับไม่ได้ผลดีสำหรับฉันโดยเฉพาะ ฉันลองใช้วิธีแก้ปัญหาของ gbn ในชุดข้อมูลขนาดใหญ่และพบว่ามันช้ามาก (> 45 วินาทีต่อ 5 ล้านเร็กคอร์ดบวกใน SQL Server 2012) เมื่อดูที่แผนการดำเนินการจะเห็นได้ชัดว่าปัญหาคือต้องมีการดำเนินการ SORT ซึ่งทำให้สิ่งต่าง ๆ ช้าลงอย่างมาก

นี่คือทางเลือกที่ผมยกมาจากกรอบกิจการที่ต้องดำเนินการเรียงลำดับไม่มีและไม่ค้นหาไม่มีคลัสเตอร์ดัชนี ซึ่งจะช่วยลดลงเวลาการดำเนินการไปยัง <2 วินาทีในอัลบั้มชุดดังกล่าวข้างต้น

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]

ตอนนี้ฉันสมมติว่ามีบางสิ่งที่ไม่ได้ระบุไว้อย่างสมบูรณ์ในคำถามต้นฉบับ แต่ถ้าการออกแบบตารางของคุณเป็นเช่นนั้นคอลัมน์ ID ของคุณคือ ID ที่เพิ่มขึ้นอัตโนมัติและ DateCreated ถูกตั้งค่าเป็นวันที่ปัจจุบันด้วยการแทรกแต่ละครั้ง โดยไม่ต้องใช้แบบสอบถามของฉันข้างต้นคุณจะได้รับประสิทธิภาพที่เพิ่มขึ้นอย่างมากในการแก้ปัญหาของ gbn (ประมาณครึ่งหนึ่งของเวลาดำเนินการ) เพียงแค่สั่งซื้อจากID แทนที่จะสั่งซื้อใน DateCreatedเพราะจะให้ลำดับการจัดเรียงที่เหมือนกัน


5

รหัสของฉันเพื่อเลือกด้านบน 1 จากแต่ละกลุ่ม

เลือก. * จาก #DocumentStatusLogs 
 สร้างใน (เลือก 1 อันดับแรกสร้างจาก #DocumentStatusLogs
ที่ไหน 
a.documentid = b.documentid
สั่งซื้อโดย datecreated เรียง
)

3

การตรวจสอบคำตอบที่ถูกต้องน่ากลัวและคลินต์จากด้านบน:

ผลการดำเนินงานระหว่างสองคำสั่งดังต่อไปนี้เป็นที่น่าสนใจ 52% เป็นหนึ่งด้านบน และ 48% เป็นคนที่สอง การปรับปรุงประสิทธิภาพ 4% โดยใช้ DISTINCT แทนที่จะเป็น ORDER BY แต่ ORDER BY มีข้อได้เปรียบในการจัดเรียงตามหลายคอลัมน์

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

ตัวเลือกที่ 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])

ตัวเลือก 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 $: หลังจากไฮไลต์และเรียกใช้บล็อกแรกให้เน้นทั้งตัวเลือก 1 และตัวเลือก 2 คลิกขวา -> [แสดงแผนการดำเนินการโดยประมาณ] จากนั้นเรียกสิ่งทั้งหมดที่จะเห็นผล

ตัวเลือก 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

ตัวเลือก 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

บันทึก:

ฉันมักจะใช้นำไปใช้เมื่อฉันต้องการเข้าร่วมเป็น 1-to-(1 ของอีกหลายคน)

ฉันใช้ JOIN ถ้าฉันต้องการให้การเข้าร่วมเป็นแบบหนึ่งต่อหลายคนหรือหลายกลุ่ม

ฉันหลีกเลี่ยง CTE ด้วย ROW_NUMBER () เว้นแต่ฉันจะต้องทำอะไรบางอย่างที่ทันสมัยและใช้ได้กับการปรับประสิทธิภาพการทำงานของหน้าต่าง

ฉันยังหลีกเลี่ยงคำถามย่อย EXISTS / IN ในส่วนคำสั่ง WHERE หรือ ON เนื่องจากฉันพบว่าสิ่งนี้ทำให้แผนปฏิบัติการแย่มาก แต่ระยะทางจะแตกต่างกันไป ตรวจสอบแผนการดำเนินการและประสิทธิภาพของโปรไฟล์ได้ทุกที่ทุกเวลาที่ต้องการ!


3

วิธีนี้สามารถใช้เพื่อรับแถวบนสุด N ล่าสุดสำหรับแต่ละพาร์ติชัน (ในตัวอย่าง N คือ 1 ในคำสั่ง WHERE และพาร์ติชันคือ 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 ;

ถ้าคุณต้องการส่งคืนเฉพาะเอกสารใบสั่งล่าสุดโดย DateCreated มันจะส่งคืนเอกสาร 1 อันดับแรกเท่านั้นโดย DocumentID


2

CROSS APPLYเป็นวิธีที่ฉันใช้สำหรับวิธีแก้ปัญหาของฉันเพราะมันใช้ได้กับฉันและสำหรับความต้องการของลูกค้า และจากสิ่งที่ฉันอ่านควรให้ประสิทธิภาพโดยรวมที่ดีที่สุดหากฐานข้อมูลของพวกเขาเติบโตขึ้นอย่างมาก


1

ต่อไปนี้เป็น 3 วิธีในการแก้ไขปัญหาพร้อมด้วยตัวเลือกที่ดีที่สุดของการทำดัชนีสำหรับแต่ละข้อความค้นหาเหล่านั้น (โปรดลองทำดัชนีตัวเองและดูการอ่านเชิงตรรกะเวลาที่ผ่านไปแผนปฏิบัติการฉันได้ให้คำแนะนำจากประสบการณ์ของฉัน แบบสอบถามดังกล่าวโดยไม่ต้องดำเนินการสำหรับปัญหาเฉพาะนี้)

วิธีที่ 1 : ใช้ ROW_NUMBER () หากดัชนี rowstore ไม่สามารถปรับปรุงประสิทธิภาพได้คุณสามารถลองใช้ดัชนี columnstore ที่ไม่ใช่คลัสเตอร์ / คลัสเตอร์สำหรับการสืบค้นที่มีการรวมและการจัดกลุ่มและสำหรับตารางที่เรียงลำดับตามในคอลัมน์ที่แตกต่างกันตลอดเวลาโดยปกติดัชนี columnstore จะเป็นตัวเลือกที่ดีที่สุด

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

วิธีที่ 2 : ใช้ FIRST_VALUE หากดัชนี rowstore ไม่สามารถปรับปรุงประสิทธิภาพได้คุณสามารถลองใช้ดัชนี columnstore ที่ไม่ใช่คลัสเตอร์ / คลัสเตอร์สำหรับการสืบค้นที่มีการรวมและการจัดกลุ่มและสำหรับตารางที่เรียงลำดับตามในคอลัมน์ที่แตกต่างกันตลอดเวลาโดยปกติดัชนี columnstore จะเป็นตัวเลือกที่ดีที่สุด

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;

วิธีที่ 3 : ใช้ CROSS ใช้เท่านั้น การสร้างดัชนี rowstore ในตาราง DocumentStatusLogs ซึ่งครอบคลุมคอลัมน์ที่ใช้ในแบบสอบถามควรเพียงพอที่จะครอบคลุมแบบสอบถามโดยไม่ต้องใช้ดัชนี 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

ฉันเชื่อว่านี่สามารถทำได้เช่นนี้ อาจต้องมีการปรับแต่งเล็กน้อย แต่คุณสามารถเลือกจำนวนสูงสุดจากกลุ่มได้

คำตอบเหล่านี้มากไป ..

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

ในสถานการณ์ที่คุณต้องการหลีกเลี่ยงการใช้ row_count () คุณสามารถใช้การเข้าร่วมด้านซ้าย:

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 

สำหรับสคีมาตัวอย่างคุณยังสามารถใช้ "ไม่ได้อยู่ในเคียวรีย่อย" ซึ่งโดยทั่วไปจะคอมไพล์ไปยังเอาต์พุตเดียวกันกับการรวมด้านซ้าย:

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)

หมายเหตุรูปแบบแบบสอบถามย่อยจะไม่ทำงานหากตารางไม่มีคีย์ / ข้อ จำกัด / ดัชนีที่ไม่ซ้ำกันอย่างน้อยหนึ่งคอลัมน์ในกรณีนี้คีย์หลัก "Id"

แบบสอบถามทั้งสองเหล่านี้มีแนวโน้มที่จะ "แพง" มากกว่าแบบสอบถาม row_count () (วัดโดย Query Analyzer) อย่างไรก็ตามคุณอาจพบสถานการณ์ที่พวกเขาคืนผลลัพธ์ได้เร็วขึ้นหรือเปิดใช้งานการเพิ่มประสิทธิภาพอื่น ๆ


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

ลองสิ่งนี้:

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]

คุณควรอธิบายคำสั่ง SQL ของคุณว่ามันจะทำงานอย่างไรและแก้แบบสอบถามของ OP
Suraj Kumar

-1

นี่คือวานิลลา TSQL ที่สุดที่ฉันสามารถหาได้

    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

น่าเสียดาย MaxDate นั้นไม่ซ้ำกัน เป็นไปได้ที่จะป้อนสองวันในเวลาที่แน่นอน ดังนั้นสิ่งนี้อาจส่งผลให้เกิดการซ้ำซ้อนต่อกลุ่ม อย่างไรก็ตามคุณสามารถใช้คอลัมน์ข้อมูลประจำตัวหรือ GUID คอลัมน์ข้อมูลประจำตัวจะช่วยให้คุณได้รับคอลัมน์ล่าสุดที่คุณป้อน (ใช้การคำนวณเอกลักษณ์เริ่มต้น 1 ... x ขั้นตอนที่ 1)
TamusJRoyce

ดีชนิดของฉันเห็นด้วย แต่ผู้เขียนขอรายการใหม่ล่าสุด - ซึ่งเว้นแต่คุณจะมีเพิ่มโดยอัตโนมัติตัวตนหมายถึงคอลัมน์สองรายการเพิ่มที่ตรงเวลาเดียวกันได้อย่างเท่าเทียมกัน 'ล่าสุด'
อุดมไปด้วย s

บันทึกล่าสุดจะเป็นหนึ่งระเบียน ใช่. คุณต้องพิจารณาคอลัมน์ข้อมูลประจำตัวที่เพิ่มขึ้นอัตโนมัติ
TamusJRoyce

-2

มีการตรวจสอบใน SQLite ที่คุณสามารถใช้แบบสอบถามง่ายๆดังต่อไปกับกลุ่มตาม

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

ต่อไปนี้เป็นMAXช่วยในการรับDateCreatedสูงสุดจากแต่ละกลุ่ม

แต่ดูเหมือนว่า MYSQL จะไม่เชื่อมโยง * -columns กับค่าของ DateCreated สูงสุด :(

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.