SQL सर्वर (UDFs) में उपयोगकर्ता-परिभाषित फ़ंक्शन प्रमुख ऑब्जेक्ट हैं जिनके बारे में प्रत्येक डेवलपर को अवगत होना चाहिए। हालांकि वे कई परिदृश्यों में बहुत उपयोगी हैं (WHERE क्लॉज, कंप्यूटेड कॉलम और चेक बाधाएं), फिर भी उनकी कुछ सीमाएं और खराब प्रथाएं हैं जो प्रदर्शन के मुद्दों का कारण बन सकती हैं। मल्टी-स्टेटमेंट यूडीएफ महत्वपूर्ण प्रदर्शन प्रभाव डाल सकते हैं, और यह लेख विशेष रूप से इन परिदृश्यों पर चर्चा करेगा।
फ़ंक्शन को उसी तरह लागू नहीं किया जाता है जैसे ऑब्जेक्ट-ओरिएंटेड भाषाओं में, हालांकि इनलाइन टेबल-वैल्यू फ़ंक्शंस का उपयोग परिदृश्यों में किया जा सकता है जब आपको पैरामीटरयुक्त दृश्यों की आवश्यकता होती है, यह उन फ़ंक्शंस पर लागू नहीं होता है जो स्केलर या टेबल लौटाते हैं। इन कार्यों को सावधानीपूर्वक उपयोग करने की आवश्यकता है क्योंकि वे बहुत अधिक प्रदर्शन समस्याएं पैदा कर सकते हैं। हालाँकि, वे कई मामलों में आवश्यक हैं, इसलिए हमें उनके कार्यान्वयन पर अधिक ध्यान देने की आवश्यकता होगी। फ़ंक्शन का उपयोग बैचों, प्रक्रियाओं, ट्रिगर्स या दृश्यों के अंदर, एड-हॉक SQL क्वेरीज़ के अंदर, या PowerBI या झांकी जैसे टूल द्वारा उत्पन्न रिपोर्टिंग क्वेरीज़ के एक भाग के रूप में, परिकलित फ़ील्ड में, और बाधाओं की जाँच के लिए SQL कथनों में किया जाता है। जबकि स्केलर फ़ंक्शन 32 स्तरों तक पुनरावर्ती हो सकते हैं, तालिका फ़ंक्शन पुनरावर्तन का समर्थन नहीं करते हैं।
SQL सर्वर में प्रकार्यों के प्रकार
SQL सर्वर में, हमारे पास तीन फ़ंक्शन प्रकार हैं:उपयोगकर्ता-परिभाषित स्केलर फ़ंक्शन (SFs) जो एकल स्केलर मान लौटाते हैं, उपयोगकर्ता-परिभाषित तालिका-मूल्यवान फ़ंक्शन (TVFs) जो एक तालिका लौटाते हैं, और इनलाइन तालिका-मूल्यवान फ़ंक्शन (ITVFs) जो कि एक स्केलर मान लौटाते हैं। कोई कार्य निकाय नहीं है। टेबल फ़ंक्शंस इनलाइन या मल्टी-स्टेटमेंट हो सकते हैं। इनलाइन फ़ंक्शंस में रिटर्न वेरिएबल्स नहीं होते हैं, वे केवल वैल्यू फ़ंक्शंस लौटाते हैं। मल्टी-स्टेटमेंट फ़ंक्शन कोड के BEGIN-END ब्लॉक में समाहित होते हैं और इसमें कई T-SQL स्टेटमेंट हो सकते हैं जो कोई साइड इफेक्ट नहीं पैदा करते हैं (जैसे किसी तालिका में सामग्री को संशोधित करना)।
हम प्रत्येक प्रकार के फ़ंक्शन को एक साधारण उदाहरण में दिखाएंगे:
/**
inline table function
**/
CREATE FUNCTION dbo.fnInline( @P1 INT, @P2 VARCHAR(50) )
RETURNS TABLE
AS
RETURN ( SELECT @P1 AS P_OUT_1, @P2 AS P_OUT_2 )
/**
multi-statement table function
**/
CREATE FUNCTION dbo.fnMultiTable( @P1 INT, @P2 VARCHAR(50) )
RETURNS @r_table TABLE ( OUT_1 INT, OUT_2 VARCHAR(50) )
AS
BEGIN
INSERT @r_table SELECT @P1, @P2;
RETURN;
END;
/**
scalar function
**/
CREATE FUNCTION dbo.fnScalar( @P1 INT, @P2 INT )
RETURNS INT
AS
BEGIN
RETURN @P1 + @P2
END
SQL सर्वर फ़ंक्शन सीमाएं
जैसा कि परिचय में बताया गया है, फ़ंक्शन उपयोग में कुछ सीमाएं हैं, और मैं नीचे कुछ ही खोजूंगा। पूरी सूची Microsoft Docs पर देखी जा सकती है :
- अस्थायी कार्यों की कोई अवधारणा नहीं है
- आप किसी अन्य डेटाबेस में फ़ंक्शन नहीं बना सकते हैं, लेकिन, आपके विशेषाधिकारों के आधार पर, आप इसे एक्सेस कर सकते हैं
- यूडीएफ के साथ, आपको डेटाबेस की स्थिति बदलने वाली कोई भी कार्रवाई करने की अनुमति नहीं है,
- यूडीएफ के अंदर, आप विस्तारित संग्रहीत प्रक्रिया को छोड़कर, एक प्रक्रिया को कॉल नहीं कर सकते हैं
- UDF एक परिणाम सेट नहीं लौटा सकता, लेकिन केवल एक तालिका डेटा प्रकार
- आप यूडीएफ में गतिशील एसक्यूएल या अस्थायी तालिकाओं का उपयोग नहीं कर सकते हैं
- यूडीएफ त्रुटि प्रबंधन क्षमताओं में सीमित हैं - वे RAISERROR और न ही TRY… CATCH का समर्थन नहीं करते हैं और आप सिस्टम @ERROR चर से डेटा प्राप्त नहीं कर सकते हैं
मल्टी-स्टेटमेंट फ़ंक्शंस में क्या अनुमति है?
केवल निम्नलिखित चीजों की अनुमति है:
- असाइनमेंट विवरण
- TRY…CATCH ब्लॉक को छोड़कर सभी फ्लो कंट्रोल स्टेटमेंट
- स्थानीय चर और कर्सर बनाने के लिए उपयोग की जाने वाली कॉलों की घोषणा करें
- आप उन चुनिंदा प्रश्नों का उपयोग कर सकते हैं जिनमें अभिव्यक्तियों के साथ सूचियां हैं और इन मानों को स्थानीय रूप से घोषित चर के लिए असाइन करें
- कर्सर केवल स्थानीय तालिकाओं को संदर्भित कर सकते हैं और उन्हें फ़ंक्शन बॉडी के अंदर खोलना और बंद करना होगा। FETCH केवल स्थानीय चर के मान निर्दिष्ट या बदल सकता है, डेटाबेस डेटा पुनर्प्राप्त या परिवर्तित नहीं कर सकता
मल्टी-स्टेटमेंट फंक्शन्स में किन चीजों से बचना चाहिए, हालांकि इसकी अनुमति है?
- आपको उन परिदृश्यों से बचना चाहिए जहां आप स्केलर फ़ंक्शन के साथ गणना किए गए कॉलम का उपयोग कर रहे हैं - इससे इंडेक्स का पुनर्निर्माण होगा और धीमी गति से अपडेट होंगे जिनके लिए पुनर्गणना की आवश्यकता होगी
- विचार करें कि किसी भी मल्टी-स्टेटमेंट फ़ंक्शन की अपनी निष्पादन योजना और प्रदर्शन प्रभाव होता है
- मल्टी-स्टेटमेंट टेबल-वैल्यूड UDF, यदि SQL एक्सप्रेशन या जॉइन स्टेटमेंट में उपयोग किया जाता है, तो गैर-इष्टतम निष्पादन योजना के कारण धीमा हो जाएगा
- WHERE स्टेटमेंट्स और ON क्लॉज़ में स्केलर फ़ंक्शंस का उपयोग न करें जब तक कि आप सुनिश्चित न हों कि यह एक छोटे डेटासेट को क्वेरी करेगा, और वह डेटासेट भविष्य में छोटा रहेगा
फ़ंक्शन के नाम और पैरामीटर
किसी भी अन्य ऑब्जेक्ट नाम की तरह, फ़ंक्शन नामों को पहचानकर्ताओं के नियमों का पालन करना होगा और उनकी स्कीमा के भीतर अद्वितीय होना चाहिए। यदि आप अदिश फ़ंक्शन बना रहे हैं, तो आप उन्हें EXECUTE कथन का उपयोग करके चला सकते हैं। इस मामले में, आपको स्कीमा नाम को फ़ंक्शन नाम में रखने की आवश्यकता नहीं है। नीचे दिए गए EXECUTE फ़ंक्शन कॉल का उदाहरण देखें (हम एक ऐसा फ़ंक्शन बनाते हैं जो एक महीने में Nth दिन की घटना देता है और फिर इस डेटा को पुनः प्राप्त करता है):
CREATE FUNCTION dbo.fnGetDayofWeekInMonth
(
@YearInput VARCHAR(50),
@MonthInput VARCHAR(50), -- English months ( 'Jan', 'Feb', ... )
@WeekDayInput VARCHAR(50)='Mon', -- Mon, Tue, Wed, Thu, Fri, Sat, Sun
@CountN INT=1 -- 1 for the first date, 2 for the second occurrence, 3 for the third
)
RETURNS DATETIME
AS
BEGIN
RETURN DATEADD(MONTH, DATEDIFF(MONTH, 0,
CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0)+ (7*@CountN)-1
-
(DATEPART (WEEKDAY, DATEADD(MONTH, DATEDIFF(MONTH, 0,
CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0))
[email protected]@DateFirst+(CHARINDEX(@WeekDayInput,'FriThuWedTueMonSunSat')-1)/3)%7
END
-- In SQL Server 2012 and later versions, you can use the EXECUTE command or the SELECT command to run a UDF, or use a standard approach
DECLARE @ret DateTime
EXEC @ret = fnGetDayofWeekInMonth '2020', 'Jan', 'Mon',2
SELECT @ret AS Third_Monday_In_January_2020
SELECT dbo.fnGetDayofWeekInMonth('2020', 'Jan', DEFAULT, DEFAULT)
AS 'Using default',
dbo.fnGetDayofWeekInMonth('2020', 'Jan', 'Mon', 2) AS 'explicit'
हम फ़ंक्शन मापदंडों के लिए डिफ़ॉल्ट को परिभाषित कर सकते हैं, उन्हें "@" के साथ उपसर्ग करना होगा और पहचानकर्ता नामकरण नियमों के अनुरूप होना चाहिए। पैरामीटर केवल स्थिर मान हो सकते हैं, उनका उपयोग टेबल, दृश्य, कॉलम या अन्य डेटाबेस ऑब्जेक्ट्स के बजाय SQL क्वेरी में नहीं किया जा सकता है, और मान अभिव्यक्ति नहीं हो सकते हैं, यहां तक कि नियतात्मक भी नहीं हो सकते हैं। TIMESTAMP डेटा प्रकार को छोड़कर, सभी डेटा प्रकारों की अनुमति है, और तालिका-मूल्यवान पैरामीटर को छोड़कर, किसी भी गैर-स्केलर डेटा प्रकार का उपयोग नहीं किया जा सकता है। "मानक" फ़ंक्शन कॉल में, यदि आप अंतिम-उपयोगकर्ता को पैरामीटर वैकल्पिक बनाने की क्षमता देना चाहते हैं, तो आपको DEFAULT विशेषता निर्दिष्ट करनी होगी। नए संस्करणों में, EXECUTE सिंटैक्स का उपयोग करते हुए, अब इसकी आवश्यकता नहीं है, आप फ़ंक्शन कॉल में इस पैरामीटर को दर्ज नहीं करते हैं। यदि हम कस्टम टेबल प्रकारों का उपयोग कर रहे हैं, तो उन्हें रीडऑनली के रूप में चिह्नित करना होगा, जिसका अर्थ है कि हम फ़ंक्शन के अंदर प्रारंभिक मान नहीं बदल सकते हैं, लेकिन उनका उपयोग अन्य मापदंडों की गणना और परिभाषाओं में किया जा सकता है।
SQL सर्वर फ़ंक्शन प्रदर्शन
पिछले अध्याय के कार्यों का उपयोग करते हुए, हम इस लेख में अंतिम विषय को शामिल करेंगे, वह है कार्य प्रदर्शन। हम इस फ़ंक्शन का विस्तार करेंगे और निष्पादन समय और निष्पादन योजनाओं की गुणवत्ता की निगरानी करेंगे। हम अन्य फ़ंक्शन संस्करण बनाकर शुरू करते हैं, और उनकी तुलना जारी रखते हैं:
CREATE FUNCTION dbo.fnGetDayofWeekInMonthBound
(
@YearInput VARCHAR(50),
@MonthInput VARCHAR(50), -- English months ( 'Jan', 'Feb', ... )
@WeekDayInput VARCHAR(50)='Mon', -- Mon, Tue, Wed, Thu, Fri, Sat, Sun
@CountN INT=1 -- 1 for the first date, 2 for the second occurrence, 3 for the third
)
RETURNS DATETIME
WITH SCHEMABINDING
AS
BEGIN
RETURN DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0)+ (7*@CountN)-1
-(DATEPART (WEEKDAY, DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0))
[email protected]@DateFirst+(CHARINDEX(@WeekDayInput,'FriThuWedTueMonSunSat')-1)/3)%7
END
GO
CREATE FUNCTION dbo.fnNthDayOfWeekOfMonthInline (
@YearInput VARCHAR(50),
@MonthInput VARCHAR(50), -- English months ( 'Jan', 'Feb', ... )
@WeekDayInput VARCHAR(50)='Mon', -- Mon, Tue, Wed, Thu, Fri, Sat, Sun
@CountN INT=1 -- 1 for the first date, 2 for the second occurence, 3 for the third
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0)+ (7*@CountN)-1
-(DATEPART (WEEKDAY, DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0))
[email protected]@DateFirst+(CHARINDEX(@WeekDayInput,'FriThuWedTueMonSunSat')-1)/3)%7 AS TheDate)
GO
CREATE FUNCTION dbo.fnNthDayOfWeekOfMonthTVF (
@YearInput VARCHAR(50),
@MonthInput VARCHAR(50), -- English months ( 'Jan', 'Feb', ... )
@WeekDayInput VARCHAR(50)='Mon', -- Mon, Tue, Wed, Thu, Fri, Sat, Sun
@CountN INT=1 -- 1 for the first date, 2 for the second occurence, 3 for the third
)
RETURNS @When TABLE (TheDate DATETIME)
WITH schemabinding
AS
Begin
INSERT INTO @When(TheDate)
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0)+ (7*@CountN)-1
-(DATEPART (WEEKDAY, DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '[email protected]+' '[email protected],113)), 0))
[email protected]@DateFirst+(CHARINDEX(@WeekDayInput,'FriThuWedTueMonSunSat')-1)/3)%7
RETURN
end
GO
कुछ परीक्षण कॉल और परीक्षण मामले बनाएं
हम तालिका संस्करणों से शुरू करते हैं:
SELECT * FROM dbo.fnNthDayOfWeekOfMonthTVF('2020','Feb','Tue',2)
SELECT TheYear, CONVERT(NCHAR(11),(SELECT TheDate FROM dbo.fnNthDayOfWeekOfMonthTVF(TheYear,'Feb','Tue',2)),113) FROM (VALUES ('2014'),('2015'),('2016'),('2017'),('2018'),('2019'),('2020'),('2021'))years(TheYear)
SELECT TheYear, CONVERT(NCHAR(11),TheDate,113) FROM (VALUES ('2014'),('2015'),('2016'),('2017'),('2018'),('2019'),('2020'),('2021'))years(TheYear)
OUTER apply dbo.fnNthDayOfWeekOfMonthTVF(TheYear,'Feb','Tue',2)
परीक्षण डेटा बनाना:
IF EXISTS(SELECT * FROM tempdb.sys.tables WHERE name LIKE '#DataForTest%')
DROP TABLE #DataForTest
GO
SELECT *
INTO #DataForTest
FROM (VALUES ('2014'),('2015'),('2016'),('2017'),('2018'),('2019'),('2020'),('2021'))years(TheYear)
CROSS join (VALUES ('jan'),('feb'),('mar'),('apr'),('may'),('jun'),('jul'),('aug'),('sep'),('oct'),('nov'),('dec'))months(Themonth)
CROSS join (VALUES ('Mon'),('Tue'),('Wed'),('Thu'),('Fri'),('Sat'),('Sun'))day(TheDay)
CROSS join (VALUES (1),(2),(3),(4))nth(nth)
परीक्षण प्रदर्शन:
DECLARE @TableLog TABLE (OrderVal INT IDENTITY(1,1), Reason VARCHAR(500), TimeOfEvent DATETIME2 DEFAULT GETDATE())
समय की शुरुआत:
INSERT INTO @TableLog(Reason) SELECT 'Starting My_Section_of_code' --place at the start
सबसे पहले, हम आधार रेखा प्राप्त करने के लिए किसी भी प्रकार के फ़ंक्शन का उपयोग नहीं करते हैं:
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '+TheMonth+' '+TheYear,113)), 0)+ (7*Nth)-1
-(DATEPART (WEEKDAY, DATEADD(MONTH, DATEDIFF(MONTH, 0, CONVERT(DATE,'1 '+TheMonth+' '+TheYear,113)), 0))
[email protected]@DateFirst+(CHARINDEX(TheDay,'FriThuWedTueMonSunSat')-1)/3)%7 AS TheDate
INTO #Test0
FROM #DataForTest
INSERT INTO @TableLog(Reason) SELECT 'Using the code entirely unwrapped';
अब हम एक इनलाइन तालिका-मूल्यवान फ़ंक्शन क्रॉस-एप्लाइड का उपयोग करते हैं:
SELECT TheYear, CONVERT(NCHAR(11),TheDate,113) AS itsdate
INTO #Test1
FROM #DataForTest
CROSS APPLY dbo.fnNthDayOfWeekOfMonthTVF(TheYear,TheMonth,TheDay,nth)
INSERT INTO @TableLog(Reason) SELECT 'Inline function cross apply'
हम एक इनलाइन तालिका-मूल्यवान फ़ंक्शन क्रॉस-एप्लाइड का उपयोग करते हैं:
SELECT TheYear, CONVERT(NCHAR(11),(SELECT TheDate FROM dbo.fnNthDayOfWeekOfMonthTVF(TheYear,TheMonth,TheDay,nth)),113) AS itsDate
INTO #Test2
FROM #DataForTest
INSERT INTO @TableLog(Reason) SELECT 'Inline function Derived table'
अविश्वसनीय की तुलना करने के लिए, हम स्कीमाबाइंडिंग के साथ एक अदिश फ़ंक्शन का उपयोग करते हैं:
SELECT TheYear, CONVERT(NCHAR(11), dbo.fnGetDayofWeekInMonthBound(TheYear,TheMonth,TheDay,nth))itsdate
INTO #Test3
FROM #DataForTest
INSERT INTO @TableLog(Reason) SELECT 'Trusted (Schemabound) scalar function'
इसके बाद, हम स्कीमा बाइंडिंग के बिना एक अदिश फ़ंक्शन का उपयोग करते हैं:
SELECT TheYear, CONVERT(NCHAR(11), dbo.fnGetDayofWeekInMonth(TheYear,TheMonth,TheDay,nth))itsdate
INTO #Test6
FROM #DataForTest
INSERT INTO @TableLog(Reason) SELECT 'Untrusted scalar function'
फिर, मल्टी-स्टेटमेंट टेबल फंक्शन व्युत्पन्न हुआ:
SELECT TheYear, CONVERT(NCHAR(11),(SELECT TheDate FROM dbo.fnNthDayOfWeekOfMonthTVF(TheYear,TheMonth,TheDay,nth)),113) AS itsdate
INTO #Test4
FROM #DataForTest
INSERT INTO @TableLog(Reason) SELECT 'multi-statement table function derived'
अंत में, मल्टी-स्टेटमेंट टेबल क्रॉस-एप्लाइड:
SELECT TheYear, CONVERT(NCHAR(11),TheDate,113) AS itsdate
INTO #Test5
FROM #DataForTest
CROSS APPLY dbo.fnNthDayOfWeekOfMonthTVF(TheYear,TheMonth,TheDay,nth)
INSERT INTO @TableLog(Reason) SELECT 'multi-statement cross APPLY'--where the routine you want to time ends
सभी समयों की सूची बनाएं:
SELECT ending.Reason AS Test, DateDiff(ms, starting.TimeOfEvent,ending.TimeOfEvent) [AS Time (ms)] FROM @TableLog starting
INNER JOIN @TableLog ending ON ending.OrderVal=starting.OrderVal+1
DROP table #Test0
DROP table #Test1
DROP table #Test2
DROP table #Test3
DROP table #Test4
DROP table #Test5
DROP table #Test6
DROP TABLE #DataForTest
उपरोक्त तालिका स्पष्ट रूप से दिखाती है कि जब आप उपयोगकर्ता द्वारा परिभाषित कार्यों का उपयोग कर रहे हों तो आपको प्रदर्शन बनाम कार्यक्षमता पर विचार करना चाहिए।
निष्कर्ष
फ़ंक्शंस कई डेवलपर्स द्वारा पसंद किए जाते हैं, ज्यादातर इसलिए क्योंकि वे "तार्किक निर्माण" हैं। आप आसानी से टेस्ट केस बना सकते हैं, वे नियतात्मक और एनकैप्सुलेटिंग हैं, वे SQL कोड प्रवाह के साथ अच्छी तरह से एकीकृत होते हैं और पैरामीटराइजेशन में लचीलेपन की अनुमति देते हैं। वे एक अच्छा विकल्प हैं जब आपको जटिल तर्क को लागू करने की आवश्यकता होती है जिसे छोटे या पहले से फ़िल्टर किए गए डेटासेट पर करने की आवश्यकता होती है जिसे आपको कई परिदृश्यों में पुन:उपयोग करने की आवश्यकता होगी। इनलाइन तालिका दृश्यों का उपयोग उन दृश्यों में किया जा सकता है जिन्हें पैरामीटर की आवश्यकता होती है, विशेष रूप से ऊपरी परतों (क्लाइंट-फेसिंग एप्लिकेशन) से। दूसरी ओर, स्केलर फ़ंक्शंस XML या अन्य पदानुक्रमित स्वरूपों के साथ काम करने के लिए बहुत अच्छे हैं, क्योंकि उन्हें पुनरावर्ती रूप से कहा जा सकता है।
यूज़र-डिफ़ाइंड मल्टी-स्टेटमेंट फ़ंक्शंस आपके डेवलपमेंट टूल स्टैक के लिए एक बढ़िया अतिरिक्त हैं, लेकिन आपको यह समझना होगा कि वे कैसे काम करते हैं और उनकी सीमाएँ और प्रदर्शन चुनौतियाँ क्या हैं। उनका गलत उपयोग किसी भी डेटाबेस के प्रदर्शन को नष्ट कर सकता है लेकिन यदि आप इन कार्यों का उपयोग करना जानते हैं, तो वे कोड के पुन:उपयोग और इनकैप्सुलेशन के लिए बहुत सारे लाभ ला सकते हैं।