Sqlserver
 sql >> डेटाबेस >  >> RDS >> Sqlserver

बेहतर गतिशील SQL से बचने के लिए 10 SP_EXECUTESQL गोचास

क्या आप जानते हैं कि डायनेमिक SQL जैसा टूल कितना शक्तिशाली हो सकता है? इसे गलत तरीके से उपयोग करें, और आप किसी को अपना डेटाबेस लेने की अनुमति दे सकते हैं। साथ ही, बहुत अधिक जटिलता हो सकती है। इस लेख का उद्देश्य SP_EXECUTESQL का उपयोग करते समय होने वाली कमियों का परिचय देना है और इससे बचने के लिए 10 सबसे सामान्य गोच प्रदान करता है।

SP_EXECUTESQL उन तरीकों में से एक है जिससे आप एक स्ट्रिंग में एम्बेडेड SQL कमांड चला सकते हैं। आप इस स्ट्रिंग को कोड के माध्यम से गतिशील रूप से बनाते हैं। इसलिए हम इसे डायनेमिक SQL कहते हैं। बयानों की एक श्रृंखला के अलावा, आप इसमें मापदंडों और मूल्यों की एक सूची भी पास कर सकते हैं। वास्तव में, ये पैरामीटर और मान EXEC कमांड से भिन्न होते हैं। EXEC डायनेमिक SQL के लिए पैरामीटर स्वीकार नहीं करता है। फिर भी, आप EXEC का उपयोग करके SP_EXECUTESQL निष्पादित करते हैं!

डायनामिक SQL में नौसिखिया के लिए, यहां बताया गया है कि आप इसे कैसे लागू करते हैं।

EXEC sp_executesql <command string>[, <input or output parameters list>, <parameter value1>, <parameter value n>]

आप उन आदेशों की स्ट्रिंग बनाते हैं जिनमें मान्य SQL कथन शामिल हैं। वैकल्पिक रूप से, आप इनपुट या आउटपुट पैरामीटर और उनके डेटा प्रकारों की सूची पास कर सकते हैं। और अंत में, आप मूल्यों की अल्पविराम से अलग की गई सूची पास करते हैं। यदि आप पैरामीटर पास करते हैं, तो आपको मान पास करने की आवश्यकता है। बाद में, आप इसके सही और गलत दोनों उदाहरण देखेंगे, जैसा कि आप आगे पढ़ेंगे।

जब आपको इसकी आवश्यकता न हो तब SP_EXECUTESQL का उपयोग करना

ये सही है। यदि आपको इसकी आवश्यकता नहीं है, तो इसका उपयोग न करें। यदि यह SP_EXECUTESQL की 10 आज्ञाएँ बन जाती हैं, तो यह पहली आज्ञा है। ऐसा इसलिए है क्योंकि इस प्रणाली प्रक्रिया का आसानी से दुरुपयोग किया जा सकता है। लेकिन आप कैसे जानते हैं?

इसका उत्तर दें:यदि आपके डायनेमिक SQL में कमांड स्थिर हो जाती है तो क्या कोई समस्या है? यदि आपको इस बिंदु पर कुछ नहीं कहना है, तो आपको इसकी आवश्यकता नहीं है। उदाहरण देखें।

DECLARE @sql NVARCHAR(100) = N'SELECT ProductID, Name FROM Production.Product ' +
			      'WHERE ProductID = @ProductID';
DECLARE @paramsList NVARCHAR(100) = N'@ProductID INT';
DECLARE @param1Value INT = 1;

EXEC sp_executesql @sql, @paramsList, @param1Value
GO

जैसा कि आप देख सकते हैं, SP_EXECUTESQL का उपयोग कमांड स्ट्रिंग, पैरामीटर और मान के साथ पूरा हो गया है। लेकिन क्या ऐसा होना जरूरी है? पक्का नहीं। इसका होना बिल्कुल ठीक है:

DECLARE @productID INT = 1;

SELECT ProductID, Name
FROM Production.Product
WHERE ProductID = @productID;

लेकिन जब आप लेख में आगे उदाहरण देखेंगे तो मुझे शर्म आ सकती है। क्योंकि मैं इस पहले बिंदु में जो दावा कर रहा हूं उसका खंडन करूंगा। आप लघु गतिशील SQL कथन देखेंगे जो स्थैतिक से बेहतर हैं। तो, मेरे साथ सहन करें क्योंकि उदाहरण यहां बताए गए बिंदुओं को साबित करेंगे। बाकी उदाहरणों के लिए, कुछ समय के लिए दिखाएँ कि आप डायनेमिक SQL के लिए बनाए गए कोड को देख रहे हैं।

कार्यक्षेत्र से बाहर की वस्तुएं और चर

SP_EXECUTESQL का उपयोग करके एक स्ट्रिंग में SQL कमांड की एक श्रृंखला चलाना एक अनाम संग्रहीत कार्यविधि बनाने और उसे चलाने जैसा है। यह जानकर, अस्थायी तालिकाओं और कमांड स्ट्रिंग के बाहर चर जैसी वस्तुएं दायरे से बाहर हो जाएंगी। इस वजह से, रनटाइम त्रुटियाँ उत्पन्न होंगी।

