कई क्लाइंट-सर्वर अनुप्रयोगों में एक सामान्य परिदृश्य अंतिम उपयोगकर्ता को परिणामों के क्रमबद्ध क्रम को निर्धारित करने की अनुमति देता है। कुछ लोग सबसे कम कीमत वाली वस्तुओं को पहले देखना चाहते हैं, कुछ सबसे पहले नवीनतम वस्तुओं को देखना चाहते हैं, और कुछ उन्हें वर्णानुक्रम में देखना चाहते हैं। ट्रांजैक्ट-एसक्यूएल में हासिल करने के लिए यह एक जटिल चीज है क्योंकि आप सिर्फ यह नहीं कह सकते:
CREATE PROCEDURE dbo.SortOnSomeTable @SortColumn NVARCHAR(128) =N'key_col', @SortDirection VARCHAR(4) ='ASC'ASBEGIN ... @SortColumn द्वारा ऑर्डर करें; -- या ... @SortColumn @SortDirection द्वारा ऑर्डर करें;ENDGO
ऐसा इसलिए है क्योंकि टी-एसक्यूएल इन स्थानों में चर की अनुमति नहीं देता है। यदि आप केवल @SortColumn का उपयोग करते हैं, तो आपको यह प्राप्त होता है:
Msg 1008, Level 16, State 1, Line xORDER BY नंबर 1 द्वारा पहचाने जाने वाले सेलेक्ट आइटम में कॉलम स्थिति की पहचान करने वाले एक्सप्रेशन के हिस्से के रूप में एक वेरिएबल होता है। वेरिएबल की अनुमति केवल तब दी जाती है जब किसी कॉलम नाम को संदर्भित करने वाले एक्सप्रेशन द्वारा ऑर्डर किया जाता है।
(और जब त्रुटि संदेश कहता है, "एक कॉलम नाम को संदर्भित करने वाला एक अभिव्यक्ति," आपको यह अस्पष्ट लग सकता है, और मैं सहमत हूं। लेकिन मैं आपको आश्वस्त कर सकता हूं कि इसका मतलब यह नहीं है कि एक चर एक उपयुक्त अभिव्यक्ति है।)
यदि आप @SortDirection को जोड़ने का प्रयास करते हैं, तो त्रुटि संदेश थोड़ा अधिक अपारदर्शी है:
Msg 102, लेवल 15, स्टेट 1, लाइन x'@SortDirection' के पास गलत सिंटैक्स।
इसके आस-पास कुछ तरीके हैं, और आपकी पहली प्रवृत्ति गतिशील SQL का उपयोग करना, या CASE अभिव्यक्ति को प्रस्तुत करना हो सकता है। लेकिन जैसा कि ज्यादातर चीजों के साथ होता है, ऐसी जटिलताएं होती हैं जो आपको एक या दूसरे रास्ते पर चलने के लिए मजबूर कर सकती हैं। तो आपको किसका उपयोग करना चाहिए? आइए जानें कि ये समाधान कैसे काम कर सकते हैं, और कुछ अलग तरीकों के प्रदर्शन पर पड़ने वाले प्रभावों की तुलना करें।
नमूना डेटा
कैटलॉग दृश्य का उपयोग करके हम सभी शायद अच्छी तरह से समझते हैं, sys.all_objects, मैंने क्रॉस जॉइन के आधार पर निम्न तालिका बनाई, तालिका को 100,000 पंक्तियों तक सीमित कर दिया (मुझे डेटा चाहिए था जो कई पृष्ठों को भर दे लेकिन उसमें क्वेरी करने में महत्वपूर्ण समय नहीं लगा और परीक्षण):
CREATE DATABASE OrderBy;GOUSE OrderBy;GO SELECT TOP (100000) key_col =ROW_NUMBER() OVER (ऑर्डर बाय s1.[object_id]), -- एक BIGINT जिसमें क्लस्टर्ड इंडेक्स s1 है।[object_id], -- बिना INT एक अनुक्रमणिका नाम =s1.name -- एक सहायक अनुक्रमणिका के साथ एक NVARCHAR COLLATE SQL_Latin1_General_CP1_CI_AS, type_desc =s1.type_desc -- एक NVARCHAR(60) बिना किसी अनुक्रमणिका के COLLATE SQL_Latin1_General_CP1_CI_AS, s1.modify_date -- एक अनुक्रमणिका के बिना एक डेटाटाइम FROM.sys. sys.all_objects AS s1 क्रॉस में शामिल हों sys.all_objects s2 द्वारा s1.[object_id];
(COLLATE ट्रिक इसलिए है क्योंकि कई कैटलॉग व्यू में अलग-अलग कॉलेशन के साथ अलग-अलग कॉलम होते हैं, और यह सुनिश्चित करता है कि इस डेमो के लिए दो कॉलम मेल खाएंगे।)
फिर मैंने एक विशिष्ट क्लस्टर/गैर-क्लस्टर इंडेक्स जोड़ी बनाई जो ऑप्टिमाइज़ेशन से पहले ऐसी तालिका पर मौजूद हो सकती है (मैं कुंजी के लिए ऑब्जेक्ट_आईडी का उपयोग नहीं कर सकता, क्योंकि क्रॉस जॉइन डुप्लीकेट बनाता है):
dbo.sys_objects(key_col) पर अद्वितीय क्लस्टर इंडेक्स key_col बनाएं; dbo.sys_objects(name);. पर इंडेक्स नाम बनाएं
मामलों का उपयोग करें
जैसा कि ऊपर उल्लेख किया गया है, उपयोगकर्ता इस डेटा को विभिन्न तरीकों से देखना चाहते हैं, तो आइए कुछ विशिष्ट उपयोग के मामलों को सेट करें जिनका हम समर्थन करना चाहते हैं (और समर्थन से, मेरा मतलब है प्रदर्शन):
- key_col आरोही द्वारा आदेश दिया गया ** डिफ़ॉल्ट यदि उपयोगकर्ता परवाह नहीं करता है
- ऑब्जेक्ट_आईडी द्वारा आदेशित (आरोही/अवरोही)
- नाम से आदेशित (आरोही/अवरोही)
- type_desc द्वारा आदेशित (आरोही/अवरोही)
- modify_date द्वारा आदेशित (आरोही/अवरोही)
हम key_col ऑर्डरिंग को डिफ़ॉल्ट के रूप में छोड़ देंगे क्योंकि अगर उपयोगकर्ता की प्राथमिकता नहीं है तो यह सबसे कुशल होना चाहिए; चूंकि key_col एक मनमाना सरोगेट है जिसका उपयोगकर्ता के लिए कोई मतलब नहीं होना चाहिए (और यहां तक कि उनके संपर्क में भी नहीं आ सकता है), उस कॉलम पर रिवर्स सॉर्टिंग की अनुमति देने का कोई कारण नहीं है।
ऐसे तरीके जो काम नहीं करते हैं
सबसे आम तरीका जो मैं देखता हूं जब कोई पहली बार इस समस्या से निपटना शुरू करता है तो क्वेरी के लिए नियंत्रण-प्रवाह तर्क पेश कर रहा है। वे ऐसा करने में सक्षम होने की उम्मीद करते हैं:
चुनें key_col, [object_id], name, type_desc, modify_dateFROM dbo.sys_objectsORDER IF @SortColumn ='key_col' key_colIF @SortColumn ='object_id' [object_id]IF @SortColumn ='name' नाम...IF @SortDirection ='एएससी' एस्सेल डीईएससी;
यह स्पष्ट रूप से काम नहीं करता है। इसके बाद मैं देखता हूं कि CASE को समान सिंटैक्स का उपयोग करके गलत तरीके से पेश किया जा रहा है:
चुनें key_col, [object_id], name, type_desc, modify_dateFROM dbo.sys_objectsORDER by CASE @SortColumn जब 'key_col' तब key_col जब 'object_id' तब [object_id] जब 'नाम' तब नाम ... END CASE @SortDirection 'ASC' फिर ASC और DESC END;
यह करीब है, लेकिन यह दो कारणों से विफल हो जाता है। एक यह है कि CASE एक ऐसा व्यंजक है जो किसी विशिष्ट डेटा प्रकार का ठीक एक मान लौटाता है; यह उन डेटा प्रकारों को मर्ज करता है जो असंगत हैं और इसलिए CASE अभिव्यक्ति को तोड़ देंगे। दूसरा यह है कि डायनेमिक SQL का उपयोग किए बिना इस तरह से क्रमबद्ध दिशा को सशर्त रूप से लागू करने का कोई तरीका नहीं है।
दृष्टिकोण जो काम करते हैं
मैंने जो तीन प्राथमिक दृष्टिकोण देखे हैं वे इस प्रकार हैं:
संगत प्रकारों और दिशाओं को एक साथ समूहित करें
ORDER BY के साथ CASE का उपयोग करने के लिए, संगत प्रकारों और दिशाओं के प्रत्येक संयोजन के लिए एक अलग अभिव्यक्ति होनी चाहिए। इस मामले में हमें कुछ इस तरह का उपयोग करना होगा:
CREATE PROCEDURE dbo.Sort_CaseExpanded @SortColumn NVARCHAR(128) =N'key_col', @SortDirection VARCHAR(4) ='ASC'ASBEGIN SET NOCOUNT ON; चयन key_col, [object_id], नाम, type_desc, dbo.sys_objects से संशोधित_तिथि करें जब @SortDirection ='ASC' तब केस @SortColumn जब 'key_col' तब key_col जब 'object_id' तब [END, CASE WEND END, CASE WEND END SortDirection ='DESC' तब केस @SortColumn जब 'key_col' तब key_col जब 'object_id' तब [object_id] END DESC, केस जब @SortDirection ='ASC' तब केस @SortColumn जब 'नाम' तब नाम जब 'type_desc' type_desc END END, CASE जब @SortDirection ='DESC' तब केस @SortColumn जब 'name' तब नाम जब 'type_desc' तब type_desc END END DESC, केस जब @SortColumn ='modify_date' और @SortDirection ='ASC' तब संशोधित करें , मामला जब @SortColumn ='modify_date' और @SortDirection ='DESC' तब संशोधित_तिथि END DESC;END
आप कह सकते हैं, वाह, यह कोड का एक बदसूरत सा है, और मैं आपसे सहमत हूं। मुझे लगता है कि यही कारण है कि बहुत से लोग अपने डेटा को फ्रंट एंड पर कैश करते हैं और प्रेजेंटेशन को अलग-अलग ऑर्डर में बाजीगरी से निपटने देते हैं। :-)
आप सभी गैर-स्ट्रिंग प्रकारों को स्ट्रिंग्स में परिवर्तित करके इस तर्क को थोड़ा और संक्षिप्त कर सकते हैं जो सही ढंग से क्रमबद्ध होंगे, उदा.
CREATE PROCEDURE dbo.Sort_CaseCaseCollapsed @SortColumn NVARCHAR(128) =N'key_col', @SortDirection VARCHAR(4) ='ASC'ASBEGIN SET NOCOUNT ON; चयन key_col, [object_id], नाम, type_desc, dbo.sys_objects से संशोधित_डेट ऑर्डर करें जब @SortDirection ='ASC' तब केस @SortColumn जब 'key_col' तब राइट ('000000000000' + RTRIM (key_col), 12) जब ' object_id' फिर सही (COALESCE(NULLIF(LEFT(RTRIM([object_id]),1)),'-'),'0') + REPLICATE('0', 23) + RTRIM([object_id]), 24) कब 'नाम' तब नाम जब 'type_desc' तब type_desc जब 'modify_date' तब कन्वर्ट (CHAR(19), mod_date, 120) END END, CASE जब @SortDirection ='DESC' तब केस @SortColumn जब 'key_col' तब राइट (' 000000000000' + आरटीआरआईएम (की_कॉल), 12) जब 'ऑब्जेक्ट_आईडी' तब दाएं (COALESCE(NULLIF(LEFT(RTRIM([object_id]),1),'-'),'0') + REPLICATE('0', 23 ) + RTRIM([object_id]), 24) जब 'नाम' तब नाम जब 'type_desc' तब type_desc जब 'modify_date' तब कनवर्ट करें (CHAR(19), mod_date, 120) END DESC;END
फिर भी, यह एक बहुत ही बदसूरत गड़बड़ है, और आपको विभिन्न प्रकार की दिशाओं से निपटने के लिए भावों को दो बार दोहराना होगा। मुझे यह भी संदेह होगा कि उस क्वेरी पर OPTION RECOMPILE का उपयोग करने से आपको पैरामीटर सूँघने से रोका जा सकेगा। डिफ़ॉल्ट मामले को छोड़कर, ऐसा नहीं है कि यहां किए जा रहे अधिकांश कार्य संकलन होने वाले हैं।
विंडो फ़ंक्शन का उपयोग करके रैंक लागू करें
मैंने एंड्रीएम से इस साफ-सुथरी चाल की खोज की, हालांकि यह उन मामलों में सबसे उपयोगी है जहां सभी संभावित ऑर्डरिंग कॉलम संगत प्रकार के हैं, अन्यथा ROW_NUMBER() के लिए उपयोग की जाने वाली अभिव्यक्ति समान रूप से जटिल है। सबसे चतुर बात यह है कि आरोही और अवरोही क्रम के बीच स्विच करने के लिए, हम बस ROW_NUMBER () को 1 या -1 से गुणा करते हैं। हम इसे इस स्थिति में इस प्रकार लागू कर सकते हैं:
क्रिएट प्रोसीजर dbo.Sort_RowNumber @SortColumn NVARCHAR(128) =N'key_col', @SortDirection VARCHAR(4) ='ASC'ASBEGIN SET NOCOUNT ON;;एक्स एएस के साथ (सेलेक्ट की_कॉल, [ऑब्जेक्ट_आईडी], नाम, टाइप_डेस्क, मॉडिफाई_डेट, आरएन =ROW_NUMBER () ओवर (केस द्वारा ऑर्डर करें @SortColumn जब 'key_col' तब राइट ('000000000000' + RTRIM (key_col), 12) जब ' object_id' फिर सही (COALESCE(NULLIF(LEFT(RTRIM([object_id]),1)),'-'),'0') + REPLICATE('0', 23) + RTRIM([object_id]), 24) कब 'नाम' तब नाम जब 'type_desc' तब type_desc जब 'modify_date' तब CONVERT(CHAR(19), mod_date, 120) END ) * केस @SortDirection जब 'ASC' तब 1 ELSE -1 END से dbo.sys_objects ) चुनें key_col , [object_id], नाम, type_desc, संशोधित_दिनांक x ORDER BY RN;ENDGO
फिर से, Option RECOMPILE यहाँ मदद कर सकता है। इसके अलावा, आप इनमें से कुछ मामलों में देख सकते हैं कि विभिन्न योजनाओं द्वारा संबंधों को अलग-अलग तरीके से संभाला जाता है - उदाहरण के लिए, नाम से ऑर्डर करते समय, आप आमतौर पर डुप्लिकेट नामों के प्रत्येक सेट के भीतर आरोही क्रम में key_col आते देखेंगे, लेकिन आप यह भी देख सकते हैं मूल्यों को मिला दिया। संबंधों की स्थिति में अधिक अनुमानित व्यवहार प्रदान करने के लिए, आप हमेशा अतिरिक्त ORDER BY क्लॉज जोड़ सकते हैं। ध्यान दें कि यदि आप पहले उदाहरण में key_col जोड़ना चाहते हैं, तो आपको इसे एक एक्सप्रेशन बनाना होगा ताकि key_col ORDER BY में दो बार सूचीबद्ध न हो (उदाहरण के लिए, आप key_col + 0 का उपयोग करके ऐसा कर सकते हैं)।
डायनामिक SQL
गतिशील एसक्यूएल के बारे में बहुत से लोगों के पास आरक्षण है - इसे पढ़ना असंभव है, यह एसक्यूएल इंजेक्शन के लिए प्रजनन स्थल है, यह कैश ब्लोट की योजना बनाता है, यह संग्रहीत प्रक्रियाओं का उपयोग करने के उद्देश्य को हरा देता है ... इनमें से कुछ बस असत्य हैं, और उनमें से कुछ कम करना आसान है। मैंने यहां कुछ सत्यापन जोड़ा है जिसे उपरोक्त किसी भी प्रक्रिया में आसानी से जोड़ा जा सकता है:
CREATE PROCEDURE dbo.Sort_DynamicSQL @SortColumn NVARCHAR(128) =N'key_col', @SortDirection VARCHAR(4) ='ASC'ASBEGIN SET NOCOUNT ON; - किसी भी अमान्य सॉर्ट दिशा-निर्देशों को अस्वीकार करें:यदि UPPER(@SortDirection) NOT IN ('ASC', 'DESC') BEGIN RAISERROR('@SortDirection के लिए अमान्य पैरामीटर:%s', 11, 1, @SortDirection); वापसी -1; END -- किसी भी अनपेक्षित कॉलम नाम को अस्वीकार करें:IF LOWER(@SortColumn) NOT IN (N'key_col', N'object_id', N'name', N'type_desc', N'modify_date') BEGIN RAISERROR('Invalid पैरामीटर for the @SortColumn:%s', 11, 1, @SortColumn); वापसी -1; अंत सेट @SortColumn =QUOTENAME(@SortColumn); DECLARE @sql NVARCHAR (MAX); SET @sql =N'SELECT key_col, [object_id], नाम, type_desc, dbo.sys_objects से संशोधित_तिथि '+ @SortColumn +' '+ @SortDirection +';'; EXEC sp_executesql @sql;END
प्रदर्शन तुलना
मैंने उपरोक्त प्रत्येक प्रक्रिया के लिए एक रैपर संग्रहीत प्रक्रिया बनाई, ताकि मैं आसानी से सभी परिदृश्यों का परीक्षण कर सकूं। चार आवरण प्रक्रियाएं इस तरह दिखती हैं, प्रक्रिया का नाम निश्चित रूप से भिन्न होता है:
CREATE PROCEDURE dbo.Test_Sort_CaseExpandedASBEGIN SET NOCOUNT ON; EXEC dbo.Sort_CaseExpanded; - डिफ़ॉल्ट EXEC dbo.Sort_CaseExpanded N'name', 'ASC'; EXEC dbo.Sort_CaseExpanded N'name', 'DESC'; EXEC dbo.Sort_CaseExpanded N'object_id', 'ASC'; EXEC dbo.Sort_CaseExpanded N'object_id', 'DESC'; EXEC dbo.Sort_CaseExpanded N'type_desc', 'ASC'; EXEC dbo.Sort_CaseExpanded N'type_desc', 'DESC'; EXEC dbo.Sort_CaseExpanded N'modify_date', 'ASC'; EXEC dbo.Sort_CaseExpanded N'modify_date', 'DESC';END
और फिर SQL संतरी योजना एक्सप्लोरर का उपयोग करते हुए, मैंने निम्नलिखित प्रश्नों के साथ वास्तविक निष्पादन योजनाएं (और इसके साथ जाने के लिए मेट्रिक्स) तैयार की, और कुल अवधि के योग के लिए प्रक्रिया को 10 बार दोहराया:
DBCC DROPCLEANBUFFERS;DBCC FREEPROCCACHE;EXEC dbo.Test_Sort_CaseExpanded;--EXEC dbo.Test_Sort_CaseCaseCaseCollapsed;--EXEC dbo.Test_Sort_RowNumber;--EXEC dbo.Test_Sort 10GO_Doमैंने OPTION RECOMPILE के साथ पहले तीन मामलों का भी परीक्षण किया (गतिशील SQL मामले के लिए बहुत मायने नहीं रखता है, क्योंकि हम जानते हैं कि यह हर बार एक नई योजना होगी), और समानांतरता हस्तक्षेप को खत्म करने के लिए MAXDOP 1 के साथ सभी चार मामले। ये रहे परिणाम:
निष्कर्ष
एकमुश्त प्रदर्शन के लिए, डायनेमिक SQL हर बार जीतता है (हालाँकि इस डेटा सेट पर केवल एक छोटे से अंतर से)। ROW_NUMBER() दृष्टिकोण, जबकि चतुर, प्रत्येक परीक्षा में हारने वाला था (क्षमा करें AndriyM)।
यह और भी मजेदार हो जाता है जब आप WHERE क्लॉज पेश करना चाहते हैं, पेजिंग पर ध्यान न दें। ये तीनों एक साधारण खोज क्वेरी के रूप में शुरू होने वाली जटिलता को पेश करने के लिए एकदम सही तूफान की तरह हैं। आपकी क्वेरी में जितने अधिक क्रमपरिवर्तन होंगे, उतनी ही अधिक संभावना है कि आप पठनीयता को विंडो से बाहर फेंकना चाहेंगे और अपने प्लान कैश में एकल-उपयोग योजनाओं के प्रभाव को कम करने के लिए "तदर्थ कार्यभार के लिए अनुकूलित करें" सेटिंग के संयोजन में गतिशील SQL का उपयोग करना चाहेंगे।