Sử dụng EXCEPT trong biểu thức bảng chung đệ quy


33

Tại sao truy vấn sau trả về hàng vô hạn? Tôi đã mong đợi EXCEPTđiều khoản chấm dứt đệ quy ..

with cte as (
    select *
    from (
        values(1),(2),(3),(4),(5)
    ) v (a)
)
,r as (
    select a
    from cte
    where a in (1,2,3)
    union all
    select a
    from (
        select a
        from cte
        except
        select a
        from r
    ) x
)
select a
from r

Tôi đã bắt gặp điều này trong khi cố gắng trả lời một câu hỏi trên Stack Overflow.

Câu trả lời:


26

Xem câu trả lời của Martin Smith để biết thông tin về tình trạng hiện tại của EXCEPTCTE đệ quy.

Để giải thích những gì bạn đã thấy, và tại sao:

Tôi đang sử dụng một biến bảng ở đây, để làm cho sự khác biệt giữa các giá trị neo và mục đệ quy rõ ràng hơn (nó không thay đổi ngữ nghĩa).

DECLARE @V TABLE (a INTEGER NOT NULL)
INSERT  @V (a) VALUES (1),(2)
;
WITH rCTE AS 
(
    -- Anchor
    SELECT
        v.a
    FROM @V AS v

    UNION ALL

    -- Recursive
    SELECT
        x.a
    FROM
    (
        SELECT
            v2.a
        FROM @V AS v2

        EXCEPT

        SELECT
            r.a
        FROM rCTE AS r
    ) AS x
)
SELECT
    r2.a
FROM rCTE AS r2
OPTION (MAXRECURSION 0)

Kế hoạch truy vấn là:

Kế hoạch CTE đệ quy

Việc thực thi bắt đầu từ gốc của kế hoạch (CHỌN) và điều khiển chuyển xuống cây vào Bộ đệm chỉ mục, Ghép nối, sau đó đến Quét bảng cấp cao nhất.

Hàng đầu tiên từ quá trình quét đi lên cây và được (a) được lưu trữ trong Stack Spool và (b) được trả về cho máy khách. Hàng nào trước tiên không được xác định, nhưng chúng ta hãy giả sử đó là hàng có giá trị {1}, vì mục đích tranh luận. Do đó, hàng đầu tiên xuất hiện là {1}.

Điều khiển một lần nữa chuyển xuống Quét bảng (toán tử Ghép tiêu thụ tất cả các hàng từ đầu vào ngoài cùng của nó trước khi mở cái tiếp theo). Quá trình quét phát ra hàng thứ hai (giá trị {2}) và điều này một lần nữa chuyển lên cây sẽ được lưu trữ trên ngăn xếp và xuất ra cho máy khách. Hiện tại khách hàng đã nhận được chuỗi {1}, {2}.

Thông qua một quy ước trong đó đỉnh của ngăn xếp LIFO ở bên trái, ngăn xếp hiện chứa {2, 1}. Khi điều khiển một lần nữa chuyển đến Quét bảng, nó báo cáo không còn hàng nào nữa và điều khiển quay trở lại toán tử Ghép nối, mở ra đầu vào thứ hai (nó cần một hàng để chuyển đến bộ đệm ngăn xếp) và điều khiển chuyển đến Kết nối bên trong lần đầu tiên

Tham gia bên trong gọi Bộ đệm bảng trên đầu vào bên ngoài của nó, nó đọc hàng trên cùng từ ngăn xếp {2} và xóa nó khỏi bàn làm việc. Bây giờ ngăn xếp chứa {1}.

Đã nhận được một hàng trên đầu vào bên ngoài của nó, Internal Join chuyển điều khiển xuống đầu vào bên trong của nó sang Left Anti-Semi Join (LASJ). Điều này yêu cầu một hàng từ đầu vào bên ngoài của nó, chuyển điều khiển đến Sắp xếp. Sắp xếp là một trình vòng lặp chặn, vì vậy nó đọc tất cả các hàng từ biến bảng và sắp xếp chúng tăng dần (khi nó xảy ra).

