[ भाग 1 | भाग 2 | भाग 3 ]
यदि आपने कभी भी संग्रहीत प्रक्रिया मापदंडों के लिए डिफ़ॉल्ट मान निर्धारित करने का प्रयास किया है, तो संभवतः आपके माथे पर बार-बार और हिंसक रूप से इसे मारने से आपके माथे पर निशान हैं। अधिकांश लेख जो पैरामीटर जानकारी प्राप्त करने के बारे में बात करते हैं (जैसे यह टिप) डिफ़ॉल्ट शब्द का भी उल्लेख नहीं करते हैं। ऐसा इसलिए है, क्योंकि वस्तु की परिभाषा में संग्रहीत कच्चे पाठ को छोड़कर, जानकारी कैटलॉग दृश्यों में कहीं भी नहीं है। कॉलम हैं has_default_value
और default_value
sys.parameters
. में कि देखो आशाजनक, लेकिन वे केवल CLR मॉड्यूल के लिए ही भरे जाते हैं।
टी-एसक्यूएल का उपयोग करके डिफ़ॉल्ट मान प्राप्त करना बोझिल और त्रुटि-प्रवण है। मैंने हाल ही में इस समस्या के बारे में स्टैक ओवरफ़्लो पर एक प्रश्न का उत्तर दिया, और इसने मुझे मेमोरी लेन से नीचे ले लिया। 2006 में वापस, मैंने कैटलॉग दृश्यों में मापदंडों के लिए डिफ़ॉल्ट मानों की दृश्यता की कमी के बारे में कई कनेक्ट आइटमों के माध्यम से शिकायत की। हालाँकि, समस्या अभी भी SQL सर्वर 2019 में मौजूद है।
हालांकि यह एक असुविधा है कि डिफ़ॉल्ट मान मेटाडेटा में उजागर नहीं होते हैं, वे सबसे अधिक संभावना नहीं हैं क्योंकि उन्हें ऑब्जेक्ट टेक्स्ट (किसी भी भाषा में, लेकिन विशेष रूप से टी-एसक्यूएल में) से बाहर निकालना कठिन है। पैरामीटर सूची की शुरुआत और अंत का पता लगाना भी मुश्किल है क्योंकि टी-एसक्यूएल की पार्सिंग क्षमता इतनी सीमित है, और आप जितना सोच सकते हैं उससे अधिक किनारे के मामले हैं। कुछ उदाहरण:
- आप
(
. की मौजूदगी पर भरोसा नहीं कर सकते और)
पैरामीटर सूची को इंगित करने के लिए, क्योंकि वे वैकल्पिक हैं (और पूरे पैरामीटर सूची में पाए जा सकते हैं) - आप पहले
AS
के लिए आसानी से पार्स नहीं कर सकते हैं शरीर की शुरुआत को चिह्नित करने के लिए, क्योंकि यह अन्य कारणों से प्रकट हो सकता है - आप
BEGIN
. की उपस्थिति पर भरोसा नहीं कर सकते शरीर की शुरुआत को चिह्नित करने के लिए, क्योंकि यह वैकल्पिक है - अल्पविराम पर विभाजित करना कठिन है, क्योंकि वे टिप्पणियों के अंदर, स्ट्रिंग अक्षर के भीतर, और डेटा प्रकार घोषणाओं के भाग के रूप में प्रकट हो सकते हैं (सोचें
(precision, scale)
) - दोनों प्रकार की टिप्पणियों को दूर करना बहुत कठिन है, जो कहीं भी दिखाई दे सकती हैं (अंदर स्ट्रिंग अक्षर सहित), और नेस्टेड किया जा सकता है
- आप अनजाने में स्ट्रिंग अक्षर और टिप्पणियों के अंदर महत्वपूर्ण कीवर्ड, अल्पविराम और बराबर चिह्न ढूंढ सकते हैं
- आपके पास डिफ़ॉल्ट मान हो सकते हैं जो संख्या या स्ट्रिंग अक्षर नहीं हैं (सोचें
{fn curdate()}
याGETDATE
)
इतनी कम वाक्यविन्यास विविधताएं हैं कि सामान्य स्ट्रिंग पार्सिंग तकनीकें अप्रभावी हो जाती हैं। क्या मैंने AS
देखा है पहले से ही? क्या यह पैरामीटर नाम और डेटा प्रकार के बीच था? क्या यह एक सही कोष्ठक के बाद था जो संपूर्ण पैरामीटर सूची को घेरता है, या [एक?] जिसका पिछली बार मेरे द्वारा कोई पैरामीटर देखे जाने से पहले कोई मिलान नहीं था? क्या वह अल्पविराम दो मापदंडों को अलग कर रहा है या यह सटीक और पैमाने का हिस्सा है? जब आप एक समय में एक शब्द को एक स्ट्रिंग के माध्यम से लूप कर रहे होते हैं, तो यह चालू और चालू रहता है, और ऐसे कई बिट्स हैं जिन्हें आपको ट्रैक करने की आवश्यकता है।
इसे लें (जानबूझकर हास्यास्पद, लेकिन फिर भी वाक्य-रचना की दृष्टि से मान्य) उदाहरण:
/* AS BEGIN , @a int = 7, comments can appear anywhere */ CREATE PROCEDURE dbo.some_procedure -- AS BEGIN, @a int = 7 'blat' AS = /* AS BEGIN, @a int = 7 'blat' AS = -- */ @a AS /* comment here because -- chaos */ int = 5, @b AS varchar(64) = 'AS = /* BEGIN @a, int = 7 */ ''blat''', @c AS int = -- 12 6 AS -- @d int = 72, DECLARE @e int = 5; SET @e = 6;
टी-एसक्यूएल का उपयोग करके उस परिभाषा से डिफ़ॉल्ट मानों को पार्स करना कठिन है। वास्तव में कठिन . बिना BEGIN
के पैरामीटर सूची के अंत को ठीक से चिह्नित करने के लिए, सभी टिप्पणी गड़बड़ी, और उन सभी मामलों में जहां कीवर्ड जैसे AS
अलग-अलग चीजों का मतलब हो सकता है, आपके पास शायद नेस्टेड अभिव्यक्तियों का एक जटिल सेट होगा जिसमें अधिक SUBSTRING
शामिल होंगे और CHARINDEX
पैटर्न की तुलना में आपने पहले कभी एक स्थान पर नहीं देखा है। और आपके पास शायद अब भी @d
. होगा और @e
स्थानीय चर के बजाय प्रक्रिया पैरामीटर की तरह दिख रहा है।
समस्या के बारे में कुछ और सोचते हुए, और यह देखने के लिए कि पिछले दशक में किसी ने कुछ नया प्रबंधित किया है या नहीं, मैं माइकल स्वार्ट द्वारा इस महान पोस्ट में आया था। उस पोस्ट में, माइकल टी-एसक्यूएल के एक ब्लॉक से सिंगल-लाइन और मल्टी-लाइन दोनों टिप्पणियों को हटाने के लिए स्क्रिप्टडॉम के TSqlParser का उपयोग करता है। इसलिए मैंने कुछ पॉवरशेल कोड को एक प्रक्रिया के माध्यम से देखने के लिए लिखा था कि कौन से अन्य टोकन की पहचान की गई थी। आइए सभी जानबूझकर समस्याओं के बिना एक सरल उदाहरण लेते हैं:
CREATE PROCEDURE dbo.procedure1 @param1 int AS PRINT 1; GO
विजुअल स्टूडियो कोड (या अपनी पसंदीदा पावरशेल आईडीई) खोलें और Test1.ps1 नामक एक नई फ़ाइल सहेजें। Microsoft.SqlServer.TransactSql.ScriptDom.dll (जिसे आप डाउनलोड कर सकते हैं और sqlpackage से यहां से निकाल सकते हैं) का नवीनतम संस्करण .ps1 फ़ाइल के समान फ़ोल्डर में होना एकमात्र शर्त है। इस कोड को कॉपी करें, सहेजें और फिर चलाएं या डीबग करें:
# need to extract this DLL from latest sqlpackage; place it in same folder # https://docs.microsoft.com/en-us/sql/tools/sqlpackage-download Add-Type -Path "Microsoft.SqlServer.TransactSql.ScriptDom.dll"; # set up a parser object using the most recent version available $parser = [Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser]($true)::New(); # and an error collector $errors = [System.Collections.Generic.List[Microsoft.SqlServer.TransactSql.ScriptDom.ParseError]]::New(); # this ultimately won't come from a constant - think file, folder, database # can be a batch or multiple batches, just keeping it simple to start $procedure = @" CREATE PROCEDURE dbo.procedure1 @param1 AS int AS PRINT 1; GO "@ # now we need to try parsing $block = $parser.Parse([System.IO.StringReader]::New($procedure), [ref]$errors); # parse the whole thing, which is a set of one or more batches foreach ($batch in $block.Batches) { # each batch contains one or more statements # (though a valid create procedure statement is also always just one batch) foreach ($statement in $batch.Statements) { # output the type of statement Write-Host " ===================================="; Write-Host " $($statement.GetType().Name)"; Write-Host " ===================================="; # each statement has one or more tokens in its token stream foreach ($token in $statement.ScriptTokenStream) { # those tokens have properties to indicate the type # as well as the actual text of the token Write-Host " $($token.TokenType.ToString().PadRight(16)) : $($token.Text)"; } } }
परिणाम:
================================CreateProcedureStatement
==================================
बनाएँ:बनाएँ
व्हाइटस्पेस:
प्रक्रिया:प्रक्रिया
व्हाइटस्पेस:
पहचानकर्ता:dbo
डॉट:.
पहचानकर्ता:प्रक्रिया1
व्हाइटस्पेस:
व्हाइटस्पेस :
वेरिएबल :@param1
व्हाइटस्पेस :
As :AS
व्हाइटस्पेस :
पहचानकर्ता :int
व्हाइटस्पेस :
As :AS
WhiteSpace :
Print :PRINT
WhiteSpace :
Integer :1
Semicolon :;
WhiteSpace :
Go :GO
एंडऑफफाइल:
कुछ शोर से छुटकारा पाने के लिए, हम लूप के लिए अंतिम के अंदर कुछ टोकन टाइप को फ़िल्टर कर सकते हैं:
foreach ($token in $statement.ScriptTokenStream) { if ($token.TokenType -notin "WhiteSpace", "Go", "EndOfFile", "SemiColon") { Write-Host " $($token.TokenType.ToString().PadRight(16)) : $($token.Text)"; } }
टोकन की अधिक संक्षिप्त श्रृंखला के साथ समाप्त:
================================CreateProcedureStatement
==================================
बनाएँ:बनाएँ
प्रक्रिया:प्रक्रिया
पहचानकर्ता:dbo
डॉट:।
पहचानकर्ता:प्रक्रिया1
चर:@param1
As:AS
पहचानकर्ता :int
As :AS
Print :PRINT
Integer :1
जिस तरह से यह एक प्रक्रिया को नेत्रहीन रूप से मैप करता है:
प्रत्येक टोकन को इस सरल प्रक्रिया निकाय से पार्स किया गया है।
आप पहले से ही उन समस्याओं को देख सकते हैं जिन्हें हम पैरामीटर नामों, डेटा प्रकारों और यहां तक कि पैरामीटर सूची के अंत का पता लगाने की कोशिश कर रहे हैं। इसे और अधिक देखने के बाद, मुझे डैन गुज़मैन की एक पोस्ट मिली, जिसमें TSqlFragmentVisitor नामक एक ScriptDom वर्ग को हाइलाइट किया गया था, जो पार्स किए गए T-SQL के ब्लॉक के टुकड़ों की पहचान करता है। अगर हम रणनीति में थोड़ा सा बदलाव करते हैं, तो हम टुकड़ों . का निरीक्षण कर सकते हैं टोकन . के बजाय . एक टुकड़ा अनिवार्य रूप से एक या अधिक टोकन का एक सेट होता है, और इसका अपना प्रकार का पदानुक्रम भी होता है। जहाँ तक मुझे पता है, कोई ScriptFragmentStream
नहीं है टुकड़ों के माध्यम से पुनरावृति करने के लिए, लेकिन हम एक आगंतुक . का उपयोग कर सकते हैं पैटर्न अनिवार्य रूप से एक ही काम करने के लिए। आइए Test2.ps1 नामक एक नई फ़ाइल बनाएं, इस कोड में पेस्ट करें और इसे चलाएं:
Add-Type -Path "Microsoft.SqlServer.TransactSql.ScriptDom.dll"; $parser = [Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser]($true)::New(); $errors = [System.Collections.Generic.List[Microsoft.SqlServer.TransactSql.ScriptDom.ParseError]]::New(); $procedure = @" CREATE PROCEDURE dbo.procedure1 @param1 AS int AS PRINT 1; GO "@ $fragment = $parser.Parse([System.IO.StringReader]::New($procedure), [ref]$errors); $visitor = [Visitor]::New(); $fragment.Accept($visitor); class Visitor: Microsoft.SqlServer.TransactSql.ScriptDom.TSqlFragmentVisitor { [void]Visit ([Microsoft.SqlServer.TransactSql.ScriptDom.TSqlFragment] $fragment) { Write-Host $fragment.GetType().Name; } }
परिणाम (इस अभ्यास के लिए दिलचस्प हैं बोल्ड में ):
TSqlScriptTSqlBatch
CreateProcedureStatement
ProcedureReference
SchemaObjectName
पहचानकर्ता
पहचानकर्ता
प्रक्रिया पैरामीटर
पहचानकर्ता
SqlDataTypeReference
SchemaObjectName
पहचानकर्ता
StatementList
PrintStatement
IntegerLiteral
यदि हम इसे अपने पिछले आरेख में दृष्टिगत रूप से मैप करने का प्रयास करते हैं, तो यह थोड़ा और जटिल हो जाता है। इनमें से प्रत्येक टुकड़ा स्वयं एक या अधिक टोकन की एक धारा है, और कभी-कभी वे ओवरलैप हो जाएंगे। कई स्टेटमेंट टोकन और कीवर्ड को एक टुकड़े के हिस्से के रूप में भी पहचाना नहीं जाता है, जैसे CREATE
, PROCEDURE
, AS
, और GO
. उत्तरार्द्ध समझ में आता है क्योंकि यह बिल्कुल भी टी-एसक्यूएल नहीं है, लेकिन पार्सर को अभी भी यह समझना होगा कि यह बैचों को अलग करता है।
जिस तरह से स्टेटमेंट टोकन और फ्रैगमेंट टोकन को पहचाना जाता है, उसकी तुलना करना।
कोड में किसी भी टुकड़े के पुनर्निर्माण के लिए, हम उस टुकड़े की यात्रा के दौरान उसके टोकन के माध्यम से पुनरावृति कर सकते हैं। यह हमें बहुत कम थकाऊ पार्सिंग और सशर्त के साथ वस्तु के नाम और पैरामीटर टुकड़े जैसी चीजों को प्राप्त करने देता है, हालांकि हमें अभी भी प्रत्येक टुकड़े के टोकन स्ट्रीम के अंदर लूप करना है। अगर हम Write-Host $fragment.GetType().Name;
. को बदलते हैं इसके लिए पिछली लिपि में:
[void]Visit ([Microsoft.SqlServer.TransactSql.ScriptDom.TSqlFragment] $fragment) { if ($fragment.GetType().Name -in ("ProcedureParameter", "ProcedureReference")) { $output = ""; Write-Host "=========================="; Write-Host " $($fragment.GetType().Name)"; Write-Host "=========================="; for ($i = $fragment.FirstTokenIndex; $i -le $fragment.LastTokenIndex; $i++) { $token = $fragment.ScriptTokenStream[$i]; $output += $token.Text; } Write-Host $output; } }
आउटपुट है:
========================प्रक्रिया संदर्भ
=========================
dbo.procedure1
========================
प्रक्रिया पैरामीटर
=========================
@param1 AS int
हमारे पास कोई अतिरिक्त पुनरावृत्ति या संयोजन किए बिना ऑब्जेक्ट और स्कीमा नाम एक साथ है। और हमारे पास किसी भी पैरामीटर घोषणा में पूरी लाइन शामिल है, जिसमें पैरामीटर नाम, डेटा प्रकार और कोई भी डिफ़ॉल्ट मान मौजूद हो सकता है। दिलचस्प बात यह है कि विज़िटर @param1 int
. को हैंडल करता है और int
दो अलग-अलग अंशों के रूप में, अनिवार्य रूप से डेटा प्रकार की दोहरी गणना करना। पहला एक ProcedureParameter
है टुकड़ा, और बाद वाला एक SchemaObjectName
है . हम वास्तव में केवल पहले . की परवाह करते हैं SchemaObjectName
संदर्भ (dbo.procedure1
) या, विशेष रूप से, केवल वही जो ProcedureReference
. का अनुसरण करता है . मैं वादा करता हूं कि हम उनसे निपटेंगे, आज उन सभी से नहीं। अगर हम $procedure
. बदलते हैं इसके लिए स्थिर (एक टिप्पणी और एक डिफ़ॉल्ट मान जोड़ना):
$procedure = @" CREATE PROCEDURE dbo.procedure1 @param1 AS int = /* comment */ -64 AS PRINT 1; GO "@
तब आउटपुट बन जाता है:
========================प्रक्रिया संदर्भ
=========================
dbo.procedure1
========================
प्रक्रिया पैरामीटर
=========================
@param1 AS int =/* कमेंट */-64
इसमें अभी भी आउटपुट में कोई भी टोकन शामिल है जो वास्तव में टिप्पणियां हैं। लूप के अंदर, हम इसे संबोधित करने के लिए किसी भी टोकन प्रकार को फ़िल्टर कर सकते हैं जिसे हम अनदेखा करना चाहते हैं (मैं अनावश्यक AS
भी हटा देता हूं। इस उदाहरण में कीवर्ड, लेकिन यदि आप मॉड्यूल निकायों का पुनर्निर्माण कर रहे हैं तो आप ऐसा नहीं करना चाहेंगे):
for ($i = $fragment.FirstTokenIndex; $i -le $fragment.LastTokenIndex; $i++) { $token = $fragment.ScriptTokenStream[$i]; if ($token.TokenType -notin ("MultiLineComment", "SingleLineComment", "As")) { $output += $token.Text; } }
आउटपुट क्लीनर है, लेकिन फिर भी सही नहीं है।
========================प्रक्रिया संदर्भ
=========================
dbo.procedure1
========================
प्रक्रिया पैरामीटर
=========================
@param1 इंट =-64
यदि हम पैरामीटर नाम, डेटा प्रकार और डिफ़ॉल्ट मान को अलग करना चाहते हैं, तो यह अधिक जटिल हो जाता है। जब हम किसी दिए गए टुकड़े के लिए टोकन स्ट्रीम के माध्यम से लूप कर रहे हैं, तो हम किसी भी डेटा प्रकार घोषणाओं से पैरामीटर नाम को विभाजित कर सकते हैं जब हम EqualsSign
हिट करते हैं टोकन। लूप के लिए इस अतिरिक्त तर्क के साथ प्रतिस्थापित करना:
if ($fragment.GetType().Name -in ("ProcedureParameter","SchemaObjectName")) { $output = ""; $param = ""; $type = ""; $default = ""; $seenEquals = $false; for ($i = $fragment.FirstTokenIndex; $i -le $fragment.LastTokenIndex; $i++) { $token = $fragment.ScriptTokenStream[$i]; if ($token.TokenType -notin ("MultiLineComment", "SingleLineComment", "As")) { if ($fragment.GetType().Name -eq "ProcedureParameter") { if (!$seenEquals) { if ($token.TokenType -eq "EqualsSign") { $seenEquals = $true; } else { if ($token.TokenType -eq "Variable") { $param += $token.Text; } else { $type += $token.Text; } } } else { if ($token.TokenType -ne "EqualsSign") { $default += $token.Text; } } } else { $output += $token.Text.Trim(); } } } if ($param.Length -gt 0) { $output = "Param name: " + $param.Trim(); } if ($type.Length -gt 0) { $type = "`nParam type: " + $type.Trim(); } if ($default.Length -gt 0) { $default = "`nDefault: " + $default.TrimStart(); } Write-Host $output $type $default; }
अब आउटपुट है:
========================प्रक्रिया संदर्भ
=========================
dbo.procedure1
========================
प्रक्रिया पैरामीटर
=========================
परम नाम:@ param1
परम प्रकार:int
डिफ़ॉल्ट:-64
यह बेहतर है, लेकिन हल करने के लिए अभी और भी बहुत कुछ है। ऐसे पैरामीटर कीवर्ड हैं जिन्हें मैंने अब तक अनदेखा किया है, जैसे OUTPUT
और READONLY
, और हमें तर्क की आवश्यकता होती है जब हमारा इनपुट एक से अधिक प्रक्रियाओं वाला बैच होता है। मैं भाग 2 में उन मुद्दों से निपटूंगा।
इस बीच, प्रयोग! ScriptDOM, TSqlParser, और TSqlFragmentVisitor के साथ आप कई अन्य शक्तिशाली चीजें कर सकते हैं।
[ भाग 1 | भाग 2 | भाग 3 ]