SQL चर का उपयोग करते समय

इसे देखें।

DECLARE @extraText VARCHAR(10) = 'Name is '; -- note this variable
DECLARE @sql NVARCHAR(100) = N'SELECT @extraText + FirstName + SPACE(1) + LastName
                               FROM Person.Person WHERE BusinessEntityID = @BusinessEntityID';
DECLARE @paramList NVARCHAR(100) = N'@BusinessEntityID INT';
DECLARE @param1Value INT = 1;

EXEC sp_executesql @sql, @paramList, @BusinessEntityId = @param1Value;
GO

चर @अतिरिक्त पाठ निष्पादित आदेशों के लिए अदृश्य है। इसके बजाय, एक शाब्दिक स्ट्रिंग या डायनामिक SQL स्ट्रिंग के अंदर घोषित चर बहुत बेहतर है। वैसे भी, परिणाम यह है:

क्या आपने चित्र 1 में वह त्रुटि देखी? यदि आपको डायनेमिक SQL स्ट्रिंग के अंदर कोई मान पास करने की आवश्यकता है, तो दूसरा पैरामीटर जोड़ें।

अस्थायी तालिकाओं का उपयोग करते समय

SQL सर्वर में अस्थायी तालिकाएँ भी एक मॉड्यूल के दायरे में मौजूद होती हैं। तो, आप इस कोड के बारे में क्या सोचते हैं?

DECLARE @sql NVARCHAR(200) = N'SELECT BusinessEntityID, LastName, FirstName, MiddleName
                               INTO #TempNames
                               FROM Person.Person
                               WHERE BusinessEntityID BETWEEN 1 and 100';

EXEC sp_executesql @sql;
EXEC sp_executesql N'SELECT * FROM #TempNames'
GO

उपरोक्त कोड उत्तराधिकार में 2 संग्रहीत प्रक्रियाओं को निष्पादित कर रहा है। पहले डायनेमिक SQL से बनाई गई अस्थायी तालिका दूसरे के लिए सुलभ नहीं होगी। परिणामस्वरूप, आपको एक अवैध वस्तु का नाम #TempNames . मिलेगा त्रुटि।

SQL सर्वर QUOTENAME गलती

यहां एक और उदाहरण दिया गया है जो पहली नज़र में ठीक लगेगा लेकिन अमान्य ऑब्जेक्ट नाम त्रुटि का कारण होगा। नीचे दिए गए कोड को देखें।

DECLARE @sql NVARCHAR(100) = N'SELECT * FROM @Table';
DECLARE @tableName NVARCHAR(20) = 'Person.Person';

SET @sql = REPLACE(@sql,'@Table',QUOTENAME(@tableName));

PRINT @sql;
EXEC sp_executesql @sql;
GO

चयन के लिए मान्य होने के लिए, स्कीमा और तालिका को इस तरह वर्गाकार कोष्ठकों के साथ संलग्न करें:[स्कीमा]।[तालिका] . या उन्हें बिल्कुल भी संलग्न न करें (जब तक कि तालिका के नाम में एक या अधिक स्थान शामिल न हों)। उपरोक्त उदाहरण में, तालिका और स्कीमा दोनों के लिए किसी वर्ग कोष्ठक का उपयोग नहीं किया गया था। @टेबल . का उपयोग करने के बजाय एक पैरामीटर के रूप में, यह REPLACE के लिए प्लेसहोल्डर बन गया। QUOTENAME का उपयोग किया गया था।

QUOTENAME एक सीमांकक के साथ एक स्ट्रिंग संलग्न करता है। यह डायनेमिक SQL स्ट्रिंग में सिंगल कोट्स से निपटने के लिए भी अच्छा है। स्क्वायर ब्रैकेट डिफ़ॉल्ट डिलीमीटर है। तो, ऊपर के उदाहरण में, आपको क्या लगता है कि QUOTENAME ने क्या किया? नीचे चित्र 2 देखें।

SQL PRINT स्टेटमेंट ने डायनेमिक SQL स्ट्रिंग को प्रिंट करके समस्या को डीबग करने में हमारी मदद की। अब हम समस्या जानते हैं। इसे ठीक करने का सबसे अच्छा तरीका क्या है? एक समाधान नीचे दिया गया कोड है:

DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) FROM @Table';
DECLARE @tableName NVARCHAR(20) = 'Person.Person';

SET @sql = REPLACE(@sql,'@Table',QUOTENAME(PARSENAME(@tableName,2)) + '.'
                               + QUOTENAME(PARSENAME(@tableName,1)));
PRINT @sql;
EXEC sp_executesql @sql;
GO

आइए इसे समझाएं।

सबसे पहले, @टेबल REPLACE के लिए प्लेसहोल्डर के रूप में उपयोग किया जाता है। यह @Table . की घटना की खोज करेगा और इसे सही मानों से बदलें। क्यों? यदि इसे एक पैरामीटर के रूप में उपयोग किया जाता है, तो एक त्रुटि होगी। REPLACE का उपयोग करने का यही कारण भी है।