Do đó, hàng đầu tiên được phát ra bởi Sắp xếp là giá trị {1}. Phía bên trong của LASJ trả về giá trị hiện tại của thành viên đệ quy (giá trị vừa bật ra khỏi ngăn xếp), đó là {2}. Các giá trị tại LASJ là {1} và {2} vì vậy {1} được phát ra, vì các giá trị không khớp.

Hàng này {1} chảy lên cây kế hoạch truy vấn vào Spool Index (Stack) nơi nó được thêm vào ngăn xếp, hiện chứa {1, 1} và được phát ra cho máy khách. Hiện tại khách hàng đã nhận được chuỗi {1}, {2}, {1}.

Kiểm soát bây giờ quay trở lại Ghép nối, quay trở lại phía bên trong (nó đã trả lại một hàng lần trước, có thể làm lại), thông qua Kết nối bên trong, đến LASJ. Nó đọc lại đầu vào bên trong của nó một lần nữa, lấy giá trị {2} từ Sắp xếp.

Thành viên đệ quy vẫn là {2}, vì vậy lần này LASJ tìm thấy {2} và {2}, dẫn đến không có hàng nào được phát ra. Không tìm thấy thêm hàng nào trong đầu vào bên trong của nó (Sắp xếp hiện không có hàng), điều khiển chuyển ngược lại đến Tham gia bên trong.

Bên trong tham gia đọc đầu vào bên ngoài của nó, dẫn đến giá trị {1} được bật ra khỏi ngăn xếp {1, 1}, để lại ngăn xếp chỉ với {1}. Quá trình bây giờ lặp lại, với giá trị {2} từ một lệnh gọi mới của Quét bảng và Sắp xếp vượt qua kiểm tra LASJ và được thêm vào ngăn xếp, và chuyển đến máy khách, hiện đã nhận được {1}, {2}, {1}, {2} ... và chúng ta đi vòng.

Giải thích yêu thích của tôi về bộ đệm Stack được sử dụng trong các kế hoạch CTE đệ quy là của Craig Freedman.


31

Mô tả BOL của CTE đệ quy mô tả ngữ nghĩa của thực thi đệ quy như sau:

  1. Chia biểu thức CTE thành neo và các thành viên đệ quy.
  2. Chạy (các) thành viên neo tạo tập lệnh đầu tiên hoặc kết quả cơ sở (T0).
  3. Chạy (các) thành viên đệ quy với Ti làm đầu vào và Ti + 1 làm đầu ra.
  4. Lặp lại bước 3 cho đến khi một bộ trống được trả về.
  5. Trả về tập kết quả. Đây là ĐOÀN TẤT CẢ T0 đến Tn.

Lưu ý ở trên là một mô tả hợp lý . Thứ tự vật lý của các hoạt động có thể hơi khác nhau như được minh họa ở đây

Áp dụng điều này cho CTE của bạn, tôi sẽ mong đợi một vòng lặp vô hạn với mẫu sau

+-----------+---------+---+---+---+
| Invocation| Results             |
+-----------+---------+---+---+---+
|         1 |       1 | 2 | 3 |   |
|         2 |       4 | 5 |   |   |
|         3 |       1 | 2 | 3 |   |
|         4 |       4 | 5 |   |   |
|         5 |       1 | 2 | 3 |   |
+-----------+---------+---+---+---+ 

Bởi vì

select a
from cte
where a in (1,2,3)

là biểu thức Anchor. Điều này rõ ràng trở lại 1,2,3nhưT0

Sau đó biểu thức đệ quy chạy

select a
from cte
except
select a
from r

Với 1,2,3như đầu vào đó sẽ mang lại một sản lượng 4,5như T1sau đó cắm mà lại cho vòng tiếp theo của đệ quy sẽ trở lại 1,2,3và vân vân vô thời hạn.

Đây không phải là những gì thực sự xảy ra tuy nhiên. Đây là kết quả của 5 lần đầu tiên

+-----------+---------+---+---+---+
| Invocation| Results             |
+-----------+---------+---+---+---+
|         1 |       1 | 2 | 3 |   |
|         2 |       1 | 2 | 4 | 5 |
|         3 |       1 | 2 | 3 | 4 |
|         4 |       1 | 2 | 3 | 5 |
|         5 |       1 | 2 | 3 | 4 |
+-----------+---------+---+---+---+

