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

Duy trì MAX (hoặc MIN) đang chạy được nhóm lại

Lưu ý:Bài đăng này ban đầu chỉ được xuất bản trong sách điện tử của chúng tôi, Kỹ thuật hiệu suất cao cho SQL Server, Tập 3. Bạn có thể tìm hiểu về sách điện tử của chúng tôi tại đây.

Một yêu cầu mà tôi thỉnh thoảng thấy là có một truy vấn được trả lại với các đơn đặt hàng được nhóm theo khách hàng, hiển thị tổng số tiền đến hạn tối đa cho bất kỳ đơn đặt hàng nào cho đến nay ("đang chạy tối đa"). Vì vậy, hãy tưởng tượng các hàng mẫu sau:

SalesOrderID ID khách hàng Ngày đặt hàng TotalDue
12 2 2014-01-01 37,55
23 1 2014-01-02 45,29
31 2 2014-01-03 24,56
32 2 2014-01-04 89,84
37 1 2014-01-05 32,56
44 2 2014-01-06 45,54
55 1 2014-01-07 99,24
62 2 2014-01-08 12,55

Một vài hàng dữ liệu mẫu

Kết quả mong muốn từ các yêu cầu đã nêu như sau - nói một cách dễ hiểu, hãy sắp xếp các đơn đặt hàng của từng khách hàng theo ngày và liệt kê từng đơn đặt hàng. Nếu đó là giá trị TotalDue cao nhất cho tất cả các đơn hàng được xem cho đến ngày đó, hãy in tổng của đơn hàng đó, nếu không, hãy in giá trị TotalDue cao nhất từ ​​tất cả các đơn hàng trước đó:

SalesOrderID ID khách hàng Ngày đặt hàng TotalDue MaxTotalDue
12 1 2014-01-02 45,29 45,29
23 1 2014-01-05 32,56 45,29
31 1 2014-01-07 99,24 99,24
32 2 2014-01-01 37,55 37,55
37 2 2014-01-03 24,56 37,55
44 2 2014-01-04 89,84 89,84
55 2 2014-01-06 45,54 89,84
62 2 2014-01-08 12,55 89,84

Kết quả mong muốn mẫu

Theo bản năng, nhiều người muốn sử dụng con trỏ hoặc vòng lặp while để thực hiện điều này, nhưng có một số cách tiếp cận không liên quan đến các cấu trúc này.

Truy vấn con có liên quan

Cách tiếp cận này có vẻ là cách tiếp cận đơn giản và dễ hiểu nhất đối với vấn đề, nhưng nó đã được chứng minh hết lần này đến lần khác là không mở rộng quy mô, vì số lần đọc tăng theo cấp số nhân khi bảng lớn hơn:

 SELECT / * Truy vấn con có liên quan * / SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue =(SELECT MAX (TotalDue) FROM Sales.SalesOrderHeader WHERE CustomerID =h.CustomerID AND SalesOrderID <=h.SalesOrderID) FROM Sales.SalesOrderHeader AS h ĐẶT HÀNG THEO ID khách hàng, ID bán hàng; 

Đây là kế hoạch chống lại AdventureWorks2014, sử dụng SQL Sentry Plan Explorer:

Kế hoạch thực thi cho truy vấn con tương quan (nhấp để phóng to)

ÁP DỤNG CROSS tự tham chiếu

Cách tiếp cận này gần giống với cách tiếp cận Truy vấn con có liên quan, về cú pháp, hình dạng kế hoạch và hiệu suất trên quy mô lớn.

ÁP DỤNG
 SELECT / * CROSS * / h.SalesOrderID, h.CustomerID, h.OrderDate, h.TotalDue, x.MaxTotalDueFROM Sales.SalesOrderHeader AS hCROSS ÁP DỤNG (CHỌN MaxTotalDue =MAX (TotalDue) TỪ Sales.SalesOrderHeader AS i WHERE i.CustomerID =h.CustomerID VÀ i.SalesOrderID <=h.SalesOrderID) NHƯ xORDER BY h.CustomerID, h.SalesOrderID; 

Kế hoạch này khá giống với kế hoạch truy vấn con tương quan, điểm khác biệt duy nhất là vị trí của một loại:

Kế hoạch thực thi ÁP DỤNG CHÉO (nhấp để phóng to)

CTE đệ quy

Đằng sau, điều này sử dụng vòng lặp, nhưng cho đến khi chúng tôi thực sự chạy nó, chúng tôi có thể giả vờ như nó không (mặc dù nó dễ dàng là đoạn mã phức tạp nhất mà tôi từng muốn viết để giải quyết vấn đề cụ thể này):