फिर, हमने PARSENAME का इस्तेमाल किया। इस फ़ंक्शन को हमने जो स्ट्रिंग दी है, वह इसे स्कीमा और टेबल से अलग कर देगी। PARSENAME(@tableName,2) स्कीमा मिलेगा। जबकि PARSENAME(@tableName,1) टेबल मिलेगा।

अंत में, PARSENAME हो जाने के बाद, QUOTENAME स्कीमा और तालिका नामों को अलग-अलग संलग्न करेगा। परिणाम [व्यक्ति] है।[व्यक्ति] . अब, यह मान्य है।

हालांकि, डायनेमिक SQL स्ट्रिंग को ऑब्जेक्ट नामों से साफ करने का एक बेहतर तरीका बाद में दिखाया जाएगा।

एक पूर्ण विवरण के साथ SP_EXECUTESQL निष्पादित करना

ऐसे दिन होते हैं जब आप परेशान या निराश होते हैं। रास्ते में गलतियाँ की जा सकती हैं। फिर मिश्रण और NULLs में एक लंबी गतिशील SQL स्ट्रिंग जोड़ें। और नतीजा?

कुछ नहीं।

SP_EXECUTESQL आपको एक खाली परिणाम देगा। कैसे? NULL को गलती से जोड़कर। नीचे दिए गए उदाहरण पर विचार करें:

DECLARE @crlf NCHAR(2);
DECLARE @sql NVARCHAR(200) = N'SELECT' + @crlf +
	' p.Name AS Product' + @crlf +
	',v.Name AS Vendor' + @crlf +
	',v.AccountNumber' + @crlf +
	',p.ListPrice' + @crlf +
	'FROM Purchasing.ProductVendor pv' + @crlf +
	'INNER JOIN Production.Product p ON pv.ProductID = p.ProductID' + @crlf +
	'INNER JOIN Purchasing.Vendor v ON pv.BusinessEntityID = v.BusinessEntityID' + @crlf +
	'WHERE pv.BusinessEntityID = @BusinessEntityID';
DECLARE @paramsList NVARCHAR(100) = N'@BusinessEntityID INT';
DECLARE @BusinessEntityID INT = 1500;

PRINT @sql;
EXEC sp_executesql @sql, @paramsList, @BusinessEntityID
GO

सबसे पहले, कोड अच्छा दिखता है। फिर भी हमारे बीच चील की आंखें @crlf . को नोटिस करेंगी चर। इसका मूल्य? चर प्रारंभ नहीं किया गया था। तो, यह शून्य है।

लेकिन उस चर का क्या मतलब है? बाद के अनुभाग में, आप जानेंगे कि स्वरूपण और डिबगिंग पर यह कितना महत्वपूर्ण है। अभी के लिए, आइए वर्तमान बिंदु पर ध्यान दें।

सबसे पहले, एक NULL वैरिएबल को डायनेमिक SQL स्ट्रिंग में संयोजित करने से NULL हो जाएगा। फिर, प्रिंट खाली प्रिंट होगा। अंत में, SP_EXECUTESQL NULL डायनेमिक SQL स्ट्रिंग के साथ ठीक चलेगा। लेकिन यह कुछ भी नहीं लौटाता है।

एनयूएलएल हमें पहले से ही बुरे दिन में मंत्रमुग्ध कर सकते हैं। एक छोटा ब्रेक लें। आराम करना। फिर एक स्पष्ट दिमाग के साथ वापस आएं।

इनलाइनिंग पैरामीटर मान

गन्दा।

डायनेमिक SQL स्ट्रिंग के लिए इनलाइनिंग मान इस तरह दिखेगा। स्ट्रिंग्स और तिथियों के लिए बहुत सारे सिंगल कोट्स होंगे। यदि आप सावधान नहीं हैं, तो ओ'ब्रायन्स और ओ'नील्स भी त्रुटियाँ पैदा करेंगे। और चूंकि डायनेमिक SQL एक स्ट्रिंग है, इसलिए आपको स्ट्रिंग में मानों को CONVERT या CAST करना होगा। यहां एक उदाहरण दिया गया है।

DECLARE @shipDate DATETIME = '06/11/2011';
 DECLARE @productID INT = 750;
 DECLARE @sql NVARCHAR(1000);
 SET @sql = N'SELECT
  soh.ShipDate
 ,sod.ProductID
 ,SUM(sod.OrderQty) AS TotalQty
 ,SUM(sod.LineTotal) AS LineTotal
 FROM Sales.SalesOrderHeader soh
 INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
 WHERE soh.ShipDate BETWEEN ' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + '''' + ' AND DATEADD(MONTH,1,' + '''' + CONVERT(VARCHAR(10), @shipDate, 101) + ''') ' +
 'AND sod.ProductID = ' + CAST(@productID AS VARCHAR(8)) +
 ' GROUP BY soh.ShipDate, sod.ProductID' +
 ' ORDER BY sod.ProductID';
 
 PRINT @sql;
 EXEC sp_executesql @sql;

