Tiêu chuẩn ISO / IEC 9075:2016 (SQL:2016) định nghĩa một tính năng được gọi là các hàm cửa sổ lồng nhau. Tính năng này cho phép bạn lồng hai loại hàm cửa sổ làm đối số của một hàm tổng hợp cửa sổ. Ý tưởng là cho phép bạn tham chiếu đến số hàng hoặc giá trị của một biểu thức, tại các điểm đánh dấu chiến lược trong các phần tử cửa sổ. Các điểm đánh dấu cung cấp cho bạn quyền truy cập vào hàng đầu tiên hoặc cuối cùng trong phân vùng, hàng đầu tiên hoặc hàng cuối cùng trong khung, hàng bên ngoài hiện tại và hàng khung hiện tại. Ý tưởng này rất mạnh mẽ, cho phép bạn áp dụng tính năng lọc và các loại thao tác khác trong chức năng cửa sổ của bạn mà đôi khi khó đạt được bằng cách khác. Bạn cũng có thể sử dụng các chức năng cửa sổ lồng nhau để dễ dàng mô phỏng các tính năng khác, chẳng hạn như khung dựa trên RANGE. Tính năng này hiện không khả dụng trong T-SQL. Tôi đã đăng một đề xuất để cải thiện SQL Server bằng cách thêm hỗ trợ cho các chức năng cửa sổ lồng nhau. Đảm bảo thêm phiếu bầu của bạn nếu bạn cảm thấy rằng tính năng này có thể có lợi cho bạn.
Những chức năng cửa sổ lồng nhau không liên quan đến
Vào ngày viết bài này, không có nhiều thông tin về các chức năng cửa sổ lồng nhau tiêu chuẩn thực sự. Điều làm khó hơn là tôi chưa biết nền tảng nào đã triển khai tính năng này. Trên thực tế, việc chạy tìm kiếm trên web cho các hàm cửa sổ lồng nhau trả về hầu hết các phạm vi và các cuộc thảo luận về các hàm tổng hợp được nhóm lồng nhau trong các hàm tổng hợp có cửa sổ. Ví dụ:giả sử bạn muốn truy vấn chế độ xem Sales.OrderValues trong cơ sở dữ liệu mẫu TSQLV5 và trả về cho từng khách hàng và ngày đặt hàng, tổng giá trị đơn đặt hàng hàng ngày và tổng số đang chạy cho đến ngày hiện tại. Một nhiệm vụ như vậy liên quan đến cả nhóm và cửa sổ. Bạn nhóm các hàng theo ID khách hàng và ngày đặt hàng, đồng thời áp dụng tổng đang chạy lên trên tổng nhóm của các giá trị đặt hàng, như sau:
USE TSQLV5; -- http://tsql.solidq.com/SampleDatabases/TSQLV5.zip SELECT custid, orderdate, SUM(val) AS daytotal, SUM(SUM(val)) OVER(PARTITION BY custid ORDER BY orderdate ROWS UNBOUNDED PRECEDING) AS runningsum FROM Sales.OrderValues GROUP BY custid, orderdate;
Truy vấn này tạo ra kết quả sau, được hiển thị ở đây dưới dạng viết tắt:
custid orderdate daytotal runningsum ------- ---------- -------- ---------- 1 2018-08-25 814.50 814.50 1 2018-10-03 878.00 1692.50 1 2018-10-13 330.00 2022.50 1 2019-01-15 845.80 2868.30 1 2019-03-16 471.20 3339.50 1 2019-04-09 933.50 4273.00 2 2017-09-18 88.80 88.80 2 2018-08-08 479.75 568.55 2 2018-11-28 320.00 888.55 2 2019-03-04 514.40 1402.95 ...
Mặc dù kỹ thuật này khá hay và mặc dù các tìm kiếm trên web cho các hàm cửa sổ lồng nhau chủ yếu trả lại các kỹ thuật như vậy, nhưng đó không phải là ý nghĩa của tiêu chuẩn SQL đối với các hàm cửa sổ lồng nhau. Vì tôi không thể tìm thấy bất kỳ thông tin nào về chủ đề này, nên tôi chỉ cần tìm ra nó từ chính tiêu chuẩn. Hy vọng rằng, bài viết này sẽ nâng cao nhận thức về tính năng các hàm cửa sổ lồng nhau thực sự và khiến mọi người chuyển sang sử dụng Microsoft và yêu cầu thêm hỗ trợ cho tính năng này trong SQL Server.
Các hàm cửa sổ lồng nhau nói về gì
Các hàm cửa sổ lồng nhau bao gồm hai hàm mà bạn có thể lồng vào nhau như một đối số của hàm tổng hợp cửa sổ. Đó là hàm số hàng lồng nhau và biểu thức value_of lồng nhau tại hàm hàng.
Hàm số hàng lồng nhau
Hàm số hàng lồng nhau cho phép bạn tham chiếu đến số hàng của các điểm đánh dấu chiến lược trong các phần tử cửa sổ. Đây là cú pháp của hàm:
Các điểm đánh dấu hàng mà bạn có thể chỉ định là:
- BEGIN_PARTITION
- END_PARTITION
- BEGIN_FRAME
- END_FRAME
- CURRENT_ROW
- FRAME_ROW
Bốn điểm đánh dấu đầu tiên là tự giải thích. Đối với hai phần cuối cùng, điểm đánh dấu CURRENT_ROW đại diện cho hàng bên ngoài hiện tại và FRAME_ROW đại diện cho hàng khung bên trong hiện tại.
Để làm ví dụ cho việc sử dụng hàm số hàng lồng nhau, hãy xem xét tác vụ sau. Bạn cần truy vấn chế độ xem Sales.OrderValues và trả về cho mỗi đơn đặt hàng một số thuộc tính của nó, cũng như sự khác biệt giữa giá trị đơn đặt hàng hiện tại và giá trị trung bình của khách hàng, nhưng loại trừ đơn đặt hàng đầu tiên và cuối cùng của khách hàng từ mức trung bình.
Tác vụ này có thể đạt được mà không cần các hàm cửa sổ lồng nhau, nhưng giải pháp bao gồm khá nhiều bước:
WITH C1 AS ( SELECT custid, val, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate, orderid ) AS rownumasc, ROW_NUMBER() OVER( PARTITION BY custid ORDER BY orderdate DESC, orderid DESC ) AS rownumdesc FROM Sales.OrderValues ), C2 AS ( SELECT custid, AVG(val) AS avgval FROM C1 WHERE 1 NOT IN (rownumasc, rownumdesc) GROUP BY custid ) SELECT O.orderid, O.custid, O.orderdate, O.val, O.val - C2.avgval AS diff FROM Sales.OrderValues AS O LEFT OUTER JOIN C2 ON O.custid = C2.custid;
Đây là kết quả của truy vấn này, được hiển thị ở đây ở dạng viết tắt:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------ 10411 10 2018-01-10 966.80 -570.184166 10743 4 2018-11-17 319.20 -809.813636 11075 68 2019-05-06 498.10 -1546.297500 10388 72 2017-12-19 1228.80 -358.864285 10720 61 2018-10-28 550.00 -144.744285 11052 34 2019-04-27 1332.00 -1164.397500 10457 39 2018-02-25 1584.00 -797.999166 10789 23 2018-12-22 3687.00 1567.833334 10434 24 2018-02-03 321.12 -1329.582352 10766 56 2018-12-05 2310.00 1015.105000 ...
Sử dụng các hàm số hàng lồng nhau, tác vụ có thể đạt được chỉ với một truy vấn duy nhất, như sau:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN ROW_NUMBER(FRAME_ROW) NOT IN ( ROW_NUMBER(BEGIN_PARTITION), ROW_NUMBER(END_PARTITION) ) THEN val END ) OVER( PARTITION BY custid ORDER BY orderdate, orderid ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS diff FROM Sales.OrderValues;
Ngoài ra, giải pháp hiện được hỗ trợ yêu cầu ít nhất một loại trong kế hoạch và nhiều lần vượt qua dữ liệu. Giải pháp sử dụng các hàm số hàng lồng nhau có tất cả khả năng được tối ưu hóa khi phụ thuộc vào thứ tự chỉ mục và giảm số lần chuyển qua dữ liệu. Tất nhiên, điều này phụ thuộc vào việc triển khai.
Biểu thức value_of lồng nhau ở hàm hàng
Biểu thức value_of lồng nhau tại hàm hàng cho phép bạn tương tác với giá trị của biểu thức tại cùng các điểm đánh dấu hàng chiến lược được đề cập trước đó trong đối số của hàm tổng hợp cửa sổ. Đây là cú pháp của hàm này:
VALUE OF
>) HẾT (<đặc tả>)
Như bạn có thể thấy, bạn có thể chỉ định một delta âm hoặc dương nhất định đối với điểm đánh dấu hàng và tùy chọn cung cấp giá trị mặc định trong trường hợp một hàng không tồn tại ở vị trí đã chỉ định.
Khả năng này cung cấp cho bạn rất nhiều sức mạnh khi bạn cần tương tác với các điểm khác nhau trong các phần tử cửa sổ. Hãy xem xét thực tế là các chức năng cửa sổ có thể được so sánh mạnh mẽ như các công cụ thay thế như truy vấn con, nhưng chức năng cửa sổ nào không hỗ trợ là một khái niệm cơ bản về mối tương quan. Sử dụng điểm đánh dấu CURRENT_ROW, bạn có quyền truy cập vào hàng bên ngoài và bằng cách này, mô phỏng các mối tương quan. Đồng thời, bạn sẽ được hưởng lợi từ tất cả những lợi thế mà các hàm cửa sổ có được so với các truy vấn con.
Ví dụ:giả sử bạn cần truy vấn chế độ xem Sales.OrderValues và trả về cho mỗi đơn hàng một số thuộc tính của nó, cũng như sự khác biệt giữa giá trị đơn đặt hàng hiện tại và mức trung bình của khách hàng, nhưng loại trừ các đơn hàng được đặt vào cùng một ngày như ngày đặt hàng hiện tại. Điều này đòi hỏi một khả năng tương tự như một mối tương quan. Với biểu thức value_of lồng nhau tại hàm hàng, sử dụng điểm đánh dấu CURRENT_ROW, điều này có thể đạt được dễ dàng như sau:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues;
Truy vấn này phải tạo ra kết quả sau:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------ 10248 85 2017-07-04 440.00 180.000000 10249 79 2017-07-05 1863.40 1280.452000 10250 34 2017-07-08 1552.60 -854.228461 10251 84 2017-07-08 654.06 -293.536666 10252 76 2017-07-09 3597.90 1735.092728 10253 34 2017-07-10 1444.80 -970.320769 10254 14 2017-07-11 556.62 -1127.988571 10255 68 2017-07-12 2490.50 617.913334 10256 88 2017-07-15 517.80 -176.000000 10257 35 2017-07-16 1119.90 -153.562352 ...
Nếu bạn đang nghĩ rằng nhiệm vụ này có thể đạt được dễ dàng với các truy vấn con tương quan, thì trong trường hợp đơn giản này, bạn đã đúng. Điều tương tự có thể đạt được với truy vấn sau:
SELECT O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( SELECT AVG(O2.val) FROM Sales.OrderValues AS O2 WHERE O2.custid = O1.custid AND O2.orderdate <> O1.orderdate ) AS diff FROM Sales.OrderValues AS O1;
Tuy nhiên, hãy nhớ rằng một truy vấn con hoạt động trên một chế độ xem dữ liệu độc lập, trong khi một hàm cửa sổ hoạt động trên tập hợp được cung cấp làm đầu vào cho bước xử lý truy vấn logic xử lý mệnh đề SELECT. Thông thường, truy vấn cơ bản có thêm logic như nối, bộ lọc, nhóm, v.v. Với truy vấn con, bạn cần chuẩn bị CTE sơ bộ hoặc lặp lại logic của truy vấn cơ bản cũng trong truy vấn con. Với các hàm cửa sổ, không cần lặp lại bất kỳ logic nào.
Ví dụ:giả sử bạn được cho là chỉ hoạt động trên các đơn đặt hàng đã giao (trong đó ngày vận chuyển không phải là NULL) do nhân viên 3. Giải pháp với chức năng cửa sổ chỉ cần thêm các vị từ bộ lọc một lần, như sau:
SELECT orderid, custid, orderdate, val, val - AVG( CASE WHEN orderdate <> VALUE OF orderdate AT CURRENT_ROW THEN val END ) OVER( PARTITION BY custid ) AS diff FROM Sales.OrderValues WHERE empid = 3 AND shippeddate IS NOT NULL;
Truy vấn này phải tạo ra kết quả sau:
orderid custid orderdate val diff -------- ------- ---------- -------- ------------- 10251 84 2017-07-08 654.06 -459.965000 10253 34 2017-07-10 1444.80 531.733334 10256 88 2017-07-15 517.80 -1022.020000 10266 87 2017-07-26 346.56 NULL 10273 63 2017-08-05 2037.28 -3149.075000 10283 46 2017-08-16 1414.80 534.300000 10309 37 2017-09-19 1762.00 -1951.262500 10321 38 2017-10-03 144.00 NULL 10330 46 2017-10-16 1649.00 885.600000 10332 51 2017-10-17 1786.88 495.830000 ...
Giải pháp với truy vấn con cần phải thêm các vị từ bộ lọc hai lần — một lần trong truy vấn bên ngoài và một lần trong truy vấn con — như vậy:
SELECT O1.orderid, O1.custid, O1.orderdate, O1.val, O1.val - ( SELECT AVG(O2.val) FROM Sales.OrderValues AS O2 WHERE O2.custid = O1.custid AND O2.orderdate <> O1.orderdate AND empid = 3 AND shippeddate IS NOT NULL) AS diff FROM Sales.OrderValues AS O1 WHERE empid = 3 AND shippeddate IS NOT NULL;
Đó là điều này hoặc thêm một CTE sơ bộ đảm nhận tất cả quá trình lọc và bất kỳ logic nào khác. Dù sao bạn nhìn vào nó, với các truy vấn con, có nhiều lớp phức tạp hơn liên quan.
Lợi ích khác trong các hàm cửa sổ lồng nhau là nếu chúng ta có hỗ trợ cho các hàm trong T-SQL, thì sẽ dễ dàng mô phỏng hỗ trợ đầy đủ còn thiếu cho đơn vị khung cửa sổ RANGE. Tùy chọn RANGE được cho là cho phép bạn xác định các khung động dựa trên độ lệch từ giá trị thứ tự trong hàng hiện tại. Ví dụ:giả sử rằng bạn cần tính toán cho mỗi đơn đặt hàng của khách hàng từ Sales.OrderValues xem giá trị trung bình động của 14 ngày qua. Theo tiêu chuẩn SQL, bạn có thể đạt được điều này bằng cách sử dụng tùy chọn RANGE và kiểu INTERVAL, như sau:
SELECT orderid, custid, orderdate, val, AVG(val) OVER( PARTITION BY custid ORDER BY orderdate RANGE BETWEEN INTERVAL '13' DAY PRECEDING AND CURRENT ROW ) AS movingavg14days FROM Sales.OrderValues;
Truy vấn này phải tạo ra kết quả sau:
orderid custid orderdate val movingavg14days -------- ------- ---------- ------- --------------- 10643 1 2018-08-25 814.50 814.500000 10692 1 2018-10-03 878.00 878.000000 10702 1 2018-10-13 330.00 604.000000 10835 1 2019-01-15 845.80 845.800000 10952 1 2019-03-16 471.20 471.200000 11011 1 2019-04-09 933.50 933.500000 10308 2 2017-09-18 88.80 88.800000 10625 2 2018-08-08 479.75 479.750000 10759 2 2018-11-28 320.00 320.000000 10926 2 2019-03-04 514.40 514.400000 10365 3 2017-11-27 403.20 403.200000 10507 3 2018-04-15 749.06 749.060000 10535 3 2018-05-13 1940.85 1940.850000 10573 3 2018-06-19 2082.00 2082.000000 10677 3 2018-09-22 813.37 813.370000 10682 3 2018-09-25 375.50 594.435000 10856 3 2019-01-28 660.00 660.000000 ...
Vào ngày viết bài này, cú pháp này không được hỗ trợ trong T-SQL. Nếu chúng tôi có hỗ trợ cho các hàm cửa sổ lồng nhau trong T-SQL, bạn sẽ có thể mô phỏng truy vấn này bằng đoạn mã sau:
SELECT orderid, custid, orderdate, val, AVG( CASE WHEN DATEDIFF(day, orderdate, VALUE OF orderdate AT CURRENT_ROW) BETWEEN 0 AND 13 THEN val END ) OVER( PARTITION BY custid ORDER BY orderdate RANGE UNBOUNDED PRECEDING ) AS movingavg14days FROM Sales.OrderValues;
Điều gì không thích?
Bỏ phiếu của bạn
Các chức năng cửa sổ lồng nhau tiêu chuẩn có vẻ như là một khái niệm rất mạnh mẽ cho phép tương tác với các điểm khác nhau trong các phần tử cửa sổ một cách linh hoạt. Tôi khá ngạc nhiên rằng tôi không thể tìm thấy bất kỳ mức độ phù hợp nào của khái niệm ngoài bản thân tiêu chuẩn và tôi không thấy nhiều nền tảng triển khai nó. Hy vọng rằng bài viết này sẽ nâng cao nhận thức cho tính năng này. Nếu bạn cảm thấy rằng việc cung cấp nó trong T-SQL có thể hữu ích cho bạn, hãy đảm bảo bỏ phiếu bầu của bạn!