Từ việc sử dụng OPTION (MAXRECURSION 1)và điều chỉnh tăng dần theo gia số 1có thể thấy rằng nó bước vào một chu kỳ trong đó mỗi cấp độ liên tiếp sẽ liên tục chuyển đổi giữa đầu ra 1,2,3,41,2,3,5.

Như thảo luận của @Quassnoi trong bài đăng trên blog này . Mẫu kết quả quan sát được như thể mỗi lần gọi đang thực hiện (1),(2),(3),(4),(5) EXCEPT (X)ở đâu Xlà hàng cuối cùng từ lần gọi trước.

Chỉnh sửa: Sau khi đọc câu trả lời xuất sắc của SQL Kiwi, rõ ràng cả lý do tại sao điều này xảy ra và đây không phải là toàn bộ câu chuyện trong đó vẫn còn vô số thứ còn lại trên ngăn xếp không bao giờ có thể được xử lý.

Neo phát ra 1,2,3cho khách hàng Nội dung ngăn xếp3,2,1

3 bật ra ngăn xếp, Nội dung ngăn xếp 2,1

LASJ trả về 1,2,4,5, Nội dung ngăn xếp5,4,2,1,2,1

5 bật ra ngăn xếp, Nội dung ngăn xếp 4,2,1,2,1

LASJ trả về 1,2,3,4 Nội dung ngăn xếp4,3,2,1,5,4,2,1,2,1

4 popped off stack, Stack Nội dung 3,2,1,5,4,2,1,2,1

LASJ trả về 1,2,3,5 Nội dung ngăn xếp5,3,2,1,3,2,1,5,4,2,1,2,1

5 bật ra ngăn xếp, Nội dung ngăn xếp 3,2,1,3,2,1,5,4,2,1,2,1

LASJ trả về 1,2,3,4 Nội dung ngăn xếp 4,3,2,1,3,2,1,3,2,1,5,4,2,1,2,1

Nếu bạn thử và thay thế thành viên đệ quy bằng biểu thức tương đương logic (trong trường hợp không có trùng lặp / NULL)

select a
from (
    select a
    from cte
    where a not in 
    (select a
    from r)
) x

Điều này không được phép và gây ra lỗi "Tham chiếu đệ quy không được phép trong các truy vấn con." vì vậy có lẽ đó là một sự giám sát EXCEPTthậm chí được cho phép trong trường hợp này.

Ngoài ra: Microsoft hiện đã trả lời Kết nối phản hồi của tôi như dưới đây

Dự đoán của Jack là chính xác: đây có thể là một lỗi cú pháp; tài liệu tham khảo đệ quy nên thực sự không được phép trong EXCEPTcác mệnh đề. Chúng tôi dự định sẽ giải quyết lỗi này trong bản phát hành dịch vụ sắp tới. Trong khi đó, tôi sẽ đề nghị tránh các tài liệu tham khảo đệ quy trong EXCEPT mệnh đề.

Để hạn chế đệ quy trên, EXCEPTchúng tôi tuân theo tiêu chuẩn SQL ANSI, bao gồm hạn chế này kể từ khi đệ quy được đưa ra (năm 1999 tôi tin). Không có thỏa thuận rộng rãi về những gì ngữ nghĩa nên dành cho đệ quy EXCEPT(còn gọi là "phủ định không phân loại") trong các ngôn ngữ khai báo như SQL. Ngoài ra, rất khó để thực hiện một cách hiệu quả ngữ nghĩa như vậy (đối với các cơ sở dữ liệu có kích thước hợp lý) trong một hệ thống RDBMS.

Và có vẻ như việc triển khai cuối cùng đã được thực hiện vào năm 2014 cho các cơ sở dữ liệu có mức độ tương thích từ 120 trở lên .

Tham chiếu đệ quy trong mệnh đề EXCEPT tạo ra lỗi tuân thủ tiêu chuẩn SQL ANSI.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.