मैंने इससे अधिक गड़बड़ गतिशील तार देखे। सिंगल कोट्स, कन्वर्ट और कास्ट पर ध्यान दें। यदि मापदंडों का उपयोग किया जाता है, तो यह बेहतर दिख सकता है। आप इसे नीचे देख सकते हैं

DECLARE @shipDate DATETIME = '06/11/2011';
 DECLARE @productID INT = 750;
 DECLARE @sql NVARCHAR(1000);
 DECLARE @paramList NVARCHAR(500) = N'@shipDate DATETIME, @productID INT';
 SET @sql = N'SELECT
  soh.ShipDate
 ,sod.ProductID
 ,SUM(sod.OrderQty) AS TotalQty
 ,SUM(sod.LineTotal) AS LineTotal
 FROM Sales.SalesOrderHeader soh
 INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
 WHERE soh.ShipDate BETWEEN @shipDate AND DATEADD(MONTH,1,@shipDate)
 AND sod.ProductID = @productID
  GROUP BY soh.ShipDate, sod.ProductID
  ORDER BY sod.ProductID';

PRINT @sql;
EXEC sp_executesql @sql, @paramList, @shipDate, @productID
GO

देखो? कम सिंगल कोट्स, कोई CONVERT और CAST नहीं, और क्लीनर भी।

लेकिन इनलाइन मूल्यों का और भी खतरनाक दुष्प्रभाव है।

एसक्यूएल इंजेक्शन

अगर हम ऐसी दुनिया में रहते जहां सभी लोग अच्छे होते, तो SQL इंजेक्शन के बारे में कभी नहीं सोचा जाता। लेकिन मामला वह नहीं है। कोई व्यक्ति आपके अंदर दुर्भावनापूर्ण SQL कोड डाल सकता है। यह कैसे हो सकता है?

यहां वह परिदृश्य है जिसका हम अपने उदाहरण में उपयोग करने जा रहे हैं:

  • मान पहले हमारे उदाहरण की तरह गतिशील SQL स्ट्रिंग से जुड़े हुए हैं। कोई पैरामीटर नहीं.
  • NVARCHAR(MAX) का उपयोग करके डायनेमिक SQL स्ट्रिंग 2GB तक है। दुर्भावनापूर्ण कोड डालने के लिए बहुत सी जगह।
  • सबसे खराब, गतिशील SQL स्ट्रिंग को उन्नत अनुमतियों के साथ निष्पादित किया जाता है।
  • SQL सर्वर इंस्टेंस SQL ​​प्रमाणीकरण स्वीकार करता है।

क्या यह बहुत ज्यादा है? एक व्यक्ति द्वारा प्रबंधित सिस्टम के लिए, ऐसा हो सकता है। वैसे भी कोई उसकी जांच नहीं करता। बड़ी कंपनियों के लिए, सुरक्षा में ढिलाई वाला आईटी विभाग कभी-कभी मौजूद होता है।

क्या यह आज भी खतरा है? यह क्लाउड सेवा प्रदाता अकामाई के अनुसार उनकी इंटरनेट/सुरक्षा रिपोर्ट की स्थिति में है। नवंबर 2017 से मार्च 2019 के बीच, SQL इंजेक्शन सभी वेब एप्लिकेशन हमलों के लगभग दो-तिहाई का प्रतिनिधित्व करता है। यह जांचे गए सभी खतरों में सबसे अधिक है। बहुत बुरा।

क्या आप इसे स्वयं देखना चाहते हैं?

एसक्यूएल इंजेक्शन अभ्यास: खराब उदाहरण

आइए इस उदाहरण में कुछ SQL इंजेक्शन करते हैं। आप इसे अपने AdventureWorks . में आजमा सकते हैं डेटाबेस। लेकिन सुनिश्चित करें कि SQL प्रमाणीकरण की अनुमति है, और आप इसे उन्नत अनुमतियों के साथ चलाते हैं।

DECLARE @lastName NVARCHAR(MAX) = 'Mu';
DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);

DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
' p.LastName ' + @crlf +
',p.FirstName ' + @crlf +
',a.AddressLine1 ' + @crlf +
',a.AddressLine2 ' + @crlf +
',a.City ' + @crlf +
'FROM Person.Person p ' + @crlf +
'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
'WHERE p.LastName = ' + NCHAR(39) + @lastName + NCHAR(39) + ' ' + @crlf +
'AND p.FirstName = '  + NCHAR(39) + @firstName + NCHAR(39);

-- SELECT @sql;	-- uncomment if you want to see what's in @sql					
EXEC sp_executesql @sql;
GO

उपरोक्त कोड किसी मौजूदा कंपनी के वास्तविक कोड का प्रतिनिधित्व नहीं करता है। यह एक ऐप द्वारा कॉल करने योग्य भी नहीं है। लेकिन यह बुरे काम को दर्शाता है। तो, हमारे पास यहाँ क्या है?

