यह लेख टी-एसक्यूएल बग, नुकसान और सर्वोत्तम प्रथाओं के बारे में श्रृंखला का पांचवां भाग है। पहले मैंने नियतत्ववाद, सबक्वेरी, जॉइन और विंडोिंग को कवर किया था। इस महीने, मैं पिवोटिंग और अनपिवोटिंग को कवर करता हूं। अपने सुझावों को साझा करने के लिए धन्यवाद एरलैंड सोमरस्कोग, आरोन बर्ट्रेंड, एलेजांद्रो मेसा, उमाचंदर जयचंद्रन (यूसी), फैबियानो नेव्स अमोरिम, मिलोस रेडिवोजेविक, साइमन सबिन, एडम मचानिक, थॉमस ग्रोसर, चैन मिंग मैन और पॉल व्हाइट!
मेरे उदाहरणों में, मैं TSQLV5 नामक एक नमूना डेटाबेस का उपयोग करूँगा। आप इस डेटाबेस को बनाने और भरने वाली स्क्रिप्ट यहां और इसके ईआर आरेख यहां पा सकते हैं।
PIVOT के साथ निहित समूहीकरण
जब लोग T-SQL का उपयोग करके डेटा को पिवट करना चाहते हैं, तो वे या तो समूहीकृत क्वेरी और CASE अभिव्यक्तियों के साथ एक मानक समाधान का उपयोग करते हैं, या मालिकाना PIVOT तालिका ऑपरेटर का उपयोग करते हैं। PIVOT ऑपरेटर का मुख्य लाभ यह है कि इसका परिणाम छोटे कोड में होता है। हालांकि, इस ऑपरेटर में कुछ कमियां हैं, उनमें से एक अंतर्निहित डिज़ाइन ट्रैप है जिसके परिणामस्वरूप आपके कोड में बग हो सकते हैं। यहां मैं ट्रैप, संभावित बग और बग को रोकने वाले सर्वोत्तम अभ्यास का वर्णन करूंगा। मैं PIVOT ऑपरेटर के सिंटैक्स को इस तरह से बढ़ाने के सुझाव का भी वर्णन करूँगा जो बग से बचने में मदद करता है।
जब आप डेटा पिवट करते हैं तो समाधान में तीन चरण शामिल होते हैं, जिनमें तीन संबद्ध तत्व होते हैं:
- समूह पर आधारित/पंक्तियों के तत्वों पर आधारित समूह
- स्प्रेडिंग/कॉल्स एलिमेंट पर आधारित स्प्रेड
- एकत्रीकरण/डेटा तत्व के आधार पर एकत्र करें
PIVOT ऑपरेटर का सिंटैक्स निम्नलिखित है:
चुनेंFROM PIVOT( PIVOT ऑपरेटर के डिज़ाइन के लिए आपको एकत्रीकरण और प्रसार तत्वों को स्पष्ट रूप से निर्दिष्ट करने की आवश्यकता होती है, लेकिन SQL सर्वर को स्पष्ट रूप से उन्मूलन द्वारा समूहीकरण तत्व का पता लगाने देता है। PIVOT ऑपरेटर को इनपुट के रूप में प्रदान की गई स्रोत तालिका में जो भी कॉलम दिखाई देते हैं, वे परोक्ष रूप से समूहीकरण तत्व बन जाते हैं।
उदाहरण के लिए मान लीजिए कि आप TSQLV5 नमूना डेटाबेस में Sales.Orders तालिका को क्वेरी करना चाहते हैं। आप शिपर आईडी को पंक्तियों पर, शिप किए गए वर्षों को स्तंभों पर, और प्रति शिपर के आदेशों की संख्या और कुल के रूप में वर्ष वापस करना चाहते हैं।
बहुत से लोगों को PIVOT ऑपरेटर के सिंटैक्स को समझने में कठिनाई होती है, और इसके परिणामस्वरूप अक्सर अवांछित तत्वों द्वारा डेटा को समूहीकृत किया जाता है। हमारे कार्य के साथ एक उदाहरण के रूप में, मान लीजिए कि आप यह नहीं समझते हैं कि समूहीकरण तत्व परोक्ष रूप से निर्धारित होता है, और आप निम्नलिखित प्रश्न के साथ आते हैं:
शिपरिड चुनें, [2017], [2018], [2019]बिक्री से। ऑर्डर क्रॉस लागू (मूल्य (वर्ष (शिप किए गए))) एएस डी (शिपेड ईयर) पिवोट (काउंट (शिपेडडेट) शिप किए गए वर्ष के लिए ([2017]) , [2018], [2019])) एएस पी;डेटा में केवल तीन शिपर्स मौजूद हैं, जिनमें शिपर आईडी 1, 2 और 3 हैं। इसलिए आप परिणाम में केवल तीन पंक्तियों को देखने की उम्मीद कर रहे हैं। हालांकि, वास्तविक क्वेरी आउटपुट कई और पंक्तियां दिखाता है:
शिपरिड 2017 2018 2019-------------------------------------------------- -3 1 0 01 1 0 02 1 0 01 1 0 02 1 0 02 1 0 02 1 0 03 1 0 02 1 0 03 1 0 0...3 0 1 03 0 1 03 0 1 01 0 1 03 0 1 01 0 1 03 0 1 03 0 1 03 0 1 01 0 1 0...3 0 0 11 0 0 12 0 0 11 0 0 12 0 0 11 0 0 13 0 0 13 0 0 12 0 1 0...(830 पंक्तियां प्रभावित)क्या हुआ?
आप एक सुराग ढूंढ सकते हैं जो चित्र 1 में दिखाए गए क्वेरी प्लान को देखकर कोड में बग का पता लगाने में आपकी सहायता करेगा।
चित्र 1:निहित समूह के साथ पिवट क्वेरी के लिए योजना
क्वेरी में VALUES क्लॉज के साथ CROSS APPLY ऑपरेटर का उपयोग आपको भ्रमित न करने दें। यह केवल स्रोत शिप किए गए दिनांक कॉलम के आधार पर परिणाम कॉलम शिप किए गए वर्ष की गणना करने के लिए किया जाता है, और योजना में पहले कंप्यूट स्केलर ऑपरेटर द्वारा नियंत्रित किया जाता है।
PIVOT ऑपरेटर की इनपुट तालिका में Sales.Orders तालिका के सभी कॉलम, साथ ही शिप किए गए परिणाम कॉलम शामिल हैं। जैसा कि उल्लेख किया गया है, SQL सर्वर समूहीकरण तत्व को स्पष्ट रूप से उन्मूलन द्वारा निर्धारित करता है जिसे आपने एकत्रीकरण (शिपडेट) और प्रसार (शिपडियर) तत्वों के रूप में निर्दिष्ट नहीं किया है। शायद आप सहज रूप से शिपरिड कॉलम को ग्रुपिंग कॉलम होने की उम्मीद करते हैं क्योंकि यह चयन सूची में दिखाई देता है, लेकिन जैसा कि आप योजना में देख सकते हैं, व्यवहार में आपको ऑर्डरिड समेत कॉलम की एक लंबी सूची मिल गई है, जो प्राथमिक कुंजी कॉलम है स्रोत तालिका। इसका मतलब है कि प्रति शिपर एक पंक्ति प्राप्त करने के बजाय, आपको प्रति ऑर्डर एक पंक्ति मिल रही है। चूंकि SELECT सूची में आपने केवल शिपरिड, [2017], [2018] और [2019] कॉलम निर्दिष्ट किए हैं, आप बाकी को नहीं देखते हैं, जो भ्रम को बढ़ाता है। लेकिन बाकी ने निहित समूह में भाग लिया।
क्या अच्छा हो सकता है यदि PIVOT ऑपरेटर का सिंटैक्स एक क्लॉज का समर्थन करता है जहां आप स्पष्ट रूप से समूहीकरण/पंक्तियों पर तत्व को इंगित कर सकते हैं। कुछ इस तरह:
चुनेंसे PIVOT( इस सिंटैक्स के आधार पर आप हमारे कार्य को संभालने के लिए निम्नलिखित कोड का उपयोग करेंगे:
शिपरिड चुनें, [2017], [2018], [2019]बिक्री से। ऑर्डर क्रॉस लागू (मूल्य (वर्ष (शिप किए गए))) एएस डी (शिपेड ईयर) पिवोट (काउंट (शिपेडडेट) शिप किए गए वर्ष के लिए ([2017]) , [2018], [2019]) ON ROWS शिपरिड ) AS P;आप यहां पिवोट ऑपरेटर के सिंटैक्स को बेहतर बनाने के सुझाव के साथ फीडबैक आइटम पा सकते हैं। इस सुधार को एक अटूट परिवर्तन बनाने के लिए, इस खंड को वैकल्पिक बनाया जा सकता है, जिसमें डिफ़ॉल्ट मौजूदा व्यवहार है। PIVOT ऑपरेटर के सिंटैक्स को और अधिक गतिशील बनाकर और कई समुच्चय का समर्थन करके इसे बेहतर बनाने के लिए अन्य सुझाव हैं।
इस बीच, एक सर्वोत्तम अभ्यास है जो आपको बग से बचने में मदद कर सकता है। एक टेबल एक्सप्रेशन का उपयोग करें जैसे कि सीटीई या एक व्युत्पन्न तालिका जहां आप केवल तीन तत्वों को प्रोजेक्ट करते हैं जिन्हें आपको पिवट ऑपरेशन में शामिल करने की आवश्यकता होती है, और फिर टेबल एक्सप्रेशन को इनपुट के रूप में PIVOT ऑपरेटर के लिए उपयोग करें। इस तरह, आप समूहीकरण तत्व को पूरी तरह से नियंत्रित करते हैं। इस सर्वोत्तम अभ्यास का अनुसरण करने वाला सामान्य वाक्य-विन्यास यहां दिया गया है:
के साथ <सीटीई_नाम> एएस (चुनें <ग्रुप_कॉल>, <स्प्रेड_कॉल>, <एग्रीगेट_कॉल> <सोर्स_टेबल> से) चुनें) ) AS <उपनाम>; हमारे कार्य के लिए लागू, आप निम्न कोड का उपयोग करते हैं:
सी एएस के साथ (शिप किए गए वर्ष के रूप में शिपरिड का चयन करें, वर्ष (शिप किए जाने की तारीख) के रूप में, बिक्री से शिप की गई तारीख। 2017], [2018], [2019])) एएस पी;इस बार आपको अपेक्षानुसार केवल तीन परिणाम पंक्तियाँ मिलेंगी:
शिपरिड 2017 2018 2019-------------------------------------------------- -3 51 125 731 36 130 792 56 143 116एक अन्य विकल्प समूहबद्ध क्वेरी और CASE अभिव्यक्तियों का उपयोग करके पिवट करने के लिए पुराने और क्लासिक मानक समाधान का उपयोग करना है, जैसे:
शिपरिड चुनें, COUNT(केस जब शिप किया गया वर्ष =2017 तब 1 समाप्त) जैसा [2017], COUNT(केस जब शिप किया गया वर्ष =2018 तब 1 समाप्त) जैसा [2018], COUNT(केस जब शिप किया गया वर्ष =2019 तब 1 समाप्त) AS [2019] बिक्री से। ऑर्डर क्रॉस लागू (मान (वर्ष (शिप किए गए))) के रूप में डी (शिपेड वर्ष) जहां शिप की गई तारीख शिपरिड द्वारा पूर्ण समूह नहीं है;इस सिंटैक्स के साथ सभी तीन पिवोटिंग चरण और उनके संबद्ध तत्वों को कोड में स्पष्ट होना चाहिए। हालाँकि, जब आपके पास बड़ी संख्या में फैलने वाले मान होते हैं, तो यह वाक्य-विन्यास क्रियात्मक हो जाता है। ऐसे मामलों में, लोग अक्सर PIVOT ऑपरेटर का उपयोग करना पसंद करते हैं।
UNPIVOT के साथ NULLs का निहित निष्कासन
इस लेख में अगला आइटम बग की तुलना में अधिक नुकसान है। इसे मालिकाना T-SQL UNPIVOT ऑपरेटर के साथ करना है, जो आपको कॉलम की स्थिति से डेटा को पंक्तियों की स्थिति में अनपिवट करने देता है।
मैं अपने नमूना डेटा के रूप में CustOrders नामक तालिका का उपयोग करूँगा। इस तालिका को बनाने, पॉप्युलेट करने और इसकी सामग्री दिखाने के लिए क्वेरी करने के लिए निम्न कोड का उपयोग करें:
ड्रॉप टेबल अगर मौजूद है dbo.CustOrders;C AS के साथ जाएं (Custid, YEAR(orderdate) as orderyear, val FROM Sales.OrderValues) सेलेक्ट कस्टिड, [2017], [2018], [2019]INTO dbo.CustOrdersFROM C PIVOT(SUM(val) for orderyear IN([2017], [2018], [2019]) ) AS P; चुनें * dbo.CustOrders से;यह कोड निम्न आउटपुट उत्पन्न करता है:
कस्टिड 2017 2018 2019 --------------------------------------1 NULL 2022.50 2250.502 88.80 799.75 514.403 403.20 5960.78 660.004 1379.00 6406.90 5604.755 4324.40 13849.02 6754.166 NULL 1079.80 2160.007 9986.20 7817.88 730.008 982.00 3026.85 224.009 4074.28 11208.36 6680.6110 1832.80 7630.25 11338.5611 479.40 3179.50 2431.0012 NULL 238.00 1576.8013 100.80 NULL NULL14 1674.22 6516.40 4158.2615 2169.00 1128.00 513.7516 NULL 787.60 931.5017 533.60 420.00 2809.6118 268.80 487.00 860.1019 950.00 4514.35 9296.6920 15568.07 48096.27 41210.65...यह तालिका प्रति ग्राहक और वर्ष के लिए कुल ऑर्डर मान रखती है। एनयूएलएल उन मामलों का प्रतिनिधित्व करते हैं जहां ग्राहक के पास लक्षित वर्ष में कोई ऑर्डर गतिविधि नहीं होती है।
मान लीजिए कि आप CustOrders तालिका से डेटा को अनपिवट करना चाहते हैं, प्रति ग्राहक और वर्ष के लिए एक पंक्ति लौटाते हुए, वर्तमान ग्राहक और वर्ष के लिए कुल ऑर्डर मान रखने वाले वैल नामक परिणाम कॉलम के साथ। किसी भी अविभाजित कार्य में आम तौर पर तीन तत्व शामिल होते हैं:
- मौजूदा स्रोत कॉलम के नाम जिन्हें आप अनपिवोट कर रहे हैं:[2017], [2018], [2019] हमारे मामले में
- एक नाम जिसे आप लक्ष्य कॉलम में निर्दिष्ट करते हैं जो स्रोत कॉलम नाम रखेगा:हमारे मामले में आदेश वर्ष
- एक नाम जिसे आप लक्ष्य कॉलम को निर्दिष्ट करते हैं जो स्रोत कॉलम मानों को धारण करेगा:हमारे मामले में वैल
यदि आप अनपिवोटिंग कार्य को संभालने के लिए UNPIVOT ऑपरेटर का उपयोग करने का निर्णय लेते हैं, तो आप पहले उपरोक्त तीन तत्वों का पता लगाते हैं, और फिर निम्नलिखित सिंटैक्स का उपयोग करते हैं:
चुनेंहमारे कार्य के लिए लागू, आप निम्न क्वेरी का उपयोग करें:
सेलेक्ट कस्टिड, ऑर्डर ईयर, वैलFROM dbo.CustOrders UNPIVOT( वैल फॉर ऑर्डर ईयर IN([2017], [2018], [2019]) AS U;यह क्वेरी निम्न आउटपुट उत्पन्न करती है:
कस्टिड ऑर्डरवर्ष वैल------------------------------1 2018 2022.501 2019 2250.502 2017 88.802 2018 799.752 2019 514.403 2017 403.203 2018 5960.783 2019 660.004 2017 1379.004 2018 6406.904 2019 5604.755 2017 4324.405 2018 13849.025 2019 6754.166 2018 1079.806 2019 2160.007 2017 9986.207 2018 7817.887 2019 730.00...स्रोत डेटा और क्वेरी परिणाम को देखते हुए, क्या आप देखते हैं कि क्या गुम है?
UNPIVOT ऑपरेटर के डिज़ाइन में परिणाम पंक्तियों का एक अंतर्निहित उन्मूलन शामिल है जिसमें हमारे मामले में मान कॉलम-वैल में एक NULL है। चित्र 2 में दिखाई गई इस क्वेरी के लिए निष्पादन योजना को देखते हुए, आप फ़िल्टर ऑपरेटर को वैल कॉलम में NULLs के साथ पंक्तियों को हटाते हुए देख सकते हैं (योजना में Expr1007)।
चित्र 2:NULLs के निहित निष्कासन के साथ अनपिवट क्वेरी के लिए योजना
कभी-कभी यह व्यवहार वांछनीय होता है, ऐसे में आपको कुछ विशेष करने की आवश्यकता नहीं होती है। समस्या यह है कि कभी-कभी आप पंक्तियों को एनयूएलएल के साथ रखना चाहते हैं। नुकसान तब होता है जब आप NULLs रखना चाहते हैं और आपको यह एहसास भी नहीं होता है कि UNPIVOT ऑपरेटर को उन्हें हटाने के लिए डिज़ाइन किया गया है।
क्या अच्छा हो सकता है यदि UNPIVOT ऑपरेटर के पास एक वैकल्पिक खंड था जो आपको यह निर्दिष्ट करने की अनुमति देता है कि क्या आप NULLs को हटाना या रखना चाहते हैं, पूर्व में पिछड़े संगतता के लिए डिफ़ॉल्ट है। यह सिंटैक्स कैसा दिखाई दे सकता है, इसका एक उदाहरण यहां दिया गया है:
चुनें, , से UNPIVOT( /पूर्व> यदि आप इस सिंटैक्स के आधार पर NULLs रखना चाहते हैं, तो आप निम्न क्वेरी का उपयोग करेंगे:
सेलेक्ट कस्टिड, ऑर्डर ईयर, वैलफ्रॉम dbo.CustOrders UNPIVOT( वैल फॉर ऑर्डर ईयर IN([2017], [2018], [2019]) KEEP NULLS ) AS U;आप इस तरह से UNPIVOT ऑपरेटर के सिंटैक्स को बेहतर बनाने के सुझाव के साथ फीडबैक आइटम पा सकते हैं।
इस बीच, यदि आप पंक्तियों को NULLs के साथ रखना चाहते हैं, तो आपको वर्कअराउंड के साथ आना होगा। यदि आप UNPIVOT ऑपरेटर का उपयोग करने पर जोर देते हैं, तो आपको दो चरणों को लागू करने की आवश्यकता है। पहले चरण में आप एक क्वेरी के आधार पर एक टेबल एक्सप्रेशन को परिभाषित करते हैं जो ISNULL या COALESCE फ़ंक्शन का उपयोग करके सभी अप्रकाशित स्तंभों में NULLs को एक मान के साथ प्रतिस्थापित करता है जो सामान्य रूप से डेटा में प्रकट नहीं हो सकता है, उदाहरण के लिए, -1 हमारे मामले में। दूसरे चरण में आप NULLIF फ़ंक्शन का उपयोग मान कॉलम के विरुद्ध बाहरी क्वेरी में -1 को वापस NULL से बदलने के लिए करते हैं। यहां संपूर्ण समाधान कोड दिया गया है:
सी एएस के साथ (सेलेक्ट कस्टिड, आईएसएनयूएलएल([2017], -1.0) एएस [2017], आईएसएनयूएलएल([2018], -1.0) एएस [2018], आईएसएनयूएलएल([2019], -1.0) एएस [2019 ] dbo.CustOrders से) सेलेक्ट कस्टिड, ऑर्डर ईयर, NULLIF(val, -1.0) AS valFROM C UNPIVOT(वैल फॉर ऑर्डर ईयर IN([2017], [2018], [2019]) AS U;यहाँ इस क्वेरी का आउटपुट दिखा रहा है कि वैल कॉलम में NULLs वाली पंक्तियाँ संरक्षित हैं:
कस्टिड ऑर्डरवर्ष वैल--------------------------1 2017 NULL1 2018 2022.501 2019 2250.502 2017 88.802 2018 799.752 2019 514.403 2017 403.203 2018 5960.783 2019 660.004 2017 1379.004 2018 6406.904 2019 5604.755 2017 4324.405 2018 13849.025 2019 6754.166 2017 NULL6 2018 1079.806 2019 2160.007 2017 9986.207 2018 7817.887 2019 730.00...यह तरीका अजीब है, खासकर जब आपके पास अनपिवट करने के लिए बड़ी संख्या में कॉलम हों।
एक वैकल्पिक समाधान APPLY ऑपरेटर और VALUES क्लॉज के संयोजन का उपयोग करता है। आप प्रत्येक अविभाजित स्तंभ के लिए एक पंक्ति का निर्माण करते हैं, जिसमें एक स्तंभ लक्ष्य नाम स्तंभ (हमारे मामले में क्रम वर्ष) का प्रतिनिधित्व करता है, और दूसरा लक्ष्य मान स्तंभ (हमारे मामले में वैल) का प्रतिनिधित्व करता है। आप नाम स्तंभ के लिए स्थिर वर्ष और मान स्तंभ के लिए प्रासंगिक सहसंबद्ध स्रोत स्तंभ प्रदान करते हैं। यहां संपूर्ण समाधान कोड दिया गया है:
सेलेक्ट कस्टिड, ऑर्डर ईयर, वैलFROM dbo.CustOrders CROSS APPLY ( VALUES(2017, [2017]), (2018, [2018]), (2019, [2019]) ) AS A(orderyear, val);यहां अच्छी बात यह है कि जब तक आप वैल कॉलम में एनयूएलएल के साथ पंक्तियों को हटाने में रुचि नहीं रखते हैं, तब तक आपको कुछ विशेष करने की आवश्यकता नहीं है। यहां कोई अंतर्निहित कदम नहीं है जो एनयूएलएस के साथ पंक्तियों को हटा देता है। इसके अलावा, चूंकि वैल कॉलम उपनाम FROM क्लॉज के हिस्से के रूप में बनाया गया है, यह WHERE क्लॉज तक पहुंच योग्य है। इसलिए, यदि आप एनयूएलएल को हटाने में रुचि रखते हैं, तो आप इसके बारे में WHERE क्लॉज में सीधे मान कॉलम उपनाम के साथ बातचीत करके स्पष्ट कर सकते हैं, जैसे:
सेलेक्ट कस्टिड, ऑर्डर ईयर, वैलFROM dbo.CustOrders CROSS APPLY ( VALUES(2017, [2017]), (2018, [2018]), (2019, [2019]) ) AS A(orderyear, val)WHERE val IS न्यूल नहीं;मुद्दा यह है कि यह वाक्यविन्यास आपको नियंत्रण देता है कि आप एनयूएलएल रखना या हटाना चाहते हैं या नहीं। यह एक अन्य तरीके से UNPIVOT ऑपरेटर की तुलना में अधिक लचीला है, जिससे आप वैल और qty दोनों जैसे कई अनपेक्षित उपायों को संभाल सकते हैं। इस लेख में मेरा ध्यान हालांकि एनयूएलएल से जुड़े नुकसान पर था, इसलिए मैं इस पहलू में नहीं आया।
निष्कर्ष
PIVOT और UNPIVOT ऑपरेटरों का डिज़ाइन कभी-कभी आपके कोड में बग और नुकसान का कारण बनता है। PIVOT ऑपरेटर का सिंटैक्स आपको समूहीकरण तत्व को स्पष्ट रूप से इंगित नहीं करने देता है। यदि आपको इसका एहसास नहीं है, तो आप अवांछित समूहीकरण तत्वों के साथ समाप्त हो सकते हैं। सर्वोत्तम अभ्यास के रूप में, यह अनुशंसा की जाती है कि आप PIVOT ऑपरेटर के इनपुट के रूप में एक तालिका अभिव्यक्ति का उपयोग करें, और यही कारण है कि स्पष्ट रूप से समूहीकरण तत्व को नियंत्रित करता है।
UNPIVOT ऑपरेटर का सिंटैक्स आपको यह नियंत्रित करने की अनुमति नहीं देता है कि परिणाम मान कॉलम में NULLs वाली पंक्तियों को निकालना या रखना है या नहीं। समाधान के रूप में, आप या तो ISNULL और NULLIF फ़ंक्शंस के साथ एक अजीब समाधान का उपयोग करते हैं, या APPLY ऑपरेटर और VALUES क्लॉज पर आधारित समाधान का उपयोग करते हैं।
मैंने पिवोट और यूएनपीवीओटी ऑपरेटरों को बेहतर बनाने के सुझावों के साथ दो फीडबैक आइटम का भी उल्लेख किया है, जिसमें ऑपरेटर और उसके तत्वों के व्यवहार को नियंत्रित करने के लिए अधिक स्पष्ट विकल्प हैं।