Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Các phương pháp tốt nhất cho tổng số chạy được nhóm lại

Bài đăng blog đầu tiên trên trang web này, vào tháng 7 năm 2012, đã nói về các phương pháp tốt nhất để chạy tổng. Kể từ đó, tôi đã nhiều lần được hỏi rằng tôi sẽ tiếp cận vấn đề như thế nào nếu tổng số đang chạy phức tạp hơn - cụ thể là nếu tôi cần tính tổng số đang chạy cho nhiều thực thể - chẳng hạn như đơn đặt hàng của từng khách hàng.

Ví dụ ban đầu sử dụng một trường hợp hư cấu về một thành phố phát hành vé quá tốc độ; tổng số lần chạy chỉ đơn giản là tổng hợp và giữ số lượng vé chạy quá tốc độ theo ngày (bất kể vé được cấp cho ai hoặc số lượng vé là bao nhiêu). Một ví dụ phức tạp hơn (nhưng thực tế) có thể là tổng hợp tổng giá trị vé chạy quá tốc độ, được nhóm theo giấy phép lái xe, mỗi ngày. Hãy hình dung bảng sau:

 TẠO BẢNG dbo.SpeedingTickets (IncidentID INT IDENTITY (1,1) PRIMARY KEY, LicenseNumber INT NOT NULL, IncidentDate DATE NOT NULL, TicketAmount DECIMAL (7,2) NOT NULL); TẠO CHỈ SỐ DUY NHẤT x TRÊN dbo.SpeedingTickets (LicenseNumber, IncidentDate) BAO GỒM (TicketAmount); 

Bạn có thể hỏi, DECIMAL(7,2) , thật sự? Những người này đi nhanh đến mức nào? Ví dụ, ở Canada, không quá khó để bị phạt 10.000 đô la khi chạy quá tốc độ.

Bây giờ, hãy điền vào bảng một số dữ liệu mẫu. Tôi sẽ không đi sâu vào tất cả các chi tiết cụ thể ở đây, nhưng điều này sẽ tạo ra khoảng 6.000 hàng đại diện cho nhiều tài xế và nhiều số tiền vé trong khoảng thời gian kéo dài một tháng:

; WITH TicketAmounts (ID, Value) AS (- 10 lượng vé tùy ý CHỌN i, p TỪ (CÁC GIÁ TRỊ (1,32,75), (2,75), (3,109), (4,175), (5,295), (6,68.50), (7,125), (8,145), (9,199), (10,250)) AS v (i, p)), Số giấy phép (LicenseNumber, [newid]) AS (- 1000 số giấy phép ngẫu nhiên CHỌN ĐẦU ( 1000) 7000000 + number, n =NEWID () FROM [master] .dbo.spt_values ​​NƠI số GIỮA 1 VÀ 999999 ĐẶT HÀNG THEO n), Ngày tháng 1 ([ngày]) NHƯ (- mỗi ngày trong tháng 1 năm 2014 CHỌN HÀNG ĐẦU (31) DATEADD (DAY, number, '20140101') FROM [master] .dbo.spt_values ​​WHERE [type] =N'P 'ORDER BY number), Tickets (LicenseNumber, [day], s) AS (- match * some * giấy phép cho những ngày họ nhận được vé CHỌN DISTINCT l.LicenseNumber, d. [day], s =RTRIM (l.LicenseNumber) FROM LicenseNumbers AS l CROSS JOIN JanuaryDates AS d WHERE CHECKSUM (NEWID ())% 100 =l.LicenseNumber% 100 VÀ (RTRIM (l.LicenseNumber) LIKE '%' + RIGHT (CHUYỂN ĐỔI (CHAR (8), d. [Day], 112), 1) + '%') HOẶC (RTRIM (l.LicenseNumber + 1) LIKE ' % '+ RIGHT ( CONVERT (CHAR (8), d. [Day], 112), 1) + '%')) CHÈN dbo.SpeedingTickets (LicenseNumber, IncidentDate, TicketAmount) CHỌN t.LicenseNumber, t. [Day], ta.Value FROM Tickets AS t INNER JOIN TicketAmounts AS ta ON ta.ID =CHUYỂN ĐỔI (INT, RIGHT (t.s, 1)) - CHUYỂN ĐỔI (INT, LEFT (RIGHT (t.s, 2), 1)) ĐẶT HÀNG THEO t. [Ngày], t .LicenseNumber; 