सबसे पहले, इंजेक्ट किया गया कोड एक SQL खाता बनाएगा जो sa . जैसा दिखता है , लेकिन ऐसा नहीं है। और जैसे सा , इसमें sysadmin . है अनुमतियाँ। अंततः, इसका उपयोग किसी भी समय पूर्ण विशेषाधिकारों के साथ डेटाबेस तक पहुँचने के लिए किया जाएगा। ऐसा करने के बाद कुछ भी संभव है:चोरी करना, हटाना, कॉर्पोरेट डेटा से छेड़छाड़ करना, आप इसे नाम दें।

क्या यह कोड चलेगा? निश्चित रूप से! और एक बार ऐसा हो जाने पर, सुपर खाता चुपचाप बना दिया जाएगा। और, ज़ाहिर है, परिणाम सेट में झेंग म्यू का पता दिखाई देगा। बाकी सब सामान्य है। छायादार, क्या आपको नहीं लगता?

उपरोक्त कोड चलाने के बाद, इसे भी चलाने का प्रयास करें:

SELECT IS_SRVROLEMEMBER('sysadmin','sà')

अगर यह 1 लौटाता है, तो वह इसमें है, जो भी यह है। वैकल्पिक रूप से, आप इसे SQL सर्वर प्रबंधन स्टूडियो में अपने SQL सर्वर के सुरक्षा लॉगिन में देख सकते हैं।

तो, आपको क्या मिला?

डरावना, है ना? (यदि यह वास्तविक है, तो है।)

SQL इंजेक्शन अभ्यास:अच्छा उदाहरण

अब, मापदंडों का उपयोग करके कोड को थोड़ा बदलते हैं। अन्य शर्तें अभी भी वही हैं।

DECLARE @lastName NVARCHAR(MAX) = 'Mu';
DECLARE @firstName NVARCHAR(MAX) = 'Zheng''; CREATE LOGIN sà WITH PASSWORD=''12345''; ALTER SERVER ROLE sysadmin ADD MEMBER sà; --';
DECLARE @crlf NCHAR(2) = nchar(13) + nchar(10);

DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @crlf +
' p.LastName ' + @crlf +
',p.FirstName ' + @crlf +
',a.AddressLine1 ' + @crlf +
',a.AddressLine2 ' + @crlf +
',a.City ' + @crlf +
'FROM Person.Person p ' + @crlf +
'INNER JOIN Person.BusinessEntityAddress bea ON p.BusinessEntityID = bea.BusinessEntityID ' + @crlf +
'INNER JOIN Person.Address a ON bea.AddressID = a.AddressID ' + @crlf +
'WHERE p.LastName = @lastName' + @crlf +
'AND p.FirstName = @firstName';

DECLARE @paramList NVARCHAR(300) = N'@lastName NVARCHAR(50), @firstName NVARCHAR(50)';

-- SELECT @sql;	-- uncomment if you want to see what's in @sql
EXEC sp_executesql @sql, @paramList, @lastName, @firstName;
GO

पहले उदाहरण की तुलना में परिणाम में 2 अंतर हैं।

  • सबसे पहले, झेंग म्यू का पता नहीं दिखेगा। परिणाम सेट खाली है।
  • फिर, पाखण्डी खाता नहीं बनाया जाता है। IS_SRVROLEMEMBER का उपयोग करने से NULL वापस आ जाएगा।

क्या हुआ?

चूंकि पैरामीटर का उपयोग किया जाता है, @firstName . का मान है 'झेंग'; पासवर्ड के साथ लॉगिन बनाएं ="12345"; ऑल्ट' . इसे एक शाब्दिक मान के रूप में लिया जाता है और केवल 50 वर्णों तक छोटा कर दिया जाता है। उपरोक्त कोड में प्रथम नाम पैरामीटर की जाँच करें। यह नवचर (50) है। इसलिए परिणाम सेट खाली है। इस तरह के पहले नाम वाला कोई भी व्यक्ति डेटाबेस में नहीं है।

यह SQL इंजेक्शन का सिर्फ एक उदाहरण है और इससे बचने का एक तरीका है। वास्तविक काम करने में अधिक शामिल है। लेकिन मुझे आशा है कि मैंने यह बात स्पष्ट कर दी है कि डायनामिक SQL में इनलाइन मान खराब क्यों हैं।

पैरामीटर सूँघना

क्या आपने किसी ऐप से धीमी गति से चलने वाली संग्रहीत कार्यविधि का अनुभव किया है, लेकिन जब आपने इसे SSMS में चलाने का प्रयास किया तो यह तेज़ हो गया? यह चौंकाने वाला है क्योंकि आपने ऐप में उपयोग किए गए सटीक पैरामीटर मानों का उपयोग किया है।

वह पैरामीटर कार्रवाई में सूँघ रहा है। SQL सर्वर एक निष्पादन योजना बनाता है जब पहली बार संग्रहीत कार्यविधि चलाई जाती है या पुन:संकलित की जाती है। फिर, अगले रन के लिए योजना का पुन:उपयोग करें। यह बहुत अच्छा लगता है क्योंकि SQL सर्वर को हर बार योजना को फिर से बनाने की आवश्यकता नहीं होती है। लेकिन कई बार एक अलग पैरामीटर मान को तेज़ी से चलाने के लिए एक अलग योजना की आवश्यकता होती है।

