यह लेख टी-एसक्यूएल बग, नुकसान और सर्वोत्तम प्रथाओं के बारे में श्रृंखला में चौथी किस्त है। पहले मैंने नियतत्ववाद, उपश्रेणियों और जुड़ावों को कवर किया था। इस महीने के लेख का फोकस विंडो फ़ंक्शंस से संबंधित बग, नुकसान और सर्वोत्तम अभ्यास है। अपने विचारों को प्रस्तुत करने के लिए धन्यवाद एरलैंड सोमरस्कोग, आरोन बर्ट्रेंड, एलेजांद्रो मेसा, उमाचंदर जयचंद्रन (यूसी), फैबियानो नेव्स अमोरिम, मिलोस रेडिवोजेविक, साइमन सबिन, एडम मचानिक, थॉमस ग्रोसर, चैन मिंग मैन और पॉल व्हाइट!
अपने उदाहरणों में मैं TSQLV5 नामक एक नमूना डेटाबेस का उपयोग करूँगा। आप इस डेटाबेस को बनाने और भरने वाली स्क्रिप्ट यहां और इसके ईआर आरेख यहां पा सकते हैं।
विंडो फ़ंक्शंस से जुड़े दो सामान्य नुकसान हैं, जिनमें से दोनों SQL मानक द्वारा लगाए गए प्रति-सहज अंतर्निहित चूक का परिणाम हैं। एक नुकसान रनिंग टोटल की गणना के साथ करना है जहां आपको निहित RANGE विकल्प के साथ एक विंडो फ्रेम मिलता है। एक और नुकसान कुछ हद तक संबंधित है, लेकिन इसके अधिक गंभीर परिणाम हैं, जिसमें FIRST_VALUE और LAST_VALUE फ़ंक्शन के लिए एक अंतर्निहित फ़्रेम परिभाषा शामिल है।
अंतर्निहित RANGE विकल्प के साथ विंडो फ़्रेम
हमारे पहले नुकसान में कुल विंडो फ़ंक्शन का उपयोग करके रनिंग टोटल की गणना शामिल है, जहां आप विंडो ऑर्डर क्लॉज को स्पष्ट रूप से निर्दिष्ट करते हैं, लेकिन आप विंडो फ्रेम यूनिट (ROWS या RANGE) और इसकी संबंधित विंडो फ्रेम सीमा, जैसे, ROWS को स्पष्ट रूप से निर्दिष्ट नहीं करते हैं। असीमित पूर्ववर्ती। निहित डिफ़ॉल्ट उल्टा है और इसके परिणाम आश्चर्यजनक और दर्दनाक हो सकते हैं।
इस नुकसान को प्रदर्शित करने के लिए, मैं क्रेडिट (सकारात्मक मान) और डेबिट (नकारात्मक मान) के साथ दो मिलियन बैंक खाता लेनदेन रखने वाले लेनदेन नामक एक तालिका का उपयोग करूंगा। लेन-देन तालिका बनाने और नमूना डेटा के साथ इसे भरने के लिए निम्न कोड चलाएँ:
NOCOUNT ON सेट करें; TSQLV5 का उपयोग करें; -- http://tsql.solidq.com/SampleDatabases/TSQLV5.zip ड्रॉप टेबल यदि मौजूद है dbo.Transactions; क्रिएट टेबल dbo.Transactions (actid INT NOT NULL, tranid INT NOT NULL, val MONEY NOT NULL, CONSTRAINT PK_Transactions PRIMARY KEY(actid, tranid) -- POC इंडेक्स बनाता है); DECLARE @num_partitions AS INT =100, @rows_per_partition AS INT =20000; (TABLOCK) (actid, tranid, val) सेलेक्ट NP.n, RPP.n, (ABS(CHECKSUM(NEWID())%2)*2-1) * (1 + ABS(CHECKSUM() NEWID ())% 5)) से dbo.GetNums(1, @num_partitions) AS NP CROSS JOIN dbo.GetNums(1, @rows_per_partition) AS RPP;
हमारे नुकसान में संभावित तार्किक बग के साथ-साथ प्रदर्शन दंड के साथ प्रदर्शन पक्ष दोनों के लिए एक तार्किक पक्ष है। प्रदर्शन दंड केवल तभी प्रासंगिक होता है जब विंडो फ़ंक्शन पंक्ति-मोड प्रसंस्करण ऑपरेटरों के साथ अनुकूलित होता है। SQL सर्वर 2016 बैच-मोड विंडो एग्रीगेट ऑपरेटर का परिचय देता है, जो नुकसान के प्रदर्शन दंड भाग को हटा देता है, लेकिन SQL सर्वर 2019 से पहले इस ऑपरेटर का उपयोग केवल तभी किया जाता है जब आपके पास डेटा पर कॉलमस्टोर इंडेक्स मौजूद हो। SQL सर्वर 2019 रोस्टोर सपोर्ट पर बैच मोड पेश करता है, जिससे आप बैच-मोड प्रोसेसिंग प्राप्त कर सकते हैं, भले ही डेटा पर कोई कॉलमस्टोर इंडेक्स मौजूद न हो। पंक्ति-मोड प्रसंस्करण के साथ प्रदर्शन दंड प्रदर्शित करने के लिए, यदि आप इस आलेख में SQL सर्वर 2019 या बाद में, या Azure SQL डेटाबेस पर कोड नमूने चला रहे हैं, तो डेटाबेस संगतता स्तर को 140 पर सेट करने के लिए निम्न कोड का उपयोग करें। अभी तक पंक्ति स्टोर पर बैच-मोड सक्षम नहीं करने के लिए:
वैकल्पिक डेटाबेस TSQLV5 SET COMPATIBILITY_LEVEL =140;
सत्र में समय और I/O आंकड़े चालू करने के लिए निम्न कोड का उपयोग करें:
सांख्यिकी समय सेट करें, IO ON;
एसएसएमएस में दो मिलियन पंक्तियों के प्रिंट होने की प्रतीक्षा से बचने के लिए, मेरा सुझाव है कि निष्पादन विकल्प चालू होने के बाद परिणाम छोड़ें (क्वेरी विकल्प, परिणाम, ग्रिड पर जाएं और निष्पादन के बाद परिणाम छोड़ें) की जांच करें। /पी>
इससे पहले कि हम संकट में आएं, निम्नलिखित प्रश्न पर विचार करें (इसे क्वेरी 1 कहते हैं) जो एक स्पष्ट फ्रेम विनिर्देश के साथ विंडो एग्रीगेट फ़ंक्शन का उपयोग करके प्रत्येक लेनदेन के बाद बैंक खाते की शेष राशि की गणना करता है:
dbo.Transactions से शेष राशि के रूप में, actid, tranid, val, SUM(val) over (एक्टिड ROWS द्वारा PARTITION BY TRANSID ROWS UNBOUNDED PRECEDING चुनें) चुनेंपंक्ति-मोड प्रसंस्करण का उपयोग करते हुए इस क्वेरी की योजना चित्र 1 में दिखाई गई है।
चित्र 1:क्वेरी 1 के लिए योजना, पंक्ति-मोड प्रसंस्करण
योजना तालिका के संकुल सूचकांक से पहले से क्रमबद्ध डेटा को खींचती है। फिर यह सेगमेंट और सीक्वेंस प्रोजेक्ट ऑपरेटरों का उपयोग पंक्ति संख्याओं की गणना करने के लिए करता है ताकि यह पता लगाया जा सके कि कौन सी पंक्तियाँ वर्तमान पंक्ति के फ्रेम से संबंधित हैं। फिर यह विंडो एग्रीगेट फ़ंक्शन की गणना करने के लिए सेगमेंट, विंडो स्पूल और स्ट्रीम एग्रीगेट ऑपरेटरों का उपयोग करता है। विंडो स्पूल ऑपरेटर का उपयोग फ्रेम पंक्तियों को स्पूल करने के लिए किया जाता है, जिसे बाद में एकत्रित करने की आवश्यकता होती है। किसी विशेष अनुकूलन के बिना, योजना को स्पूल में अपनी सभी लागू फ्रेम पंक्तियों को प्रति पंक्ति लिखना होगा, और फिर उन्हें एकत्रित करना होगा। इसका परिणाम द्विघात, या एन, जटिलता में होता। अच्छी खबर यह है कि जब फ़्रेम अनबाउंडेड PRECEDING से शुरू होता है, तो SQL सर्वर मामले की पहचान फास्ट ट्रैक के रूप में करता है। मामला, जिसमें यह केवल पिछली पंक्ति के रनिंग टोटल को लेता है और वर्तमान पंक्ति के मान को वर्तमान पंक्ति के रनिंग टोटल की गणना करने के लिए जोड़ता है, जिसके परिणामस्वरूप रैखिक स्केलिंग होती है। इस फास्ट ट्रैक मोड में, योजना स्पूल प्रति इनपुट पंक्ति में केवल दो पंक्तियाँ लिखती है—एक समग्र के साथ, और एक विवरण के साथ।
विंडो स्पूल को भौतिक रूप से दो तरीकों में से एक में कार्यान्वित किया जा सकता है। या तो एक तेज़ इन-मेमोरी स्पूल के रूप में जिसे विशेष रूप से विंडो फ़ंक्शंस के लिए डिज़ाइन किया गया था, या एक धीमी ऑन-डिस्क स्पूल के रूप में, जो अनिवार्य रूप से tempdb में एक अस्थायी तालिका है। यदि पंक्तियों की संख्या जिन्हें स्पूल में लिखे जाने की आवश्यकता है प्रति अंतर्निहित पंक्ति 10,000 से अधिक हो सकता है, या यदि SQL सर्वर संख्या की भविष्यवाणी नहीं कर सकता है, तो यह धीमी ऑन-डिस्क स्पूल का उपयोग करेगा। हमारी क्वेरी योजना में, हमारे पास स्पूल प्रति अंतर्निहित पंक्ति में ठीक दो पंक्तियाँ हैं, इसलिए SQL सर्वर इन-मेमोरी स्पूल का उपयोग करता है। दुर्भाग्य से, योजना से यह बताने का कोई तरीका नहीं है कि आपको किस प्रकार का स्पूल मिल रहा है। इसका पता लगाने के दो तरीके हैं। एक एक विस्तारित घटना का उपयोग करना है जिसे window_spool_ondisk_warning कहा जाता है। एक अन्य विकल्प सांख्यिकी IO को सक्षम करना है, और वर्कटेबल नामक तालिका के लिए रिपोर्ट किए गए तार्किक रीड्स की संख्या की जांच करना है। शून्य से अधिक संख्या का अर्थ है कि आपको ऑन-डिस्क स्पूल मिल गया है। ज़ीरो का मतलब है कि आपको इन-मेमोरी स्पूल मिल गया है। हमारी क्वेरी के लिए I/O आंकड़े यहां दिए गए हैं:
तालिका 'वर्कटेबल' तार्किक पढ़ता है:0. तालिका 'लेनदेन' तार्किक पढ़ता है:6208।जैसा कि आप देख सकते हैं, हमें इन-मेमोरी स्पूल का उपयोग किया गया है। आम तौर पर ऐसा तब होता है जब आप पहले सीमांकक के रूप में UNBOUNDED PRECEDING के साथ ROWS विंडो फ्रेम यूनिट का उपयोग करते हैं।
यहां हमारी क्वेरी के लिए समय के आंकड़े दिए गए हैं:
सीपीयू समय:4297 एमएस, बीता हुआ समय:4441 एमएस।इस क्वेरी को मेरी मशीन पर पूरा करने में लगभग 4.5 सेकंड का समय लगा और परिणाम खारिज कर दिए गए।
अब पकड़ने के लिए। यदि आप समान सीमांकक के साथ ROWS के बजाय RANGE विकल्प का उपयोग करते हैं, तो अर्थ में एक सूक्ष्म अंतर हो सकता है, लेकिन पंक्ति मोड में प्रदर्शन में एक बड़ा अंतर हो सकता है। अर्थ में अंतर केवल तभी प्रासंगिक है जब आपके पास कुल ऑर्डरिंग नहीं है, यानी, यदि आप किसी ऐसी चीज़ से ऑर्डर कर रहे हैं जो अद्वितीय नहीं है। ROWS UNBOUNDED PRECEDING विकल्प वर्तमान पंक्ति के साथ रुक जाता है, इसलिए संबंधों के मामले में, गणना गैर-निर्धारक है। इसके विपरीत, RANGE UNBOUNDED PRECEDING विकल्प वर्तमान पंक्ति से आगे दिखता है, और यदि मौजूद है तो इसमें संबंध शामिल हैं। यह टॉप विद टाईज विकल्प के समान तर्क का उपयोग करता है। जब आपके पास कुल ऑर्डरिंग होती है, यानी, आप किसी अनोखी चीज़ से ऑर्डर कर रहे होते हैं, तो इसमें शामिल करने के लिए कोई संबंध नहीं होते हैं, और इसलिए ऐसे मामले में ROWS और RANGE तार्किक रूप से समान हो जाते हैं। समस्या यह है कि जब आप RANGE का उपयोग करते हैं, तो SQL सर्वर हमेशा पंक्ति-मोड प्रसंस्करण के तहत ऑन-डिस्क स्पूल का उपयोग करता है क्योंकि किसी दी गई पंक्ति को संसाधित करते समय यह भविष्यवाणी नहीं कर सकता कि कितनी अधिक पंक्तियाँ शामिल होंगी। यह एक गंभीर प्रदर्शन दंड हो सकता है।
निम्नलिखित क्वेरी पर विचार करें (इसे क्वेरी 2 कहते हैं), जो कि क्वेरी 1 के समान है, केवल ROWS के बजाय RANGE विकल्प का उपयोग करना:
dbo.Transactions से बैलेंस के रूप में एक्टिड, ट्रैनिड, वैल, एसयूएम (वैल) ओवर चुनें (पार्टिशन बाय एक्टिड ऑर्डर बाय ट्रैनिड रेंज अनबाउंडेड प्रीसीडिंग)इस क्वेरी की योजना चित्र 2 में दिखाई गई है।
चित्र 2:क्वेरी 2 के लिए योजना, पंक्ति-मोड संसाधन
क्वेरी 2 तार्किक रूप से क्वेरी 1 के बराबर है क्योंकि हमारे पास कुल ऑर्डर है; हालांकि, चूंकि यह RANGE का उपयोग करता है, इसलिए यह ऑन-डिस्क स्पूल के साथ अनुकूलित हो जाता है। ध्यान दें कि क्वेरी 2 की योजना में विंडो स्पूल क्वेरी 1 की योजना की तरह ही दिखती है और अनुमानित लागतें समान हैं।
यहां क्वेरी 2 के निष्पादन के लिए समय और I/O आंकड़े दिए गए हैं:
CPU समय:19515 ms, बीता हुआ समय:20201 ms.
टेबल 'वर्कटेबल' लॉजिकल रीड्स:12044701. टेबल 'लेन-देन' लॉजिकल रीड्स:6208।वर्कटेबल के खिलाफ बड़ी संख्या में तार्किक रीड्स पर ध्यान दें, यह दर्शाता है कि आपको ऑन-डिस्क स्पूल मिला है। रन टाइम क्वेरी 1 की तुलना में चार गुना अधिक लंबा है।
यदि आप सोच रहे हैं कि यदि ऐसा है, तो आप केवल RANGE विकल्प का उपयोग करने से बचेंगे, जब तक कि आपको वास्तव में संबंधों को शामिल करने की आवश्यकता न हो, यह अच्छी सोच है। समस्या यह है कि यदि आप एक विंडो फ़ंक्शन का उपयोग करते हैं जो एक स्पष्ट विंडो ऑर्डर क्लॉज के साथ एक फ्रेम (समुच्चय, FIRST_VALUE, LAST_VALUE) का समर्थन करता है, लेकिन विंडो फ्रेम यूनिट और इसकी संबद्ध सीमा का कोई उल्लेख नहीं है, तो आपको डिफ़ॉल्ट रूप से RANGE UNBOUNDED PRECEDING मिल रहा है। . यह डिफ़ॉल्ट SQL मानक द्वारा निर्धारित होता है, और मानक ने इसे इसलिए चुना क्योंकि यह आमतौर पर डिफ़ॉल्ट के रूप में अधिक नियतात्मक विकल्प पसंद करता है। निम्नलिखित प्रश्न (इसे प्रश्न 3 कहते हैं) एक उदाहरण है जो इस जाल में पड़ता है:
dbo.Transactions से शेष राशि के रूप में, actid, tranid, val, SUM(val) OVER (एक्टिड द्वारा पार्टिशन द्वारा TRANID ) का चयन करें;अक्सर लोग यह मानकर ऐसा लिखते हैं कि उन्हें डिफ़ॉल्ट रूप से ROWS UNBOUNDED PRECEDING मिल रही है, यह महसूस नहीं करते कि वे वास्तव में RANGE UNBOUNDED PRECEDING प्राप्त कर रहे हैं। बात यह है कि चूंकि फ़ंक्शन कुल ऑर्डर का उपयोग करता है, इसलिए आपको ROWS जैसा ही परिणाम मिलता है, इसलिए आप यह नहीं बता सकते कि परिणाम से कोई समस्या है। लेकिन आपको जो प्रदर्शन संख्याएँ मिलेंगी, वे क्वेरी 2 की तरह हैं। मैं देखता हूँ कि लोग हर समय इस जाल में फंसते हैं।
इस समस्या से बचने के लिए सबसे अच्छा अभ्यास उन मामलों में है जहां आप एक फ्रेम के साथ एक विंडो फ़ंक्शन का उपयोग करते हैं, विंडो फ्रेम इकाई और इसकी सीमा के बारे में स्पष्ट रहें, और आम तौर पर ROWS को प्राथमिकता दें। RANGE का उपयोग केवल उन मामलों के लिए आरक्षित करें जहां ऑर्डर करना अद्वितीय नहीं है और आपको संबंधों को शामिल करने की आवश्यकता है।
ROWS और RANGE के बीच एक वैचारिक अंतर होने पर एक मामले को दर्शाने वाली निम्नलिखित क्वेरी पर विचार करें:
सेलेक्ट ऑर्डरडेट, ऑर्डरिड, वैल, एसयूएम (वैल) ओवर (ऑर्डर द्वारा ऑर्डर की गई पंक्तियाँ अनबाउंडेड प्रीसीडिंग) समरो के रूप में, एसयूएम (वैल) ओवर (ऑर्डर द्वारा ऑर्डरडेट रेंज अनबाउंडेड प्रीसिडिंग) बिक्री से समरेंज के रूप में। ऑर्डर वैल्यूज ऑर्डरडेट द्वारा ऑर्डर करें। /पूर्व>यह क्वेरी निम्न आउटपुट उत्पन्न करती है:
ऑर्डरडेट ऑर्डरिड वैल समरो समरेंज ---------- -------- -------- -------- -------- - 2017-07-04 10248 440.00 440.00 440.00 2017-07-05 10249 1863.40 2303.40 2303.40 2017-07-08 10250 1552.60 3856.00 4510.06 2017-07-08 10251 654.06 4510.06 4510.06 2017-07-09 10252 3597.90 8107.96 8107.96 ...उन पंक्तियों के परिणामों में अंतर देखें जहां एक ही ऑर्डर तिथि एक से अधिक बार दिखाई देती है, जैसा कि 8 जुलाई, 2017 के मामले में है। ध्यान दें कि कैसे ROWS विकल्प में संबंध शामिल नहीं हैं और इसलिए यह गैर-निर्धारिती है, और RANGE विकल्प कैसे करता है संबंधों को शामिल करें, और इसलिए हमेशा नियतात्मक होता है।
यह संदिग्ध है, हालांकि यदि व्यवहार में आपके पास ऐसे मामले हैं जहां आप किसी ऐसी चीज से ऑर्डर करते हैं जो अद्वितीय नहीं है, और गणना को नियतात्मक बनाने के लिए आपको वास्तव में संबंधों को शामिल करने की आवश्यकता है। व्यवहार में शायद अधिक सामान्य बात दो चीजों में से एक करना है। एक यह है कि विंडो ऑर्डर में कुछ जोड़कर संबंधों को तोड़ दिया जाए ताकि इसे अद्वितीय बनाया जा सके और इस तरह से एक नियतात्मक गणना हो सके, जैसे:
सेलेक्ट ऑर्डरडेट, ऑर्डरिड, वैल, एसयूएम (वैल) ओवर (ऑर्डर की तारीख से ऑर्डर, ऑर्डरिड रो अनबाउंडेड प्रीसीडिंग) बिक्री से रनिंगसम के रूप में। ऑर्डर वैल्यू ऑर्डर द्वारा ऑर्डर करें;यह क्वेरी निम्न आउटपुट उत्पन्न करती है:
ऑर्डरडेट ऑर्डरिड वैल रनिंगसम ---------- -------- -----------------------2017-07-04 10248 440.00 440.00 2017-07-05 10249 1863.40 2303.40 2017-07-08 10250 1552.60 3856.00 2017-07-08 10251 654.06 4510.06 2017-07-09 10252 3597.90 8107.96 ...एक अन्य विकल्प यह है कि हमारे मामले में, आदेश तिथि के अनुसार प्रारंभिक समूहीकरण लागू किया जाए, जैसे:
सेलेक्ट ऑर्डरडेट, एसयूएम (वैल) दिन के कुल के रूप में, एसयूएम (एसयूएम (वैल)) ओवर (ऑर्डर द्वारा ऑर्डर की गई पंक्तियों को अनबाउंडेड प्रीसीडिंग) सेल्स से रनिंगसम के रूप में। ऑर्डर वैल्यू ग्रुप बाय ऑर्डरडेट ऑर्डर द्वारा ऑर्डरडेट;यह क्वेरी निम्नलिखित आउटपुट उत्पन्न करती है जहां प्रत्येक ऑर्डर तिथि केवल एक बार दिखाई देती है:
ऑर्डर की दिन की कुल रनिंगसम ------------------------------2017-07-04 440.00 440.00 2017-07-05 1863.40 2303.40 2017-07-08 2206.66 4510.06 2017-07-09 3597.90 8107.96 ...किसी भी मामले में, यहां सर्वोत्तम अभ्यास याद रखना सुनिश्चित करें!
अच्छी खबर यह है कि यदि आप SQL सर्वर 2016 या उसके बाद के संस्करण पर चल रहे हैं और डेटा पर एक कॉलमस्टोर इंडेक्स मौजूद है (भले ही यह एक नकली फ़िल्टर्ड कॉलमस्टोर इंडेक्स हो), या यदि आप SQL सर्वर 2019 या उसके बाद के संस्करण पर चल रहे हैं, या Azure SQL डेटाबेस पर, कॉलमस्टोर इंडेक्स की उपस्थिति के बावजूद, सभी तीन उपरोक्त प्रश्न बैच-मोड विंडो एग्रीगेट ऑपरेटर के साथ अनुकूलित हो जाते हैं। इस ऑपरेटर के साथ, कई पंक्ति-मोड प्रसंस्करण अक्षमताएं समाप्त हो जाती हैं। यह ऑपरेटर स्पूल का बिल्कुल भी उपयोग नहीं करता है, इसलिए इन-मेमोरी बनाम ऑन-डिस्क स्पूल की कोई समस्या नहीं है। यह अधिक परिष्कृत प्रसंस्करण का उपयोग करता है जहां यह ROWS और RANGE दोनों के लिए मेमोरी में पंक्तियों की विंडो पर कई समानांतर पास लागू कर सकता है।
बैच-मोड ऑप्टिमाइज़ेशन का उपयोग करके प्रदर्शित करने के लिए, सुनिश्चित करें कि आपका डेटाबेस संगतता स्तर 150 या उच्चतर पर सेट है:
ALTER DATABASE TSQLV5 SET COMPATIBILITY_LEVEL =150;क्वेरी 1 फिर से चलाएँ:
dbo.Transactions से शेष राशि के रूप में, actid, tranid, val, SUM(val) over (एक्टिड ROWS द्वारा PARTITION BY TRANSID ROWS UNBOUNDED PRECEDING चुनें) चुनेंइस क्वेरी की योजना चित्र 3 में दिखाई गई है।
चित्र 3:क्वेरी 1 के लिए योजना, बैच-मोड प्रोसेसिंग
इस क्वेरी के लिए मुझे जो प्रदर्शन आंकड़े मिले हैं, वे यहां दिए गए हैं:
CPU समय:937 ms, बीता हुआ समय:983 ms.
तालिका 'लेन-देन' तार्किक पढ़ता है:6208।रन टाइम घटकर 1 सेकंड रह गया!
क्वेरी 2 को स्पष्ट RANGE विकल्प के साथ फिर से चलाएँ:
dbo.Transactions से बैलेंस के रूप में एक्टिड, ट्रैनिड, वैल, एसयूएम (वैल) ओवर चुनें (पार्टिशन बाय एक्टिड ऑर्डर बाय ट्रैनिड रेंज अनबाउंडेड प्रीसीडिंग)इस क्वेरी की योजना चित्र 4 में दिखाई गई है।
चित्र 2:क्वेरी 2 के लिए योजना, बैच-मोड प्रोसेसिंग
इस क्वेरी के लिए मुझे जो प्रदर्शन आंकड़े मिले हैं, वे यहां दिए गए हैं:
CPU समय:969 ms, बीता हुआ समय:1048 ms.
तालिका 'लेन-देन' तार्किक पढ़ता है:6208।प्रदर्शन वही है जो क्वेरी 1 के लिए है।
अंतर्निहित RANGE विकल्प के साथ फिर से क्वेरी 3 चलाएँ:
dbo.Transactions से शेष राशि के रूप में, actid, tranid, val, SUM(val) OVER (एक्टिड द्वारा पार्टिशन द्वारा TRANID ) का चयन करें;योजना और प्रदर्शन संख्या निश्चित रूप से क्वेरी 2 के समान ही हैं।
जब आपका काम हो जाए, तो प्रदर्शन आंकड़े बंद करने के लिए निम्न कोड चलाएँ:
सांख्यिकी समय सेट करें, IO बंद करें;साथ ही, SSMS में एक्ज़ीक्यूशन ऑप्शन के बाद डिसकार्ड रिजल्ट्स को बंद करना न भूलें।
FIRST_VALUE और LAST_VALUE के साथ अंतर्निहित फ़्रेम
FIRST_VALUE और LAST_VALUE फ़ंक्शन ऑफ़सेट विंडो फ़ंक्शन हैं जो क्रमशः विंडो फ़्रेम में पहली या अंतिम पंक्ति से एक एक्सप्रेशन लौटाते हैं। उनके बारे में मुश्किल बात यह है कि अक्सर जब लोग पहली बार उनका उपयोग करते हैं, तो उन्हें एहसास नहीं होता कि वे एक फ्रेम का समर्थन करते हैं, बल्कि सोचते हैं कि वे पूरे विभाजन पर लागू होते हैं।
आदेश की जानकारी, साथ ही ग्राहक के पहले और अंतिम आदेशों के मूल्यों को वापस करने के निम्नलिखित प्रयास पर विचार करें:
सेलेक्ट कस्टिड, ऑर्डरडेट, ऑर्डरिड, वैल, FIRST_VALUE (वैल) ओवर (कस्टिड ऑर्डर द्वारा ऑर्डरडेट, ऑर्डरिड द्वारा पार्टिशन) फर्स्टवल के रूप में, LAST_VALUE (वैल) ओवर (कस्टिड द्वारा ऑर्डर ऑर्डर द्वारा ऑर्डर, ऑर्डरिड) बिक्री से अंतिम के रूप में। ऑर्डर वैल्यू कस्टिड द्वारा ऑर्डर, ऑर्डरडेट, ऑर्डरिड;यदि आप गलत तरीके से मानते हैं कि ये फ़ंक्शन पूरे विंडो विभाजन पर काम करते हैं, जो कि पहली बार इन कार्यों का उपयोग करने वाले कई लोगों का विश्वास है, तो आप स्वाभाविक रूप से FIRST_VALUE से ग्राहक के पहले ऑर्डर का ऑर्डर मूल्य और LAST_VALUE को वापस करने की अपेक्षा करते हैं। ग्राहक के अंतिम आदेश का आदेश मूल्य। व्यवहार में, हालांकि, ये कार्य एक फ्रेम का समर्थन करते हैं। एक अनुस्मारक के रूप में, एक फ्रेम का समर्थन करने वाले कार्यों के साथ, जब आप विंडो ऑर्डर क्लॉज निर्दिष्ट करते हैं, लेकिन विंडो फ्रेम यूनिट और इसकी संबद्ध सीमा नहीं, तो आपको डिफ़ॉल्ट रूप से RANGE UNBOUNDED PRECEDING मिलता है। FIRST_VALUE फ़ंक्शन के साथ, आपको अपेक्षित परिणाम मिलेगा, लेकिन यदि आपकी क्वेरी पंक्ति-मोड ऑपरेटरों के साथ अनुकूलित हो जाती है, तो आप ऑन-डिस्क स्पूल का उपयोग करने के दंड का भुगतान करेंगे। LAST_VALUE फ़ंक्शन के साथ यह और भी खराब है। इतना ही नहीं आप ऑन-डिस्क स्पूल के दंड का भुगतान करेंगे, बल्कि विभाजन में अंतिम पंक्ति से मूल्य प्राप्त करने के बजाय, आपको वर्तमान पंक्ति से मूल्य प्राप्त होगा!
यहाँ उपरोक्त क्वेरी का आउटपुट दिया गया है:
कस्टिड ऑर्डरडेट ऑर्डरिड वैल फर्स्टवल लास्टवल ------------------------------------------- ---- ---------- 1 2018-08-25 10643 814.50 814.50 814.50 1 2018-10-03 10692 878.00 814.50 878.00 1 2018-10-13 10702 330.00 814.50 330.00 1 2019-01-15 10835 845.80 814.50 845.80 1 2019-03-16 10952 471.20 814.50 471.20 1 2019-04-09 11011 933.50 814.50 933.50 2 2017-09-18 10308 88.80 88.80 88.80 2 2018-08-08 10625 479.75 88.80 479.75 2 2018-11-28.75 2 10759 320.00 88.80 320.00 2 2019-03-04 10926 514.40 88.80 514.40 3 2017-11-27 10365 403.20 403.20 403.20 3 2018-04-15 10507 749.06 403.20 749.06 3 2018-05-13 10535 1940.85 403.20 1940.85 3 2018-06-19 10573 2082.00 403.20 2082.00 3 2018-09-22 10677 813.37 403.20 813.37 3 2018-09-25 10682 375.50 403.20 375.50 3 2019-01-28 10856 660.00 403.20 660.00 ...अक्सर जब लोग इस तरह के आउटपुट को पहली बार देखते हैं, तो उन्हें लगता है कि SQL सर्वर में बग है। लेकिन निश्चित रूप से, यह नहीं है; यह केवल SQL मानक का डिफ़ॉल्ट है। क्वेरी में एक बग है। यह महसूस करते हुए कि इसमें एक फ्रेम शामिल है, आप फ्रेम विनिर्देश के बारे में स्पष्ट होना चाहते हैं, और उस न्यूनतम फ्रेम का उपयोग करना चाहते हैं जो उस पंक्ति को कैप्चर करता है जो आप कर रहे हैं। साथ ही, सुनिश्चित करें कि आप ROWS इकाई का उपयोग करते हैं। इसलिए, विभाजन में पहली पंक्ति प्राप्त करने के लिए, पहले और वर्तमान पंक्ति के बीच फ्रेम पंक्तियों के साथ FIRST_VALUE फ़ंक्शन का उपयोग करें। विभाजन में अंतिम पंक्ति प्राप्त करने के लिए, LAST_VALUE फ़ंक्शन का उपयोग फ्रेम पंक्तियों के बीच वर्तमान पंक्ति और अनबाउंड FOLLOWING के साथ करें।
यहां हमारी संशोधित क्वेरी है जिसमें बग को ठीक किया गया है:
चयन कस्टिड, ऑर्डरडेट, ऑर्डरिड, वैल, FIRST_VALUE (वैल) ओवर (कस्टिड ऑर्डर द्वारा ऑर्डरडेट द्वारा पार्टिशन, अनबाउंडेड प्रीसीडिंग और करंट रो के बीच ऑर्डरिड) पहलेवल के रूप में, LAST_VALUE (वैल) ऑर्डर ओवर (पार्टिट द्वारा ऑर्डर) वर्तमान पंक्ति और अनबाउंड निम्नलिखित के बीच ऑर्डरिड पंक्तियाँ) बिक्री से अंतिम के रूप में। ऑर्डर वैल्यू कस्टिड द्वारा ऑर्डर, ऑर्डरडेट, ऑर्डरिड;इस बार आपको मिलेगा सही परिणाम:
कस्टिड ऑर्डरडेट ऑर्डरिड वैल फर्स्टवल लास्टवल ------------------------------------------- ---- ---------- 1 2018-08-25 10643 814.50 814.50 933.50 1 2018-10-03 10692 878.00 814.50 933.50 1 2018-10-13 10702 330.00 814.50 933.50 1 2019-01-15 10835 845.80 814.50 933.50 1 2019-03-16 10952 471.20 814.50 933.50 1 2019-04-09 11011 933.50 814.50 933.50 2 2017-09-18 10308 88.80 88.80 514.40 2 2018-08-08 10625 479.75 88.80 514.40 2 2018-11-28 10759 320.00 88.80 514.40 2 2019-03-04 10926 514.40 88.80 514.40 3 2017-11-27 10365 403.20 403.20 660.00 3 2018-04-15 10507 749.06 403.20 660.00 3 2018-05-13 10535 1940.85 403.20 660.00 3 2018-06-19 10573 2082.00 403.20 660.00 3 2018-09-22 10677 813.37 403.20 660.00 3 2018-09-25 10682 375.50 403.20 660.00 3 2019-01-28 10856 660.00 403.20 660.00 ...कोई आश्चर्य करता है कि इन कार्यों के साथ एक फ्रेम का समर्थन करने के लिए मानक के लिए प्रेरणा क्या थी। यदि आप इसके बारे में सोचते हैं, तो आप विभाजन में पहली या आखिरी पंक्तियों से कुछ प्राप्त करने के लिए अधिकतर उनका उपयोग करेंगे। यदि आपको 2 PRECEDING से शुरू होने वाले फ्रेम के साथ FIRST_VALUE का उपयोग करने के बजाय, वर्तमान से पहले दो पंक्तियों के मूल्य की आवश्यकता है, तो क्या 2 के स्पष्ट ऑफसेट के साथ LAG का उपयोग करना अधिक आसान नहीं है, जैसे:
सेलेक्ट कस्टिड, ऑर्डरडेट, ऑर्डरिड, वैल, एलएजी (वैल, 2) ओवर (ऑर्डरडेट, ऑर्डरिड द्वारा कस्टिड ऑर्डर द्वारा पार्टिशन) सेल्स से प्रीट्वोवल के रूप में। ऑर्डर वैल्यू कस्टिड, ऑर्डरडेट, ऑर्डरिड द्वारा ऑर्डर करें;यह क्वेरी निम्न आउटपुट उत्पन्न करती है:
कस्टिड ऑर्डरडेट ऑर्डरिड वैल प्रीट्वोवल ------------------------------------------------ ---- 1 2018-08-25 10643 814.50 NULL 1 2018-10-03 10692 878.00 NULL 1 2018-10-13 10702 330.00 814.50 1 2019-01-15 10835 845.80 878.00 1 2019-03-16 10952 471.20 330.00 1 2019-04-09 11011 933.50 845.80 2 2017-09-18 10308 88.80 NULL 2 2018-08-08 10625 479.75 NULL 2 2018-11-28 10759 320.00 88.80 2 2019-03-04 10926 514.40 479.75 3 2017-11-27 10365 403.20 NULL 3 2018-04-15 10507 749.06 NULL 3 2018-05-13 10535 1940.85 403.20 3 2018-06-19 10573 2082.00 749.06 3 2018-09-22 10677 813.37 1940.85 3 2018-09-25 10682 375.50 2082.00 3 2019 -01-28 10856 660.00 813.37 ...जाहिरा तौर पर, LAG फ़ंक्शन के उपरोक्त उपयोग और 2 PRECEDING से शुरू होने वाले फ्रेम के साथ FIRST_VALUE के बीच एक अर्थपूर्ण अंतर है। पूर्व के साथ, यदि वांछित ऑफ़सेट में कोई पंक्ति मौजूद नहीं है, तो आपको डिफ़ॉल्ट रूप से एक NULL मिलता है। उत्तरार्द्ध के साथ, आप अभी भी पहली पंक्ति से मान प्राप्त करते हैं, यानी, विभाजन में पहली पंक्ति से मान। निम्नलिखित प्रश्न पर विचार करें:
सेलेक्ट कस्टिड, ऑर्डरडेट, ऑर्डरिड, वैल, FIRST_VALUE (वैल) ओवर (ऑर्डरडेट द्वारा कस्टिड ऑर्डर द्वारा पार्टिशन, 2 पूर्ववर्ती और वर्तमान पंक्ति के बीच ऑर्डरिड) बिक्री से प्रचलित के रूप में। ऑर्डर वैल्यू ऑर्डर द्वारा ऑर्डर दिनांक, ऑर्डर; पूर्व>यह क्वेरी निम्न आउटपुट उत्पन्न करती है:
कस्टिड ऑर्डरडेट ऑर्डरिड वैल प्रीट्वोवल ------------------------------------------------ ---- 1 2018-08-25 10643 814.50 814.50 1 2018-10-03 10692 878.00 814.50 1 2018-10-13 10702 330.00 814.50 1 2019-01-15 10835 845.80 878.00 1 2019-03-16 10952 471.20 330.00 1 2019-04-09 11011 933.50 845.80 2 2017-09-18 10308 88.80 88.80 2 2018-08-08 10625 479.75 88.80 2 2018-11-28 10759 320.00 88.80 2 2019-03-04 10926 514.40 479.75 3 2017-11-27 10365 403.20 403.20 3 2018-04-15 10507 749.06 403.20 3 2018-05-13 10535 1940.85 403.20 3 2018-06-19 10573 2082.00 749.06 3 2018-09-22 10677 813.37 1940.85 3 2018-09-25 10682 375.50 2082.00 3 2019 -01-28 10856 660.00 813.37 ...ध्यान दें कि इस बार आउटपुट में कोई NULL नहीं है। तो FIRST_VALUE और LAST_VALUE के साथ फ्रेम का समर्थन करने में कुछ मूल्य है। बस यह सुनिश्चित करें कि आपको इन कार्यों के साथ फ़्रेम विनिर्देश के बारे में हमेशा स्पष्ट रहने के लिए सर्वोत्तम अभ्यास याद है, और उस न्यूनतम फ़्रेम के साथ ROWS विकल्प का उपयोग करना जिसमें वह पंक्ति है जिसके बाद आप हैं।
निष्कर्ष
यह आलेख विंडो फ़ंक्शंस से संबंधित बग, नुकसान और सर्वोत्तम प्रथाओं पर केंद्रित है। याद रखें कि दोनों विंडो एग्रीगेट फ़ंक्शन और FIRST_VALUE और LAST_VALUE विंडो ऑफ़सेट फ़ंक्शन एक फ़्रेम का समर्थन करते हैं, और यदि आप विंडो ऑर्डर क्लॉज़ निर्दिष्ट करते हैं, लेकिन आप विंडो फ़्रेम यूनिट और इसकी संबद्ध सीमा को निर्दिष्ट नहीं करते हैं, तो आपको RANGE UNBOUNDED PRECEDING मिल रहा है चूक। जब क्वेरी पंक्ति-मोड ऑपरेटरों के साथ अनुकूलित हो जाती है तो यह एक प्रदर्शन जुर्माना लगाता है। LAST_VALUE फ़ंक्शन के साथ यह विभाजन में अंतिम पंक्ति के बजाय वर्तमान पंक्ति से मान प्राप्त करता है। फ़्रेम के बारे में स्पष्ट होना याद रखें और आम तौर पर RANGE के बजाय ROWS विकल्प को प्राथमिकता दें। बैच-मोड विंडो एग्रीगेट ऑपरेटर के साथ प्रदर्शन में सुधार देखना बहुत अच्छा है। जब यह लागू होता है, तो कम से कम प्रदर्शन संबंधी गड़बड़ी समाप्त हो जाती है।