Điều này có vẻ hơi liên quan, nhưng một trong những thách thức lớn nhất mà tôi thường gặp phải khi soạn các bài đăng trên blog này là xây dựng một lượng dữ liệu thực tế "ngẫu nhiên" / tùy ý phù hợp. Nếu bạn có một phương pháp tốt hơn cho tập hợp dữ liệu tùy ý, bằng mọi cách, đừng sử dụng mumblings của tôi làm ví dụ - chúng ngoại vi đối với điểm của bài đăng này.

Phương pháp tiếp cận

Có nhiều cách khác nhau để giải quyết vấn đề này trong T-SQL. Dưới đây là bảy cách tiếp cận, cùng với các kế hoạch liên quan của chúng. Tôi đã bỏ qua các kỹ thuật như con trỏ (vì chúng sẽ chậm hơn không thể phủ nhận) và CTE đệ quy dựa trên ngày (vì chúng phụ thuộc vào các ngày liền kề).

    Truy vấn con # 1

     SELECT LicenseNumber, IncidentDate, TicketAmount, RunningTotal =TicketAmount + COALESCE ((SELECT SUM (TicketAmount) FROM dbo.SpeedingTickets AS s WHERE s.LicenseNumber =o.LicenseNumber AND s.IncidentDate  


    Kế hoạch cho truy vấn con # 1

    Truy vấn con # 2

     SELECT LicenseNumber, IncidentDate, TicketAmount, RunningTotal =(SELECT SUM (TicketAmount) FROM dbo.SpeedingTickets WHERE LicenseNumber =t.LicenseNumber AND IncidentDate <=t.IncidentDate) FROM dbo.SpeedingTickets AS tORDER BY LicenseNumber, IncidentDate; 


    Kế hoạch cho truy vấn con # 2

    Tự tham gia

     SELECT t1.LicenseNumber, t1.IncidentDate, t1.TicketAmount, RunningTotal =SUM (t2.TicketAmount) FROM dbo.SpeedingTickets AS t1INNER JOIN dbo.SpeedingTickets AS t2 ON t1.LicenseNumber =t2.LicenseNumber AND t1.IncidentDate> =IncidentDate t2.IncidentDateGROUP BY t1.LicenseNumber, t1.IncidentDate, t1.TicketAmountORDER BY t1.LicenseNumber, t1.IncidentDate; 


    Lên kế hoạch tự tham gia

    Áp dụng bên ngoài

     SELECT t1.LicenseNumber, t1.IncidentDate, t1.TicketAmount, RunningTotal =SUM (t2.TicketAmount) FROM dbo.SpeedingTickets AS t1OUTER ÁP DỤNG (CHỌN TicketAmount TỪ dbo.SpeedingTickets WHERE LicenseNumber =t1.LicenseNumber =AND IncidentDate  


    Kế hoạch áp dụng bên ngoài

    SUM OVER () sử dụng RANGE (chỉ dành cho năm 2012 trở lên)

     CHỌN LicenseNumber, IncidentDate, TicketAmount, RunningTotal =SUM (TicketAmount) OVER (PHẦN THEO SỐ Giấy phép, ĐẶT HÀNG THEO Sự cố RANGE UNBOUNDED PRECEDING) TỪ dbo.SpeedingTickets ORDER BY LicenseNumber, IncidentDate; 


    Lập kế hoạch cho SUM OVER () bằng RANGE

    SUM OVER () sử dụng ROWS (chỉ dành cho năm 2012 trở lên)

     CHỌN LicenseNumber, IncidentDate, TicketAmount, RunningTotal =SUM (TicketAmount) OVER (PHẦN THEO SỐ Giấy phép, ĐẶT HÀNG THEO LỆNH CỦA SỰ KIỆN 


    Lập kế hoạch cho SUM OVER () bằng ROWS

    Lặp lại dựa trên bộ

    Với tín dụng của Hugo Kornelis (@Hugo_Kornelis) cho Chương # 4 trong SQL Server MVP Deep Dives Volume # 1, phương pháp này kết hợp phương pháp dựa trên tập hợp và phương pháp con trỏ.

     DECLARE @x TABLE (LicenseNumber INT NOT NULL, IncidentDate DATE NOT NULL, TicketAmount DECIMAL (7,2) NOT NULL, RunningTotal DECIMAL (7,2) NOT NULL, rn INT NOT NULL, PRIMARY KEY (LicenseNumber, IncidentDate) ); INSERT @x (LicenseNumber, IncidentDate, TicketAmount, RunningTotal, rn) SELECT LicenseNumber, IncidentDate, TicketAmount, TicketAmount, ROW_NUMBER () OVER (PARTITION BY LicenseNumber ORDER BY IncidentDate) FROM dbo.SpeedingTickets; KHAI BÁO @rn INT =1, @rc INT =1; WHILE @rc> 0BEGIN SET @rn + =1; CẬP NHẬT [hiện tại] ĐẶT RunningTotal =[cuối] .RunningTotal + [hiện tại] .TicketAmount TỪ @x NHƯ [hiện tại] INNER JOIN @x NHƯ [cuối] BẬT [hiện tại] .LicenseNumber =[cuối] .LicenseNumber VÀ [cuối]. rn =@rn - 1 WHERE [hiện tại] .rn =@rn; SET @rc =@@ ROWCOUNT; KẾT THÚC CHỌN Số giấy phép, Ngày xảy ra sự cố, Số vé, Tổng số lần chạy TỪ @x ĐẶT HÀNG THEO SỐ Giấy phép, Ngày xảy ra sự cố; 

    Do bản chất của nó, cách tiếp cận này tạo ra nhiều kế hoạch giống nhau trong quá trình cập nhật biến bảng, tất cả đều tương tự như kế hoạch tự nối và áp dụng bên ngoài, nhưng có thể sử dụng tìm kiếm:


    Một trong nhiều kế hoạch CẬP NHẬT được tạo thông qua lặp lại dựa trên bộ

    Sự khác biệt duy nhất giữa mỗi kế hoạch trong mỗi lần lặp là số hàng. Qua mỗi lần lặp lại liên tiếp, số lượng hàng bị ảnh hưởng phải giữ nguyên hoặc giảm xuống, vì số hàng bị ảnh hưởng ở mỗi lần lặp lại đại diện cho số tài xế có vé vào số ngày đó (hoặc chính xác hơn là số ngày ở "thứ hạng").

Kết quả hoạt động

Dưới đây là cách các phương pháp xếp chồng lên nhau, như được hiển thị bởi SQL Sentry Plan Explorer, ngoại trừ phương pháp lặp dựa trên tập hợp, bởi vì nó bao gồm nhiều câu lệnh riêng lẻ, không đại diện tốt khi so sánh với phần còn lại.


Số liệu thời gian chạy Plan Explorer cho sáu trong bảy phương pháp tiếp cận

Ngoài việc xem xét các kế hoạch và so sánh các chỉ số thời gian chạy trong Plan Explorer, tôi cũng đo thời gian chạy thô trong Management Studio. Dưới đây là kết quả của việc chạy mỗi truy vấn 10 lần, hãy nhớ rằng điều này cũng bao gồm thời gian hiển thị trong SSMS:


Thời lượng chạy, tính bằng mili giây, cho cả bảy phương pháp tiếp cận (10 lần lặp )

Vì vậy, nếu bạn đang sử dụng SQL Server 2012 trở lên, cách tiếp cận tốt nhất dường như là SUM OVER() sử dụng ROWS UNBOUNDED PRECEDING . Nếu bạn không sử dụng SQL Server 2012, cách tiếp cận truy vấn con thứ hai có vẻ là tối ưu về thời gian chạy, mặc dù số lần đọc cao so với OUTER APPLY truy vấn. Tất nhiên, trong mọi trường hợp, bạn nên thử nghiệm các cách tiếp cận này, được điều chỉnh cho phù hợp với giản đồ của bạn, dựa trên hệ thống của riêng bạn. Dữ liệu, chỉ mục và các yếu tố khác của bạn có thể dẫn đến một giải pháp khác tối ưu nhất trong môi trường của bạn.

Sự phức tạp khác

Bây giờ, chỉ mục duy nhất biểu thị rằng bất kỳ kết hợp LicenseNumber + IncidentDate nào sẽ chứa một tổng số tích lũy duy nhất, trong trường hợp một người lái xe cụ thể nhận được nhiều vé vào bất kỳ ngày nhất định nào. Quy tắc kinh doanh này giúp đơn giản hóa logic của chúng ta một chút, tránh sự cần thiết của bộ ngắt kết nối để tạo ra các tổng số chạy xác định.

Nếu bạn gặp phải trường hợp bạn có thể có nhiều hàng cho bất kỳ kết hợp LicenseNumber + IncidentDate nhất định nào, bạn có thể phá vỡ ràng buộc bằng cách sử dụng một cột khác giúp tạo sự kết hợp duy nhất (rõ ràng là bảng nguồn sẽ không còn ràng buộc duy nhất trên hai cột đó nữa) . Lưu ý rằng điều này có thể xảy ra ngay cả trong trường hợp DATE cột thực sự là DATETIME - nhiều người cho rằng giá trị ngày / giờ là duy nhất, nhưng điều này chắc chắn không phải lúc nào cũng được đảm bảo, bất kể mức độ chi tiết.

Trong trường hợp của tôi, tôi có thể sử dụng IDENTITY cột, IncidentID; đây là cách tôi sẽ điều chỉnh từng giải pháp (thừa nhận rằng có thể có nhiều cách tốt hơn; chỉ cần đưa ra ý kiến):

 / * --------- subquery # 1 --------- * / SELECT LicenseNumber, IncidentDate, TicketAmount, RunningTotal =TicketAmount + COALESCE ((CHỌN SUM (TicketAmount) FROM dbo. SpeedingTickets AS s WHERE s.LicenseNumber =o.LicenseNumber AND (s.IncidentDate  =t2.IncidentDate - đã thêm dòng này:AND t1.IncidentID> =t2.IncidentIDGROUP BY t1.LicenseNumber, t1.IncidentDate, t1 .TicketAmountORDER BY t1.LicenseNumber, t1.IncidentDate; / * --------- áp dụng bên ngoài --------- * / CHỌN t1.LicenseNumber, t1.IncidentDate, t1.TicketAmount, RunningTotal =SUM (t2.TicketAmount) TỪ dbo.SpeedingTickets AS ÁP DỤNG t1OUTER (SELECT TicketAmount TỪ dbo.SpeedingTickets WHERE LicenseNumber =t1.LicenseNumber AND IncidentDate <=t1.IncidentDate - đã thêm dòng này:AND IncidentID <=t1.IncidentID) AS t2GROUP BY t1.LicenseNumber, t1.IncidentDate, t1.Ticket THEO t1.LicenseNumber, t1.IncidentDate; / * --------- SUM () HƠN khi sử dụng RANGE --------- * / CHỌN LicenseNumber, IncidentDate, TicketAmount, RunningTotal =SUM (TicketAmount) OVER (PHẦN BẰNG LỆNH SỐ Giấy phép THEO Ngày tháng, IncidentID RANGE UNBOUNDED PRECEDING - đã thêm cột này ^^^^^^^^^^^^) TỪ dbo.SpeedingTickets ORDER BY LicenseNumber, IncidentDate; / * --------- SUM () HƠN khi sử dụng ROWS --------- * / CHỌN LicenseNumber, IncidentDate, TicketAmount, RunningTotal =SUM (TicketAmount) OVER (PHẦN BỞI ĐƠN HÀNG SỐ Giấy phép THEO Ngày xảy ra sự cố, IncidentID ROWS UNBOUNDED PRECEDING - đã thêm cột này ^^^^^^^^^^^^) TỪ dbo.SpeedingTickets ORDER BY LicenseNumber, IncidentDate; / * --------- lặp lại dựa trên bộ --------- * / DECLARE @x TABLE (- đã thêm cột này và đặt nó thành PK:IncidentID INT PRIMARY KEY, LicenseNumber INT NOT NULL, IncidentDate DATE NOT NULL, TicketAmount DECIMAL (7,2) NOT NULL, RunningTotal DECIMAL (7,2) NOT NULL, rn INT NOT NULL); - đã thêm cột bổ sung vào INSERT / SELECT:INSERT @x (IncidentID, LicenseNumber, IncidentDate, TicketAmount, RunningTotal, rn) SELECT IncidentID, LicenseNumber, IncidentDate, TicketAmount, TicketAmount, ROW_NUMBER () OVER (PARTITION BY LicenseNumber ORDER BY IncidentDate , IncidentID) - và đã thêm cột phá vỡ này ------------------------------ ^^^^^^^^ ^^^^ TỪ dbo.SpeedingTickets; - phần còn lại của giải pháp lặp lại dựa trên tập hợp vẫn không thay đổi 

Một phức tạp khác mà bạn có thể gặp phải là khi bạn không theo dõi toàn bộ bảng, mà là một tập hợp con (giả sử, trong trường hợp này là tuần đầu tiên của tháng Giêng). Bạn sẽ phải điều chỉnh bằng cách thêm WHERE các mệnh đề và ghi nhớ các vị từ đó khi bạn cũng có các truy vấn con tương quan.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Huawei GaussDB

  2. Xóa tập tin theo dõi bằng ADRCI

  3. Cắt mỡ trong nhật ký giao dịch

  4. Tải xuống bản sao cơ sở dữ liệu của bạn

  5. Toán tử SQL Equals (=) cho người mới bắt đầu