यहाँ SP_EXECUTESQL और एक सादे स्थिर SQL का उपयोग करके एक प्रदर्शन दिया गया है।

DECLARE @sql NVARCHAR(150) = N'SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID';
DECLARE @paramList NVARCHAR(100) = N'@ProductSubcategoryID INT';
DECLARE @ProductSubcategoryID INT = 23;

EXEC sp_executesql @sql, @paramList, @ProductSubcategoryID

यह वाला बहुत ही सरल है। चित्र 3 में निष्पादन योजना की जाँच करें।

आइए अब स्थिर SQL का उपयोग करके उसी क्वेरी का प्रयास करें।

DECLARE @ProductSubcategoryID INT = 23;
SELECT Name FROM Production.Product WHERE ProductSubcategoryID = @ProductSubcategoryID

चित्र 4 देखें, फिर उसकी तुलना चित्र 3 से करें।

चित्र 3 में, सूचकांक खोज और नेस्टेड लूप उपयोग किया जाता है। लेकिन चित्र 4 में, यह एक संकुल अनुक्रमणिका स्कैन है . हालांकि इस समय कोई स्पष्ट प्रदर्शन दंड नहीं है, इससे पता चलता है कि पैरामीटर सूँघना केवल एक कल्पना नहीं है।

एक बार क्वेरी धीमी हो जाने पर यह बहुत निराशाजनक हो सकता है। आप इससे बचने के लिए पुन:संकलन या क्वेरी संकेतों का उपयोग करने जैसी तकनीकों का उपयोग कर सकते हैं। इनमें से किसी में भी कमियां हैं।

SP_EXECUTESQL में अनफॉर्मेटेड डायनेमिक SQL स्ट्रिंग

इस कोड में क्या गलत हो सकता है?

DECLARE @sql NVARCHAR(100) = N'SELECT COUNT(*) AS ProductCount' +
                              'FROM Production.Product';
PRINT @sql;
EXEC sp_executesql @sql;

यह छोटा और सरल है। लेकिन नीचे चित्र 5 देखें।

डायनेमिक SQL स्ट्रिंग बनाते समय कीवर्ड और ऑब्जेक्ट के बीच एक भी स्थान पर ध्यान न देने पर त्रुटियाँ उत्पन्न होती हैं। जैसे चित्र 5 में, जहां उत्पाद गणना कॉलम उपनाम और कीवर्ड FROM के बीच में कोई स्थान नहीं है। एक बार स्ट्रिंग का एक हिस्सा कोड की अगली पंक्ति में प्रवाहित होने पर यह भ्रमित हो जाता है। इससे आपको लगता है कि वाक्य रचना सही है।

यह भी ध्यान दें कि स्ट्रिंग कोड विंडो में 2 लाइनों का उपयोग करती है, लेकिन PRINT का आउटपुट 1 लाइन दिखाता है। कल्पना कीजिए कि यह एक बहुत, बहुत लंबी कमांड स्ट्रिंग है। जब तक आप संदेश टैब से स्ट्रिंग को ठीक से प्रारूपित नहीं करते, तब तक समस्या का पता लगाना कठिन है।

इस समस्या को हल करने के लिए कैरिज रिटर्न और लाइन फीड जोड़ें। आप शायद एक चर @crlf . देख सकते हैं पिछले उदाहरणों से। अपने डायनेमिक SQL स्ट्रिंग को स्पेस और एक नई लाइन के साथ फ़ॉर्मेट करना डायनेमिक SQL स्ट्रिंग को अधिक पठनीय बना देगा। यह डिबगिंग के लिए भी बहुत अच्छा है।

जॉइन के साथ एक सेलेक्ट स्टेटमेंट पर विचार करें। इसे नीचे दिए गए उदाहरण की तरह कोड की कई पंक्तियों की आवश्यकता है।

DECLARE @sql NVARCHAR(400)
DECLARE @shipDate DATETIME = '06/11/2011';
DECLARE @paramList NVARCHAR(100) = N'@shipDate DATETIME';
DECLARE @crlf NCHAR(2) = NCHAR(13) + NCHAR(10);

set @sql = N'SELECT ' + @crlf +
 'soh.ShipDate ' + @crlf +
 ',sod.ProductID ' + @crlf +
 ',SUM(sod.OrderQty) AS TotalQty ' + @crlf +
 ',SUM(sod.LineTotal) AS LineTotal ' + @crlf +
 'FROM Sales.SalesOrderHeader soh ' + @crlf +
 'INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID ' + @crlf +
 'WHERE soh.ShipDate = @shipDate' + @crlf +
 'GROUP BY soh.ShipDate, sod.ProductID ' + @crlf +
 'ORDER BY sod.ProductID';

 PRINT @sql;
 EXEC sp_executesql @sql,@paramList,@shipDate
 GO

