इस साइट पर पहली ब्लॉग पोस्ट, जुलाई 2012 में, कुल योग चलाने के सर्वोत्तम तरीकों के बारे में बात की गई थी। तब से, मुझसे कई मौकों पर पूछा गया है कि अगर चल रहे योग अधिक जटिल थे - विशेष रूप से, यदि मुझे कई संस्थाओं के लिए चल रहे योगों की गणना करने की आवश्यकता है, तो मैं समस्या से कैसे संपर्क करूंगा - जैसे, प्रत्येक ग्राहक के आदेश।
मूल उदाहरण में एक शहर के तेजी से टिकट जारी करने के एक काल्पनिक मामले का इस्तेमाल किया गया था; रनिंग टोटल बस एकत्रित करना था और दिन के हिसाब से तेज़ टिकटों की संख्या की गिनती रखना था (इस बात की परवाह किए बिना कि टिकट किसके लिए जारी किया गया था या यह कितना था)। एक अधिक जटिल (लेकिन व्यावहारिक) उदाहरण प्रति दिन ड्राइविंग लाइसेंस द्वारा समूहीकृत, तेज गति वाले टिकटों के चल रहे कुल मूल्य को एकत्रित करना हो सकता है। आइए निम्न तालिका की कल्पना करें:
टेबल डीबीओ बनाएं। स्पीडिंग टिकट (घटना आईडी पहचान (1,1) प्राथमिक कुंजी, लाइसेंस संख्या शून्य नहीं है, घटना तिथि शून्य नहीं है, टिकट राशि दशमलव (7,2) शून्य नहीं है); डीबीओ पर अद्वितीय इंडेक्स बनाएं। स्पीडिंग टिकट (लाइसेंस संख्या, घटना तिथि) शामिल करें (टिकट राशि);
आप पूछ सकते हैं, DECIMAL(7,2)
, वास्तव में? कितनी तेजी से जा रहे हैं ये लोग? उदाहरण के लिए, कनाडा में, $10,000 का तेज़ जुर्माना प्राप्त करना इतना कठिन नहीं है।
अब, तालिका को कुछ नमूना डेटा के साथ पॉप्युलेट करते हैं। मैं यहां सभी बारीकियों में नहीं जाऊंगा, लेकिन इससे एक महीने की अवधि में कई ड्राइवरों और कई टिकट राशियों का प्रतिनिधित्व करने वाली लगभग 6,000 पंक्तियों का उत्पादन होना चाहिए:
;टिकटअमाउंट्स (आईडी, वैल्यू) के साथ (- 10 मनमानी टिकट राशियाँ चुनें i,p FROM ( VALUES(1,32.75),(2,75), (3,109),(4,175),(5,295), (6,68.50),(7,125),(8,145),(9,199),(10,250) ) AS v(i,p)),LicenseNumbers(LicenseNumber,[newid]) AS (- 1000 रैंडम लाइसेंस नंबर सेलेक्ट टॉप ( 1000) 700000 + संख्या, n =NEWID () [मास्टर] से। DATEADD(दिन, संख्या, '20140101') [मास्टर] से। जिन दिनों के लिए उन्हें टिकट मिला है, उनके लिए लाइसेंस चुनें DISTINCT l.LicenseNumber, d.[day], s =RTRIM(l.LicenseNumber) लायसेंस नंबरों से जैसे l क्रॉस जॉइन जनवरीतिथियां d जहां CHECKSUM(NEWID()) % 100 =l.LicenseNumber % 100 और (RTRIM(l.LicenseNumber) LIKE '%' + RIGHT(Convert(CHAR(8)), d.[day], 112),1) + '%') या (RTRIM(l.LicenseNumber+1) LIKE' %' + अधिकार ( CONVERT(CHAR(8), d.[day], 112),1) + '%')) INSERT dbo.SpeedingTickets(LicenseNumber, IncidentDate,TicketAmount) t.LicenseNumber, t.[day], ta.Value से चुनें टिकट के रूप में टी इनर जॉइन टिकट की राशि टीए पर टीए.आईडी =कन्वर्ट (इंट, राइट (टीएस, 1)) - कन्वर्ट (इंट, लेफ्ट (राइट (टीएस, 2), 1)) ऑर्डर द्वारा टी। [दिन], टी .लाइसेंसनंबर;
यह थोड़ा बहुत शामिल लग सकता है, लेकिन इन ब्लॉग पोस्टों की रचना करते समय मेरे पास अक्सर सबसे बड़ी चुनौतियों में से एक यथार्थवादी "यादृच्छिक"/मनमाना डेटा की उपयुक्त मात्रा का निर्माण होता है। यदि आपके पास मनमानी डेटा आबादी के लिए एक बेहतर तरीका है, तो हर तरह से, मेरे बड़बड़ाहट का उदाहरण के रूप में उपयोग न करें - वे इस पोस्ट के बिंदु के लिए परिधीय हैं।
दृष्टिकोण
टी-एसक्यूएल में इस समस्या को हल करने के कई तरीके हैं। यहां उनकी संबद्ध योजनाओं के साथ सात दृष्टिकोण दिए गए हैं। मैंने कर्सर जैसी तकनीकों को छोड़ दिया है (क्योंकि वे निर्विवाद रूप से धीमे होंगे) और दिनांक-आधारित पुनरावर्ती सीटीई (क्योंकि वे सन्निहित दिनों पर निर्भर करते हैं)।
सबक्वायरी #1
सिलेक्ट लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, रनिंग टोटल =टिकटअमाउंट + कोलेस (डीबीओ से SUM(TicketAmount) का चयन करें। स्पीडिंग टिकट जहां से s.LicenseNumber =o.LicenseNumber AND s.IncidentDate
सबक्वायरी #1 के लिए योजनासबक्वायरी #2
सेलेक्ट लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, रनिंग टोटल =(सेलेक्ट एसयूएम (टिकटअमाउंट) डीबीओ से। स्पीडिंग टिकट जहां लाइसेंस नंबर =टी। लाइसेंस नंबर और इंसीडेंटडेट <=टी। इंसीडेंटडेट) डीबीओ से;
सबक्वायरी #2 के लिए योजनासेल्फ़-जॉइन
t1.LicenseNumber चुनें t2.IncidentDateGROUP द्वारा t1.LicenseNumber, t1.IncidentDate, t1.TicketAmountORDER BY t1.LicenseNumber, t1.IncidentDate;
स्वयं-जुड़ने की योजनाबाहरी आवेदन
t1.LicenseNumber, t1.IncidentDate, t1.TicketAmount, RunTotal =SUM (t2.TicketAmount) को dbo से चुनें। t1OUTER APPLY के रूप में स्पीडिंग टिकट (dbo से टिकट राशि चुनें। घटना दिनांक) t2GROUP द्वारा t1.LicenseNumber, t1.IncidentDate, t1.TicketAmountORDER BY t1.LicenseNumber, t1.IncidentDate;
बाहरी आवेदन की योजनाRANGE (केवल 2012+) का उपयोग करके SUM OVER()
सेलेक्ट लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, रनिंग टोटल =एसयूएम (टिकटअमाउंट) ओवर (लाइसेंस नंबर ऑर्डर बाय इंसीडेंटडेट रेंज अनबाउंडेड प्रीसिडिंग) डीबीओ से। स्पीडिंग टिकट ऑर्डर बाय लाइसेंस नंबर, इंसीडेंटडेट;
RANGE का उपयोग करके SUM OVER() की योजना बनाएंROWS (केवल 2012+) का उपयोग करके SUM OVER()
सेलेक्ट लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, रनिंग टोटल =एसयूएम (टिकटअमाउंट) ओवर (लाइसेंस नंबर ऑर्डर बाय इंसीडेंटडेट लाइन्स अनबाउंडेड प्रीसिडिंग) डीबीओ से। स्पीडिंग टिकट ऑर्डर बाय लाइसेंस नंबर, इंसीडेंटडेट;
ROWS का उपयोग करके SUM OVER() की योजना बनाएंसेट-आधारित पुनरावृत्ति
SQL सर्वर MVP डीप डाइव्स वॉल्यूम #1 में अध्याय #4 के लिए ह्यूगो कॉर्नेलिस (@Hugo_Kornelis) को श्रेय के साथ, यह दृष्टिकोण एक सेट-आधारित दृष्टिकोण और एक कर्सर दृष्टिकोण को जोड़ती है।
घोषणा @x तालिका (लाइसेंस संख्या शून्य नहीं है, घटना तिथि शून्य नहीं है, टिकट राशि दशमलव (7,2) शून्य नहीं है, कुल दशमलव (7,2) शून्य नहीं है, शून्य नहीं है, प्राथमिक कुंजी (लाइसेंस संख्या, घटना तिथि) ); INSERT @x (लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, रनिंग टोटल, आरएन) सेलेक्ट लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, टिकटअमाउंट, ROW_NUMBER() ओवर (पार्टिशन बाय लाइसेंस नंबर ऑर्डर बाय इंसीडेंटडेट) डीबीओ से स्पीडिंग टिकट; DECLARE @rn INT =1, @rc INT =1; जबकि @rc> 0BEGIN SET @rn +=1; अद्यतन [वर्तमान] सेट रनिंग टोटल =[अंतिम]। रनिंग टोटल + [वर्तमान]। @x से टिकट राशि [वर्तमान] इनर जॉइन @ एक्स एएस [अंतिम] चालू [वर्तमान]। लाइसेंस संख्या =[अंतिम]। लाइसेंस संख्या और [अंतिम]। आरएन =@ आरएन - 1 जहां [वर्तमान]। आरएन =@ आरएन; SET @rc =@@ ROWCOUNT; एंड सिलेक्ट लाइसेंस नंबर, इंसीडेंटडेट, टिकटअमाउंट, रनिंग टोटल फ्रॉम @x ऑर्डर बाय लाइसेंस नंबर, इंसीडेंटडेट;इसकी प्रकृति के कारण, यह दृष्टिकोण तालिका चर को अद्यतन करने की प्रक्रिया में कई समान योजनाएं तैयार करता है, जिनमें से सभी स्वयं-जुड़ने और बाहरी लागू योजनाओं के समान हैं, लेकिन एक तलाश का उपयोग करने में सक्षम हैं:
सेट-आधारित पुनरावृत्ति के माध्यम से निर्मित कई अद्यतन योजनाओं में से एकउन्हें>प्रत्येक पुनरावृत्ति में प्रत्येक योजना के बीच एकमात्र अंतर पंक्ति गणना है। प्रत्येक क्रमिक पुनरावृत्ति के माध्यम से, प्रभावित पंक्तियों की संख्या समान रहनी चाहिए या कम हो जानी चाहिए, क्योंकि प्रत्येक पुनरावृत्ति पर प्रभावित पंक्तियों की संख्या उस दिन की संख्या (या, अधिक सटीक रूप से, दिनों की संख्या) पर टिकट वाले ड्राइवरों की संख्या का प्रतिनिधित्व करती है। वह "रैंक")।
प्रदर्शन परिणाम
सेट-आधारित पुनरावृत्ति दृष्टिकोण के अपवाद के साथ, एसक्यूएल सेंट्री प्लान एक्सप्लोरर द्वारा दिखाए गए दृष्टिकोणों को यहां बताया गया है, क्योंकि इसमें कई अलग-अलग बयान शामिल हैं, बाकी की तुलना में अच्छी तरह से प्रतिनिधित्व नहीं करते हैं।
सात में से छह दृष्टिकोणों के लिए एक्सप्लोरर रनटाइम मेट्रिक्स की योजना बनाएंप्लान एक्सप्लोरर में योजनाओं की समीक्षा करने और रनटाइम मेट्रिक्स की तुलना करने के अलावा, मैंने प्रबंधन स्टूडियो में कच्चे रनटाइम को भी मापा। प्रत्येक क्वेरी को 10 बार चलाने के परिणाम यहां दिए गए हैं, यह ध्यान में रखते हुए कि इसमें SSMS में रेंडर टाइम भी शामिल है:
रनटाइम अवधि, मिलीसेकंड में, सभी सात दृष्टिकोणों के लिए (10 पुनरावृत्तियां) )इसलिए, यदि आप SQL सर्वर 2012 या उससे बेहतर पर हैं, तो सबसे अच्छा तरीका
SUM OVER()
है।ROWS UNBOUNDED PRECEDING
. का उपयोग करना . यदि आप SQL Server 2012 पर नहीं हैं, तोOUTER APPLY
की तुलना में पढ़ने की उच्च संख्या के बावजूद, रनटाइम के संदर्भ में दूसरा सबक्वेरी दृष्टिकोण इष्टतम प्रतीत होता है। सवाल। सभी मामलों में, निश्चित रूप से, आपको अपने स्वयं के सिस्टम के विरुद्ध, अपने स्कीमा के अनुकूल इन दृष्टिकोणों का परीक्षण करना चाहिए। आपका डेटा, अनुक्रमणिका, और अन्य कारक आपके परिवेश में एक भिन्न समाधान को सबसे इष्टतम बना सकते हैं।अन्य जटिलताएं
अब, अद्वितीय सूचकांक यह दर्शाता है कि किसी भी लाइसेंस संख्या + घटना दिनांक संयोजन में एक एकल संचयी योग होगा, उस स्थिति में जहां किसी विशिष्ट ड्राइवर को किसी भी दिन कई टिकट मिलते हैं। यह व्यवसाय नियम हमारे तर्क को थोड़ा सरल बनाने में मदद करता है, जिससे नियतात्मक चलने वाले योगों का उत्पादन करने के लिए टाई-ब्रेकर की आवश्यकता से बचा जाता है।
यदि आपके पास ऐसे मामले हैं जहां आपके पास किसी दिए गए लाइसेंस नंबर + इंसीडेंटडेट संयोजन के लिए कई पंक्तियाँ हो सकती हैं, तो आप किसी अन्य कॉलम का उपयोग करके टाई को तोड़ सकते हैं जो संयोजन को अद्वितीय बनाने में मदद करता है (जाहिर है कि स्रोत तालिका में अब उन दो स्तंभों पर एक अद्वितीय बाधा नहीं होगी) . ध्यान दें कि यह उन मामलों में भी संभव है जहां
DATE
कॉलम वास्तव मेंDATETIME
है - बहुत से लोग मानते हैं कि दिनांक/समय मान अद्वितीय हैं, लेकिन यह निश्चित रूप से हमेशा गारंटी नहीं है, चाहे विवरण कुछ भी हो।मेरे मामले में, मैं
IDENTITY
. का उपयोग कर सकता था कॉलम,IncidentID
; यहां बताया गया है कि मैं प्रत्येक समाधान को कैसे समायोजित करूंगा (यह स्वीकार करते हुए कि बेहतर तरीके हो सकते हैं; केवल विचारों को फेंकना):/* --------- सबक्वायरी # 1 --------- */ लाइसेंस नंबर, घटना तिथि, टिकट राशि, रनिंग टोटल =टिकटअमाउंट + कोलेस ((चुनें योग (टिकट राशि) डीबीओ से चुनें। स्पीडिंग टिकट जहां से s.LicenseNumber =o.LicenseNumber AND (s.IncidentDate=t2.IncidentDate -- इस पंक्ति को जोड़ा गया:और t1.IncidentID>=t2.IncidentID,T1.1.ROUP IncidentIDG .TicketAmountorder by t1.LicenseNumber, t1.IncidentDate; /* --------- बाहरी लागू --------- */ t1.LicenseNumber, t1.IncidentDate, t1.TicketAmount, RunTotal =SUM(t2.TicketAmount) from dbo.SpeedingTicket AS चुनें t1OUTER APPLY (dbo.SpeedingTicket से टिकट राशि चुनें जहां लाइसेंस संख्या =t1.LicenseNumber और IncidentDate <=t1.IncidentDate - इस लाइन को जोड़ा गया:और InidentID <=t1.IncidentID) AS t2GROUP TiNatenc.Lt1.Lt1. द्वारा t1.LicenseNumber, t1.IncidentDate; /* --------- SUM() रेंज का उपयोग करने से अधिक ------------- */ लाइसेंस नंबर, घटना तिथि, टिकट राशि, रनिंग टोटल =एसयूएम (टिकट राशि) का चयन करें (घटना तिथि द्वारा लाइसेंस संख्या द्वारा विभाजन, इंसिडेंटिड रेंज अनबाउंडेड प्रीसीडिंग - इस कॉलम को जोड़ा गया ^^^^^^^^^^^ ) डीबीओ से। स्पीडिंग टिकट ऑर्डर लाइसेंस नंबर, इंसीडेंटडेट द्वारा; /* --------- SUM () ROWS का उपयोग करने पर --------- */ लाइसेंस नंबर, घटना तिथि, टिकट राशि, रनिंग टोटल =SUM (टिकट राशि) का चयन करें IncidentID ROWS UNBOUNDED PRECEDING -- इस कॉलम को जोड़ा गया ^^^^^^^^^^^ ) dbo से। स्पीडिंग टिकट लाइसेंस नंबर द्वारा ऑर्डर, घटना तिथि; /* --------- सेट-आधारित पुनरावृत्ति --------- */ DECLARE @x TABLE(-- इस कॉलम को जोड़ा, और इसे PK:IncidentID INT प्राथमिक कुंजी, लाइसेंस संख्या INT बनाया नॉट न्यूल, इंसीडेंट डेट नॉट न्यूल, टिकटअमाउंट डेसीमल (7,2) नॉट न्यूल, रनिंग टोटल डेसिमल (7,2) नॉट न्यूल, आरएन इंट नॉट न्यूल); - INSERT/SELECT में अतिरिक्त कॉलम जोड़ा गया:INSERT @x (IncidentID, लाइसेंस संख्या, हादसा दिनांक, टिकट राशि, रनिंग टोटल, आरएन) चयन घटना आईडी, लाइसेंस संख्या, घटना तिथि, टिकट राशि, टिकट राशि, ROW_NUMBER() ओवर (लाइसेंस संख्या द्वारा विभाजन घटना तिथि द्वारा आदेश , IncidentID) -- और इस टाई-ब्रेकर कॉलम को जोड़ा ----------------------------^^^^^^^ ^^^^ डीबीओ से। स्पीडिंग टिकट; -- बाकी सेट-आधारित पुनरावृति समाधान अपरिवर्तित रहे एक और जटिलता जो आपके सामने आ सकती है वह यह है कि जब आप पूरी तालिका के बाद नहीं होते हैं, बल्कि एक उपसमुच्चय (जैसे, इस मामले में, जनवरी के पहले सप्ताह) में होते हैं। आपको
WHERE
. जोड़कर समायोजन करना होगा खंड, और उन विधेय को ध्यान में रखें जब आपने उपश्रेणियों को भी सहसंबद्ध किया हो।