Mức tham chiếu hàm vô hướng tự vượt quá khi thêm một lựa chọn


24

Mục đích

Khi cố gắng tạo một ví dụ thử nghiệm về chức năng tự tham chiếu, một phiên bản thất bại trong khi một phiên bản khác thành công.

Sự khác biệt duy nhất là được thêm vào SELECTthân hàm dẫn đến một kế hoạch thực hiện khác nhau cho cả hai.


Chức năng hoạt động

CREATE FUNCTION dbo.test5(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN  dbo.test5(1) + dbo.test5(2)
END
)
END;

Gọi hàm

SELECT dbo.test5(3);

Trả về

(No column name)
3

Chức năng không hoạt động

CREATE FUNCTION dbo.test6(@i int)
RETURNS INT
AS 
BEGIN
RETURN(
SELECT TOP 1
CASE 
WHEN @i = 1 THEN 1
WHEN @i = 2 THEN 2
WHEN @i = 3 THEN (SELECT dbo.test6(1) + dbo.test6(2))
END
)END;

Gọi hàm

SELECT dbo.test6(3);

hoặc là

SELECT dbo.test6(2);

Kết quả là lỗi

Thủ tục lưu trữ tối đa, chức năng, kích hoạt hoặc xem mức lồng nhau vượt quá (giới hạn 32).

Đoán nguyên nhân

Có một phép tính vô hướng bổ sung trên sơ đồ ước tính của hàm bị lỗi, gọi

<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END">

Và expr1000 được

<ColumnReference Column="Expr1000" />
<ScalarOperator ScalarString="[dbo].[test6]((1))+[dbo].[test6]((2))">

Mà có thể giải thích các tài liệu tham khảo đệ quy vượt quá 32.

Câu hỏi thực tế

Việc thêm vào SELECTlàm cho hàm gọi chính nó lặp đi lặp lại, dẫn đến một vòng lặp vô tận, nhưng tại sao lại thêm một SELECTkết quả này?


thông tin bổ sung

Kế hoạch thực hiện dự kiến

DB <> Fiddle

Build version:
14.0.3045.24

Đã thử nghiệm về tính tương thích_levels 100 và 140

Câu trả lời:


26

Đây là một lỗi trong bình thường hóa dự án , được thể hiện bằng cách sử dụng một truy vấn con bên trong một biểu thức trường hợp với hàm không xác định.

Để giải thích, chúng ta cần lưu ý hai điều trước:

  1. SQL Server không thể thực thi các truy vấn con trực tiếp, vì vậy chúng luôn không được kiểm soát hoặc chuyển đổi thành một ứng dụng .
  2. Các ngữ nghĩa của CASEsao cho một THENbiểu thức chỉ nên được đánh giá nếu WHENmệnh đề trả về true.

Truy vấn con (tầm thường) được giới thiệu trong trường hợp có vấn đề do đó dẫn đến một toán tử áp dụng (tham gia các vòng lặp lồng nhau). Để đáp ứng yêu cầu thứ hai, SQL Server ban đầu đặt biểu thức dbo.test6(1) + dbo.test6(2)ở phía bên trong của ứng dụng:

đánh dấu vô hướng tính toán

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

... với CASEngữ nghĩa được vinh danh bởi một vị từ xuyên qua khi tham gia:

[@i]=(1) OR [@i]=(2) OR IsFalseOrNull [@i]=(3)

Phía bên trong của vòng lặp chỉ được đánh giá nếu điều kiện chuyển qua đánh giá là sai (có nghĩa @i = 3). Đây là tất cả chính xác cho đến nay. Các Tính vô hướng sau các vòng lồng nhau tham gia cũng vinh danh những CASEngữ nghĩa một cách chính xác:

[Expr1001] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

Vấn đề là giai đoạn chuẩn hóa dự án của quá trình biên dịch truy vấn thấy Expr1000không tương thích và xác định rằng nó sẽ an toàn (trình tường thuật: không ) để di chuyển nó ra ngoài vòng lặp:

chuyển dự án

[Expr1000] = Scalar Operator([dbo].[test6]((1))+[dbo].[test6]((2)))

Điều này phá vỡ * ngữ nghĩa được thực hiện bởi vị từ đi qua , do đó, hàm được đánh giá khi không nên và kết quả vòng lặp vô hạn.

Bạn nên báo cáo lỗi này. Một cách giải quyết là để ngăn chặn biểu thức được di chuyển ra ngoài ứng dụng bằng cách làm cho nó tương quan (nghĩa là bao gồm @itrong biểu thức) nhưng đây là một hack tất nhiên. Có một cách để vô hiệu hóa bình thường hóa dự án, nhưng tôi đã được yêu cầu trước đó không chia sẻ công khai, vì vậy tôi sẽ không.

Vấn đề này không phát sinh trong SQL Server 2019 khi hàm vô hướng được nội tuyến , bởi vì logic nội tuyến hoạt động trực tiếp trên cây được phân tích cú pháp (trước khi chuẩn hóa dự án). Logic đơn giản trong câu hỏi có thể được đơn giản hóa bằng logic nội tuyến thành không đệ quy:

[Expr1019] = (Scalar Operator((1)))
[Expr1045] = Scalar Operator(CONVERT_IMPLICIT(int,CONVERT_IMPLICIT(int,[Expr1019],0)+(2),0))

... Trả về 3.

Một cách khác để minh họa vấn đề cốt lõi là:

-- Not schema bound to make it non-det
CREATE OR ALTER FUNCTION dbo.Error() 
RETURNS integer 
-- WITH INLINE = OFF -- SQL Server 2019 only
AS
BEGIN
    RETURN 1/0;
END;
GO
DECLARE @i integer = 1;

SELECT
    CASE 
        WHEN @i = 1 THEN 1
        WHEN @i = 2 THEN 2
        WHEN @i = 3 THEN (SELECT dbo.Error()) -- 'subquery'
        ELSE NULL
    END;

Tái sản xuất các bản dựng mới nhất của tất cả các phiên bản từ 2008 R2 đến 2019 CTP 3.0.

Một ví dụ nữa (không có hàm vô hướng) do Martin Smith cung cấp :

SELECT IIF(@@TRANCOUNT >= 0, 1, (SELECT CRYPT_GEN_RANDOM(4)/ 0))

Điều này có tất cả các yếu tố chính cần thiết:

  • CASE(thực hiện trong nội bộ như ScaOp_IIF)
  • Hàm không xác định ( CRYPT_GEN_RANDOM)
  • Một truy vấn con trên nhánh không nên được thực thi ( (SELECT ...))

* Nghiêm túc, việc chuyển đổi ở trên vẫn có thể đúng nếu đánh giá Expr1000được hoãn lại một cách chính xác, vì nó chỉ được tham chiếu bởi cấu trúc an toàn:

[Expr1002] = Scalar Operator(CASE WHEN [@i]=(1) THEN (1) ELSE CASE WHEN [@i]=(2) THEN (2) ELSE CASE WHEN [@i]=(3) THEN [Expr1000] ELSE NULL END END END)

... nhưng điều này đòi hỏi một cờ ForceOrder nội bộ (không phải gợi ý truy vấn), cũng không được đặt. Trong mọi trường hợp, việc thực hiện logic được áp dụng bởi chuẩn hóa dự án là không chính xác hoặc không đầy đủ.

Báo cáo lỗi trên trang web Phản hồi Azure cho SQL Server.

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.