स्ट्रिंग को प्रारूपित करने के लिए, @crlf वेरिएबल NCHAR(13), एक कैरिज रिटर्न, और NCHAR(10), एक लाइन फीड पर सेट है। SELECT स्टेटमेंट की एक लंबी स्ट्रिंग को तोड़ने के लिए इसे प्रत्येक लाइन से जोड़ा जाता है। संदेश टैब में परिणाम देखने के लिए, हम PRINT का उपयोग करते हैं। नीचे चित्र 6 में आउटपुट की जाँच करें।

आप डायनामिक एसक्यूएल स्ट्रिंग कैसे बनाते हैं आप पर निर्भर है। समय आने पर इसे स्पष्ट, पठनीय और डीबग करने में आसान बनाने के लिए आपको जो भी उपयुक्त लगे।

अस्वच्छ वस्तुओं के नाम

क्या आपको किसी भी कारण से गतिशील रूप से तालिका, दृश्य या डेटाबेस नाम सेट करने की आवश्यकता है? फिर आपको त्रुटियों से बचने के लिए इन ऑब्जेक्ट नामों को "स्वच्छता" या मान्य करने की आवश्यकता है।

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

इससे पहले, हम तालिका नाम से स्कीमा नाम को अलग करने के लिए PARSENAME का उपयोग करते हैं। इसका उपयोग तब भी किया जा सकता है जब स्ट्रिंग में सर्वर और डेटाबेस का नाम हो। लेकिन इस उदाहरण में, हम केवल स्कीमा और टेबल नामों का उपयोग करेंगे। बाकी मैं आपके तेज दिमाग पर छोड़ता हूं। यह इस बात पर ध्यान दिए बिना काम करेगा कि आप अपनी टेबल को रिक्त स्थान के साथ या बिना नाम देते हैं। टेबल पर स्पेस या व्यू नाम मान्य हैं। तो, यह dbo.MyFoodCravings . के लिए काम करता है या [dbo].[माई फ़ूड क्रेविंग्स]

उदाहरण

चलिए एक टेबल बनाते हैं।

CREATE TABLE [dbo].[My Favorite Bikes]
(
	id INT NOT NULL,
	BikeName VARCHAR(50)
)
GO

फिर हम एक स्क्रिप्ट बनाते हैं जो SP_EXECUTESQL का उपयोग करेगी। यह 1-कॉलम कुंजी दी गई किसी भी तालिका के लिए एक सामान्य DELETE कथन चलाएगा। पहली बात यह है कि पूरे ऑब्जेक्ट नाम को पार्स करना है।

DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);

इस तरह हम स्कीमा को तालिका से अलग करते हैं। आगे सत्यापित करने के लिए, हम OBJECT_ID का उपयोग करते हैं। अगर यह NULL नहीं है, तो यह मान्य है।

IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
BEGIN
	PRINT @object + ' is valid!'
	-- do the rest of your stuff here
END
ELSE
BEGIN
        PRINT 'Invalid object name ' + @object
	-- if you need to do anything else, insert it here
END

यह भी ध्यान दें कि हमने QUOTENAME का उपयोग किया है। यह सुनिश्चित करेगा कि रिक्त स्थान वाले तालिका नामों को वर्गाकार कोष्ठकों के साथ संलग्न करके कोई त्रुटि उत्पन्न नहीं होगी।

लेकिन कुंजी कॉलम को मान्य करने के बारे में कैसे? आप sys.columns . में लक्ष्य तालिका के कॉलम के अस्तित्व की जांच कर सकते हैं ।

IF (SELECT COUNT(*) FROM sys.columns
	    WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
		  AND [name] = @idKey) > 0
BEGIN
     -- add miscellaneous code here, if needed
     EXEC sp_executesql @sql, @paramsList, @id
END
ELSE
BEGIN
     PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
     -- if you need to do anything else, insert it here
END

अब, हम जो हासिल करना चाहते हैं उसकी पूरी स्क्रिप्ट यहां दी गई है।

DECLARE @object NVARCHAR(128) = '[dbo].[My Favorite Bikes]';
DECLARE @schemaName NVARCHAR(128) = PARSENAME(@object,2);
DECLARE @tableName NVARCHAR(128) = PARSENAME(@object,1);
DECLARE @isDebug BIT = 1;
DECLARE @idKey NVARCHAR(128) = N'id';

DECLARE @sql NVARCHAR(200) = N'DELETE FROM @object WHERE @idKey = @id';
DECLARE @id INT = 0;
DECLARE @paramList NVARCHAR(100) = N'@id INT';

IF NOT OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName)) IS NULL
BEGIN
   PRINT @object + ' is valid!'
   
   IF (SELECT COUNT(*) FROM sys.columns
       WHERE [object_id] = OBJECT_ID(QUOTENAME(@schemaName) + '.' + QUOTENAME(@tableName))
         AND [name] = @idKey) > 0
   BEGIN
       SET @sql = REPLACE(@sql, '@object', QUOTENAME(@schemaName) + '.' +          
                  QUOTENAME(@tableName));
       SET @sql = REPLACE(@sql, '@idkey', QUOTENAME(@idKey));
       IF @isDebug = 1
	   PRINT @sql;
       EXEC sp_executesql @sql, @paramList, @id
   END
   ELSE
       PRINT 'Invalid column name ' + @idKey + ' for object ' + @object