; WITH / * Recursive CTE * / cte AS (SELECT SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue FROM (SELECT SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue =TotalDue, rn =ROW_NUMBER () HẾT (PHẦN BẰNG ĐƠN HÀNG CustomerID BY SalesOrderID) TỪ Sales.SalesOrderHeader) AS x WHERE rn =1 UNION ALL SELECT r.SalesOrderID, r.CustomerID, r.OrderDate, r.TotalDue, MaxTotalDue =CASE WHEN r.TotalDue> cte.MaxTotalDue THEN r.TotalDue .MaxTotalDue KẾT THÚC TỪ Cte CROSS ÁP DỤNG (CHỌN SalesOrderID, CustomerID, OrderDate, TotalDue, rn =ROW_NUMBER () OVER (PARTITION BY CustomerID ORDER BY SalesOrderID) TỪ Sales.SalesOrderHeader AS h WHERE h.CustomerID =cte.CustomerID VÀ h.SalesOrderID> cte.SalesOrderID) AS r WHERE r.rn =1) CHỌN SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDueFROM cteORDER BY CustomerID, SalesOrderIDOPTION (MAXRECURSION 0); 

Bạn có thể thấy ngay rằng kế hoạch phức tạp hơn hai kế hoạch trước, điều này không có gì đáng ngạc nhiên khi truy vấn phức tạp hơn:

Kế hoạch thực thi cho CTE đệ quy (nhấp để phóng to)

Do một số ước tính không tốt, chúng tôi thấy một tìm kiếm chỉ mục với tra cứu khóa đi kèm có lẽ phải được thay thế bằng một lần quét duy nhất và chúng tôi cũng nhận được một thao tác sắp xếp cuối cùng cần chuyển sang tempdb (bạn có thể xem điều này trong chú giải công cụ nếu bạn di chuột qua toán tử sắp xếp có biểu tượng cảnh báo):

TỐI ĐA () HẾT (ROWS CHƯA ĐƯỢC TỔNG HỢP)

Đây là giải pháp chỉ có sẵn trong SQL Server 2012 trở lên, vì nó sử dụng các phần mở rộng mới được giới thiệu cho các chức năng cửa sổ.

 SELECT / * MAX () OVER () * / SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue =MAX (TotalDue) OVER (PHẦN THEO ĐƠN ĐẶT HÀNG CỦA KHÁCH HÀNG BỞI SalesOrderID ROWS UNBOUNDED PRECEDING) TỪ Sales.SalesOrderHeaderORDER BY CustomerID,  

Kế hoạch cho thấy chính xác lý do tại sao nó mở rộng quy mô tốt hơn tất cả các kế hoạch khác; nó chỉ có một hoạt động quét chỉ mục theo nhóm, trái ngược với hai (hoặc lựa chọn không hợp lý là quét và tìm kiếm + tra cứu trong trường hợp CTE đệ quy):

Kế hoạch thực thi cho MAX () OVER () (nhấp để phóng to)

So sánh Hiệu suất

Các kế hoạch chắc chắn khiến chúng tôi tin rằng MAX() OVER() mới khả năng trong SQL Server 2012 là một người chiến thắng thực sự, nhưng còn các số liệu thời gian chạy hữu hình thì sao? Dưới đây là cách các thực thi được so sánh:

Hai truy vấn đầu tiên gần như giống hệt nhau; trong khi trong trường hợp này, CROSS APPLY tốt hơn về thời lượng tổng thể với một biên độ nhỏ, thay vào đó, truy vấn con tương quan đôi khi đánh bại nó một chút. CTE đệ quy về cơ bản chậm hơn đáng kể mỗi lần và bạn có thể thấy các yếu tố góp phần gây ra điều đó - cụ thể là ước tính sai, số lượng đọc lớn, tra cứu khóa và thao tác sắp xếp bổ sung. Và như tôi đã chứng minh trước đây với việc chạy tổng, giải pháp SQL Server 2012 tốt hơn ở hầu hết mọi khía cạnh.

Kết luận

Nếu bạn đang sử dụng SQL Server 2012 trở lên, bạn chắc chắn muốn làm quen với tất cả các phần mở rộng cho chức năng cửa sổ được giới thiệu lần đầu tiên trong SQL Server 2005 - chúng có thể cung cấp cho bạn một số hiệu suất tăng khá nghiêm trọng khi xem lại mã vẫn đang chạy " theo cách cũ. " Nếu bạn muốn tìm hiểu thêm về một số tính năng mới này, tôi thực sự giới thiệu cuốn sách của Itzik Ben-Gan, Microsoft SQL Server 2012 T-SQL Sử dụng Chức năng Cửa sổ Hiệu suất cao.

Nếu bạn chưa sử dụng SQL Server 2012, ít nhất trong thử nghiệm này, bạn có thể chọn giữa CROSS APPLY và truy vấn con tương quan. Như thường lệ, bạn nên kiểm tra các phương pháp khác nhau dựa trên dữ liệu trên phần cứng của mình.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SQL, cách sử dụng SELECT

  2. Cách nhận hồ sơ từ 30 ngày qua

  3. Lọc dữ liệu với JDBC RowSet

  4. SAP Lumira và Cầu JDBC-ODBC

  5. Theo dõi cập nhật tự động cho thống kê