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

Các hàm cửa sổ lồng nhau trong SQL

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:

(<đối số với ROW_NUMBER () >) HẾT (<đặc tả>)

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:

(<đối số với
VALUE OF AT []
>) 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!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. CÁC CÂU HỎI VỀ SQL

  2. Cài đặt WordPress bằng WP-CLI

  3. Điều chỉnh Hiệu suất Toàn bộ Kế hoạch Truy vấn

  4. Xem xét Hiệu suất Ảnh chụp Cơ sở dữ liệu

  5. Hướng dẫn về các chức năng của PubNub