END
ELSE
BEGIN
   PRINT 'Invalid object name ' + @object
   -- if you need to do anything else, insert it here
END
GO

इस स्क्रिप्ट का परिणाम नीचे चित्र 7 में है।

आप इसे अन्य तालिकाओं के साथ आज़मा सकते हैं। बस @ऑब्जेक्ट . बदलें , @idkey , और @id परिवर्तनशील मान।

डिबगिंग के लिए कोई प्रावधान नहीं

त्रुटियां हो सकती हैं। तो, आपको मूल कारण खोजने के लिए उत्पन्न गतिशील SQL स्ट्रिंग को जानना होगा। गतिशील SQL स्ट्रिंग के रूप का अनुमान लगाने के लिए हम भविष्यवक्ता या जादूगर नहीं हैं। तो, आपको डिबग फ़्लैग की आवश्यकता है।

पहले चित्र 7 में ध्यान दें कि गतिशील SQL स्ट्रिंग SSMS के संदेश टैब में मुद्रित होती है। हमने एक @isDebug जोड़ा है BIT वेरिएबल और इसे कोड में 1 पर सेट करें। जब मान 1 होता है, तो डायनेमिक SQL स्ट्रिंग प्रिंट होगी। यह अच्छा है अगर आपको इस तरह की स्क्रिप्ट या संग्रहीत प्रक्रिया को डीबग करने की आवश्यकता है। जब आप डिबगिंग कर लें तो बस इसे वापस शून्य पर सेट करें। यदि यह एक संग्रहीत कार्यविधि है, तो इस ध्वज को शून्य के डिफ़ॉल्ट मान के साथ एक वैकल्पिक पैरामीटर बनाएं।

डायनेमिक SQL स्ट्रिंग देखने के लिए, आप 2 संभावित विधियों का उपयोग कर सकते हैं।

  • प्रिंट का उपयोग करें यदि स्ट्रिंग 8000 वर्णों से कम या उसके बराबर है।
  • या यदि स्ट्रिंग 8000 वर्णों से अधिक है तो SELECT का उपयोग करें।

SP_EXECUTESQL में उपयोग किया गया खराब प्रदर्शन करने वाला डायनेमिक SQL

यदि आप इसे धीमी गति से चलने वाली क्वेरी असाइन करते हैं तो SP_EXECUTESQL धीमा हो सकता है। अवधि। इसमें अभी तक पैरामीटर सूँघने की समस्या शामिल नहीं है।

तो, उस कोड के साथ स्थिर प्रारंभ करें जिसे आप गतिशील रूप से चलाना चाहते हैं। फिर, निष्पादन योजना और सांख्यिकी IO की जाँच करें। देखें कि क्या ऐसी अनुपलब्ध अनुक्रमणिकाएँ हैं जिन्हें आपको बनाने की आवश्यकता है। इसे जल्दी ट्यून करें।

SP_EXECUTESQL में बॉटमलाइन

SP_EXECUTESQL का उपयोग करना एक शक्तिशाली हथियार चलाने जैसा है। लेकिन जो इसे धारण करता है उसे इसमें कुशल होने की आवश्यकता होती है। हालांकि यह भी कोई रॉकेट साइंस नहीं है। यदि आप आज नौसिखिया हैं, तो समय के साथ अभ्यास के साथ यह सामान्य ज्ञान बन सकता है।

गोचरों की यह सूची पूरी नहीं है। लेकिन इसमें आम लोगों को शामिल किया गया है। अगर आपको और जानकारी चाहिए, तो इन लिंक्स को देखें:

  • द कर्स एंड ब्लेसिंग्स ऑफ़ डायनेमिक SQL, Erland Somarskog द्वारा
  • SP_EXECUTESQL (ट्रांजैक्ट-एसक्यूएल), माइक्रोसॉफ्ट द्वारा

ऐशे ही? तो कृपया इसे अपने पसंदीदा सोशल मीडिया प्लेटफॉर्म पर साझा करें। आप हमारे साथ अपनी समय-परीक्षित युक्तियों को टिप्पणी अनुभाग में भी साझा कर सकते हैं।


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. सी # में SQL क्वेरी को सीधे कैसे निष्पादित करें?

  2. SQL सर्वर में एक विशाल तालिका डेटा को किसी अन्य तालिका में कैसे कॉपी करें

  3. डेटाबेस में सभी टेबल के लिए ड्रॉप टेबल स्टेटमेंट कैसे जेनरेट करें - SQL सर्वर / T-SQL ट्यूटोरियल पार्ट 48

  4. अद्यतन विवरण से प्रभावित पंक्तियों की वापसी संख्या

  5. SQL सर्वर 2012 OPENROWSET त्रुटि का उपयोग करके एक्सेस 2007 डेटा को क्वेरी कर रहा है