चयन करते समय स्व संदर्भित स्केलर फ़ंक्शन नेस्टिंग स्तर पार हो गया


24

उद्देश्य

जब एक सेल्फ रेफरेंसिंग फ़ंक्शन का परीक्षण उदाहरण बनाने की कोशिश की जाती है, तो एक संस्करण विफल हो जाता है जबकि दूसरा सफल होता है।

SELECTफ़ंक्शन बॉडी में एक ही अंतर जोड़ा जा रहा है जिसके परिणामस्वरूप दोनों के लिए एक अलग निष्पादन योजना है।


जो कार्य करता है

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;

फ़ंक्शन को कॉल करना

SELECT dbo.test5(3);

रिटर्न

(No column name)
3

वह कार्य जो काम न करे

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;

फ़ंक्शन को कॉल करना

SELECT dbo.test6(3);

या

SELECT dbo.test6(2);

परिणाम में त्रुटि

अधिकतम संग्रहीत कार्यविधि, फ़ंक्शन, ट्रिगर, या नेस्टिंग स्तर पार हो गया (सीमा 32)।

कारण का अनुमान लगाना

कॉल करने में विफल फ़ंक्शन की अनुमानित योजना पर एक अतिरिक्त गणना स्केलर है

<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">

और expr1000 जा रहा है

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

जो 32 से अधिक पुनरावर्ती संदर्भों की व्याख्या कर सकता है।

वास्तविक सवाल

जोड़ा SELECTफंक्शन को खुद को बार-बार कॉल करता है, जिसके परिणामस्वरूप एक अंतहीन लूप होता है, लेकिन SELECTयह एक परिणाम क्यों जोड़ रहा है?


अतिरिक्त जानकारी

अनुमानित निष्पादन योजना

डीबी <> फिडल

Build version:
14.0.3045.24

संगतता 100 और 140 पर संगतता

जवाबों:


26

यह प्रोजेक्ट सामान्यीकरण में एक बग है , जो एक गैर-निर्धारक समारोह के साथ एक मामले की अभिव्यक्ति के अंदर एक उप-वर्ग का उपयोग करके उजागर किया गया है।

समझाने के लिए, हमें दो बातों पर ध्यान देना होगा:

  1. SQL सर्वर सीधे सबक्वेरी को निष्पादित नहीं कर सकता है, इसलिए वे हमेशा अनियंत्रित होते हैं या एक आवेदन में परिवर्तित होते हैं
  2. इस प्रकार के शब्दार्थ CASEऐसे होते हैं कि THENयदि WHENखंड सही निकलता है तो एक अभिव्यक्ति का मूल्यांकन किया जाना चाहिए ।

समस्याग्रस्त मामले में पेश किया गया (तुच्छ) उपशम इसलिए परिणामी ऑपरेटर (नेस्टेड लूप्स जॉइन) होता है। दूसरी आवश्यकता को पूरा करने के लिए, SQL सर्वर शुरू में एक्सप्रेशन dbo.test6(1) + dbo.test6(2)के अंदरूनी हिस्से को रखता है :

हाइलाइट किए गए गणना स्केलर

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

... शामिल होने पर विधेय के माध्यम सेCASE सम्मानित किए गए शब्दार्थ के साथ :

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

लूप के आंतरिक पक्ष का मूल्यांकन केवल तभी किया जाता है जब पास-थ्रू स्थिति झूठी (अर्थ @i = 3) का मूल्यांकन करती है । यह सब अब तक सही है। नेस्टेड लूप्स के बाद होने वाली गणना स्केलर भी CASEशब्दार्थ को सही ढंग से सम्मानित करती है:

[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)

समस्या यह है कि क्वेरी संकलन का प्रोजेक्ट सामान्यीकरण चरण Expr1000असंबद्ध है और यह निर्धारित करता है कि यह सुरक्षित होगा ( कथावाचक: यह नहीं है ) इसे लूप के बाहर ले जाने के लिए:

स्थानांतरित परियोजना

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

यह * पास-थ्रू विधेय द्वारा लागू किए गए शब्दार्थ को तोड़ता है, इसलिए फ़ंक्शन का मूल्यांकन तब किया जाता है जब यह नहीं होना चाहिए, और एक अनंत लूप परिणाम।

आपको इस बग की रिपोर्ट करनी चाहिए। एक वर्कअराउंड अभिव्यक्ति को सहसंबद्ध (यानी @iअभिव्यक्ति में शामिल) करके इसे बाहर ले जाने से रोकने के लिए है, लेकिन यह निश्चित रूप से हैक है। प्रोजेक्ट सामान्यीकरण को अक्षम करने का एक तरीका है, लेकिन मुझे सार्वजनिक रूप से साझा नहीं करने के लिए कहा गया है, इसलिए मैं नहीं करूंगा।

यह समस्या SQL सर्वर 2019 में उत्पन्न नहीं होती है जब स्केलर फ़ंक्शन इनलाइन होता है , क्योंकि इनलाइनिंग तर्क सीधे पार्स किए गए पेड़ पर संचालित होता है (अच्छी तरह से परियोजना के सामान्य होने से पहले)। गैर-पुनरावर्ती को inlining तर्क द्वारा प्रश्न में सरल तर्क को सरल बनाया जा सकता है:

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

... जो 3 लौटाता है।

मुख्य मुद्दे को स्पष्ट करने का एक और तरीका है:

-- 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;

2008 R2 से 2019 CTP 3.0 तक सभी संस्करणों के नवीनतम बिल्ड पर पुन: प्रस्तुत करता है।

मार्टिन स्मिथ द्वारा प्रदान किया गया एक और उदाहरण (स्केलर फ़ंक्शन के बिना) :

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

इसके लिए आवश्यक सभी प्रमुख तत्व हैं:

  • CASE(आंतरिक रूप से कार्यान्वित ScaOp_IIF)
  • एक गैर-नियतात्मक कार्य ( CRYPT_GEN_RANDOM)
  • शाखा पर एक उपश्रेणी जिसे निष्पादित नहीं किया जाना चाहिए ( (SELECT ...))

* कड़ाई से, उपरोक्त परिवर्तन अभी भी सही हो सकता है यदि मूल्यांकन को Expr1000सही ढंग से स्थगित किया गया था, क्योंकि यह केवल सुरक्षित निर्माण द्वारा संदर्भित है:

[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)

... लेकिन इसके लिए एक आंतरिक ForceOrder ध्वज (क्वेरी संकेत नहीं) की आवश्यकता होती है, जो या तो सेट नहीं है। किसी भी मामले में, परियोजना के सामान्यीकरण द्वारा लागू किए गए तर्क का कार्यान्वयन गलत या अपूर्ण है।

SQL सर्वर के लिए Azure फ़ीडबैक साइट पर बग रिपोर्ट

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.