Database
 sql >> डेटाबेस >  >> RDS >> Database

संपूर्ण क्वेरी योजना का प्रदर्शन ट्यूनिंग

निष्पादन योजनाएं जानकारी का एक समृद्ध स्रोत प्रदान करती हैं जो महत्वपूर्ण प्रश्नों के प्रदर्शन को बेहतर बनाने के तरीकों की पहचान करने में हमारी सहायता कर सकती हैं। लोग अक्सर संभावित डेटा एक्सेस पथ अनुकूलन की पहचान करने के तरीके के रूप में बड़े स्कैन और लुकअप जैसी चीज़ों की तलाश करते हैं। इन मुद्दों को अक्सर एक नई अनुक्रमणिका बनाकर या अधिक सम्मिलित स्तंभों के साथ किसी मौजूदा को विस्तारित करके जल्दी से हल किया जा सकता है।

हम योजना ऑपरेटरों के बीच अपेक्षित पंक्ति गणना के साथ वास्तविक तुलना करने के लिए निष्पादन के बाद की योजनाओं का भी उपयोग कर सकते हैं। जहां ये महत्वपूर्ण रूप से भिन्नता में पाए जाते हैं, हम मौजूदा आंकड़ों को अपडेट करके, नए आंकड़े ऑब्जेक्ट बनाकर, गणना किए गए कॉलम पर आंकड़ों का उपयोग करके, या शायद जटिल क्वेरी को कम-जटिल घटक में तोड़कर ऑप्टिमाइज़र को बेहतर सांख्यिकीय जानकारी प्रदान करने का प्रयास कर सकते हैं। भागों।

इसके अलावा, हम योजना में महंगे संचालन को भी देख सकते हैं, विशेष रूप से मेमोरी-खपत वाले जैसे सॉर्टिंग और हैशिंग। अनुक्रमण परिवर्तनों के माध्यम से कभी-कभी छँटाई से बचा जा सकता है। दूसरी बार, हमें सिंटैक्स का उपयोग करके क्वेरी को फिर से सक्रिय करना पड़ सकता है जो एक ऐसी योजना का समर्थन करता है जो एक विशेष वांछित क्रम को संरक्षित करता है।

कभी-कभी, इन सभी प्रदर्शन ट्यूनिंग तकनीकों को लागू करने के बाद भी प्रदर्शन अभी भी पर्याप्त नहीं होगा। एक संभावित अगला कदम यह है कि पूरी योजना के बारे में थोड़ा और सोचें . इसका अर्थ है एक कदम पीछे हटना, क्वेरी अनुकूलक द्वारा चुनी गई समग्र रणनीति को समझने की कोशिश करना, यह देखने के लिए कि क्या हम एक एल्गोरिथम सुधार की पहचान कर सकते हैं।

यह लेख इस बाद के प्रकार के विश्लेषण की पड़ताल करता है, जिसमें मामूली बड़े डेटा सेट में अद्वितीय कॉलम मान खोजने की एक सरल उदाहरण समस्या का उपयोग किया जाता है। जैसा कि वास्तविक दुनिया की समान समस्याओं में अक्सर होता है, तालिका में पंक्तियों की संख्या की तुलना में रुचि के कॉलम में अपेक्षाकृत कुछ अद्वितीय मान होंगे। इस विश्लेषण के दो भाग हैं:नमूना डेटा बनाना, और विशिष्ट-मान क्वेरी स्वयं लिखना।

नमूना डेटा बनाना

सरलतम संभव उदाहरण प्रदान करने के लिए, हमारी परीक्षण तालिका में क्लस्टर इंडेक्स के साथ केवल एक कॉलम है (इस कॉलम में डुप्लिकेट मान होंगे ताकि इंडेक्स को अद्वितीय घोषित नहीं किया जा सके):

क्रिएट टेबल डीबीओ.टेस्ट (डेटा इंटीजर नॉट न्यूल),);GOCREATE CLUSTERED INDEX cxON dbo.Test (डेटा);

कुछ संख्याओं को हवा से बाहर निकालने के लिए, हम दस मिलियन पंक्तियों को लोड करना चुनेंगे कुल मिलाकर, हजारों विशिष्ट मानों . पर समान वितरण के साथ . इस तरह से डेटा उत्पन्न करने की एक सामान्य तकनीक है कुछ सिस्टम तालिकाओं को पार करना और ROW_NUMBER लागू करना समारोह। हम उत्पन्न संख्याओं को वांछित विशिष्ट मानों तक सीमित करने के लिए मोडुलो ऑपरेटर का भी उपयोग करेंगे:

INSERT dbo.Test with (TABLOCK) (डेटा) सेलेक्ट टॉप (10000000) (ROW_NUMBER() ओवर (ऑर्डर बाय (सिलेक्ट 0))% 1000) + 1FROM Master.sys.columns AS C1 विद (READUNCOMMITTED)CROSS JOIN (READUNCOMMITTED)क्रॉस के साथ C2 के रूप में master.sys.columns (READUNCOMMITTED) के साथ Master.sys.columns C3 में शामिल हों;

उस क्वेरी के लिए अनुमानित निष्पादन योजना इस प्रकार है (यदि आवश्यक हो तो इसे बड़ा करने के लिए चित्र पर क्लिक करें):

इसमें लगभग 30 सेकंड लगते हैं मेरे लैपटॉप पर नमूना डेटा बनाने के लिए। यह किसी भी तरह से बहुत अधिक समय नहीं है, लेकिन यह विचार करना अभी भी दिलचस्प है कि हम इस प्रक्रिया को और अधिक कुशल बनाने के लिए क्या कर सकते हैं…

योजना विश्लेषण

हम यह समझकर शुरू करेंगे कि योजना में प्रत्येक ऑपरेशन किस लिए है।

सेगमेंट ऑपरेटर के दाईं ओर निष्पादन योजना का अनुभाग क्रॉस जॉइनिंग सिस्टम टेबल द्वारा पंक्तियों के निर्माण से संबंधित है:

यदि विंडो फ़ंक्शन में PARTITION BY . है तो सेगमेंट ऑपरेटर वहां मौजूद है खंड। यहां ऐसा नहीं है, लेकिन यह वैसे भी क्वेरी प्लान में शामिल है। सीक्वेंस प्रोजेक्ट ऑपरेटर पंक्ति संख्या उत्पन्न करता है, और शीर्ष योजना आउटपुट को दस मिलियन पंक्तियों तक सीमित करता है:

कंप्यूट स्केलर उस अभिव्यक्ति को परिभाषित करता है जो मॉड्यूलो फ़ंक्शन को लागू करता है और परिणाम में एक जोड़ता है:

प्लान एक्सप्लोरर के एक्सप्रेशन टैब का उपयोग करके हम देख सकते हैं कि सीक्वेंस प्रोजेक्ट और कंप्यूट स्केलर एक्सप्रेशन लेबल कैसे संबंधित हैं:

यह हमें इस योजना के प्रवाह के लिए एक अधिक संपूर्ण अनुभव देता है:अनुक्रम परियोजना पंक्तियों की संख्या और अभिव्यक्ति को लेबल करती है Expr1050; कंप्यूट स्केलर मॉड्यूलो और प्लस-वन कंप्यूटेशन के परिणाम को Expr1052 के रूप में लेबल करता है . कंप्यूट स्केलर एक्सप्रेशन में निहित रूपांतरण पर भी ध्यान दें। गंतव्य तालिका स्तंभ पूर्णांक प्रकार का होता है, जबकि ROW_NUMBER फ़ंक्शन एक बड़ा संकेत देता है, इसलिए एक संकीर्ण रूपांतरण आवश्यक है।

योजना में अगला ऑपरेटर एक सॉर्ट है। क्वेरी ऑप्टिमाइज़र के लागत अनुमानों के अनुसार, यह सबसे महंगा ऑपरेशन होने की उम्मीद है (88.1% अनुमानित ):

यह तुरंत स्पष्ट नहीं हो सकता है कि इस योजना में छँटाई क्यों है, क्योंकि क्वेरी में कोई स्पष्ट आदेश देने की आवश्यकता नहीं है। यह सुनिश्चित करने के लिए योजना में सॉर्ट जोड़ा गया है कि क्लस्टर इंडेक्स क्रम में क्लस्टर इंडेक्स इंसर्ट ऑपरेटर तक पंक्तियां पहुंचें। यह क्रमिक लेखन को बढ़ावा देता है, पृष्ठ विभाजन से बचाता है, और न्यूनतम लॉग किए गए INSERT के लिए पूर्व-आवश्यकताओं में से एक है। संचालन।

ये सभी संभावित रूप से अच्छी चीजें हैं, लेकिन सॉर्ट ही काफी महंगा है। वास्तव में, निष्पादन के बाद ("वास्तविक") निष्पादन योजना की जाँच से पता चलता है कि सॉर्ट भी निष्पादन के समय मेमोरी से बाहर हो गया और उसे भौतिक tempdb तक फैलाना पड़ा डिस्क:

पंक्तियों की अनुमानित संख्या के बिल्कुल सही होने के बावजूद सॉर्ट स्पिल होता है, और इस तथ्य के बावजूद कि क्वेरी को उसके द्वारा मांगी गई सभी मेमोरी दी गई थी (जैसा कि रूट के लिए योजना गुणों में देखा गया है INSERT नोड):

सॉर्ट स्पिल को IO_COMPLETION . की उपस्थिति से भी दर्शाया जाता है प्लान एक्सप्लोरर प्रो प्रतीक्षा आँकड़े टैब में प्रतीक्षा करता है:

अंत में इस योजना विश्लेषण अनुभाग के लिए, DML Request Sort पर ध्यान दें क्लस्टर्ड इंडेक्स इंसर्ट ऑपरेटर की संपत्ति सही है:

यह ध्वज इंगित करता है कि ऑप्टिमाइज़र को इंडेक्स कुंजी क्रमबद्ध क्रम में पंक्तियां प्रदान करने के लिए सम्मिलित करें के नीचे उप-पेड़ की आवश्यकता होती है (इसलिए समस्याग्रस्त सॉर्ट ऑपरेटर की आवश्यकता होती है)।

सॉर्ट से बचना

अब जबकि हम जानते हैं क्यों सॉर्ट दिखाई देता है, हम यह देखने के लिए परीक्षण कर सकते हैं कि अगर हम इसे हटा दें तो क्या होगा। ऐसे तरीके हैं जिनसे हम ऑप्टिमाइज़र को "मूर्ख" करने के लिए क्वेरी को फिर से लिख सकते हैं, यह सोचकर कि कम पंक्तियों को डाला जाएगा (इसलिए सॉर्ट करना सार्थक नहीं होगा) लेकिन सीधे सॉर्ट से बचने का एक त्वरित तरीका (केवल प्रयोगात्मक उद्देश्यों के लिए) अनिर्दिष्ट ट्रेस ध्वज का उपयोग करना है 8795. यह DML Request Sort . सेट करता है संपत्ति झूठी है, इसलिए पंक्तियों को क्लस्टर्ड इंडेक्स इंसर्ट पर क्लस्टर कुंजी क्रम में आने की आवश्यकता नहीं है:

TRUNCATE TABLE dbo.Test;GOINSERT dbo.Test with (TABLOCK) (डेटा) सेलेक्ट टॉप (10000000) ROW_NUMBER() ओवर (ऑर्डर बाय (सिलेक्ट 0)) % 1000FROM Master.sys.columns AS C1 के साथ (READUNCOMMITTED) (READUNCOMMITTED) के साथ C2 के रूप में क्रॉस जॉइन करें (READUNCOMMITTED)CROSS JOIN Master.sys.columns C3 के साथ (READUNCOMMITTED) विकल्प (QUERYTRACEON 8795);

निष्पादन के बाद की नई क्वेरी योजना इस प्रकार है (विस्तार करने के लिए चित्र पर क्लिक करें):

सॉर्ट ऑपरेटर चला गया है, लेकिन नई क्वेरी 50 सेकंड से अधिक समय तक चलती है (30 सेकंड . की तुलना में) इससे पहले)। इसके पीछे कुछ कारण हैं। सबसे पहले, हम न्यूनतम-लॉग इन्सर्ट की कोई संभावना खो देते हैं क्योंकि इन्हें सॉर्ट किए गए डेटा (डीएमएल अनुरोध सॉर्ट =सत्य) की आवश्यकता होती है। दूसरा, सम्मिलन के दौरान बड़ी संख्या में "खराब" पृष्ठ विभाजन होंगे। यदि यह उल्टा लगता है, तो याद रखें कि हालांकि ROW_NUMBER फ़ंक्शन संख्या पंक्तियाँ क्रमिक रूप से, मॉड्यूलो ऑपरेटर का प्रभाव क्लस्टर इंडेक्स इंसर्ट में संख्याओं 1…1000 के दोहराव अनुक्रम को प्रस्तुत करना है।

यदि हम असमर्थित ट्रेस फ़्लैग का उपयोग करने के बजाय सॉर्ट से बचने के लिए अपेक्षित पंक्ति गणना को कम करने के लिए T-SQL ट्रिक्स का उपयोग करते हैं तो वही मूलभूत समस्या उत्पन्न होती है।

सॉर्ट II से बचना

पूरी योजना को देखते हुए, यह स्पष्ट प्रतीत होता है कि हम पंक्तियों को इस तरह से उत्पन्न करना चाहते हैं जो एक स्पष्ट प्रकार से बचता है, लेकिन जो अभी भी न्यूनतम लॉगिंग और खराब पृष्ठ विभाजन से बचने के लाभों को पुनः प्राप्त करता है। सीधे शब्दों में कहें:हम एक ऐसी योजना चाहते हैं जो पंक्तियों को क्लस्टर कुंजी क्रम में प्रस्तुत करे, लेकिन बिना छँटाई के।

इस नई अंतर्दृष्टि के साथ, हम अपनी क्वेरी को एक अलग तरीके से व्यक्त कर सकते हैं। निम्नलिखित क्वेरी प्रत्येक संख्या को 1 से 1000 तक उत्पन्न करती है और दोहराव की आवश्यक डिग्री उत्पन्न करने के लिए 10,000 पंक्तियों के साथ क्रॉस जुड़ती है। विचार एक सम्मिलित सेट उत्पन्न करना है जो 10,000 पंक्तियों को '1' और फिर 10,000 क्रमांकित '2' ... और इसी तरह प्रस्तुत करता है।

TRUNCATE TABLE dbo.Test;GOINSERT dbo.Test with (TABLOCK) (डेटा) से N.नंबर चुनें (SV.number से Master.dbo.spt_values ​​के रूप में SV के साथ (READUNCOMMITTED) जहां SV.[type] =N चुनें। 'P' और SV.number>=1 और SV.number <=1000) एनक्रॉस जॉइन के रूप में (सेलेक्ट टॉप (10000) डमी =मास्टर से न्यूल। C2 के साथ (READUNCOMMITTED) क्रॉस में शामिल हों master.sys.columns C3 के साथ (READUNCOMMITTED)) AS C;

दुर्भाग्य से, अनुकूलक अभी भी एक प्रकार के साथ एक योजना तैयार करता है:

यहाँ ऑप्टिमाइज़र के बचाव में कहने के लिए बहुत कुछ नहीं है, यह सिर्फ एक धूर्त योजना है। इसने 10,000 पंक्तियों को उत्पन्न करने के लिए चुना है, फिर 1 से 1000 तक की संख्या वाले लोगों को पार करें। यह संख्याओं के प्राकृतिक क्रम को संरक्षित करने की अनुमति नहीं देता है, इसलिए इस प्रकार से बचा नहीं जा सकता है।

सॉर्ट से बचना - अंत में!

ऑप्टिमाइज़र ने जो रणनीति याद की वह संख्या 1…1000 पहले . लेना है , और क्रॉस प्रत्येक संख्या को 10,000 पंक्तियों के साथ जोड़ते हैं (क्रम में प्रत्येक संख्या की 10,000 प्रतियां बनाते हुए)। अपेक्षित योजना एक नेस्टेड लूप क्रॉस जॉइन का उपयोग करके एक प्रकार से बचना होगा जो आदेश को संरक्षित करता है बाहरी इनपुट पर पंक्तियों की।

हम ऑप्टिमाइज़र को क्वेरी में निर्दिष्ट क्रम में व्युत्पन्न तालिकाओं तक पहुँचने के लिए FORCE ORDER का उपयोग करके मजबूर करके इस परिणाम को प्राप्त कर सकते हैं। क्वेरी संकेत:

TRUNCATE TABLE dbo.Test;GOINSERT dbo.Test with (TABLOCK) (डेटा) से N.नंबर चुनें (SV.number से Master.dbo.spt_values ​​के रूप में SV के साथ (READUNCOMMITTED) जहां SV.[type] =N चुनें। 'P' और SV.number>=1 और SV.number <=1000) एनक्रॉस जॉइन के रूप में (सेलेक्ट टॉप (10000) डमी =मास्टर से न्यूल। C2 के साथ (READUNCOMMITTED) क्रॉस में शामिल हों master.sys.columns C3 के साथ (READUNCOMMITTED)) AS COPTION (फोर्स ऑर्डर);

अंत में, हमें वह योजना मिलती है जिसके बाद हम थे:

यह योजना एक स्पष्ट प्रकार से बचाती है जबकि अभी भी "खराब" पृष्ठ विभाजन से बचती है और न्यूनतम-लॉग इन्सर्ट को क्लस्टर इंडेक्स में सक्षम करती है (यह मानते हुए कि डेटाबेस FULL का उपयोग नहीं कर रहा है। रिकवरी मॉडल)। यह लगभग 9 सेकंड . में सभी दस मिलियन पंक्तियों को लोड करता है मेरे लैपटॉप पर (एक सिंगल 7200 आरपीएम सैटा स्पिनिंग डिस्क के साथ)। यह 30-50 सेकंड . में एक उल्लेखनीय दक्षता लाभ का प्रतिनिधित्व करता है बीता हुआ समय पुनर्लेखन से पहले देखा गया।

विशिष्ट मूल्यों का पता लगाना

अब हमारे पास नमूना डेटा बनाया गया है, हम तालिका में अलग-अलग मानों को खोजने के लिए एक प्रश्न लिखने के लिए अपना ध्यान केंद्रित कर सकते हैं। T-SQL में इस आवश्यकता को व्यक्त करने का एक स्वाभाविक तरीका इस प्रकार है:

dbo से DISTINCT डेटा चुनें। (TABLOCK) विकल्प के साथ परीक्षण करें (MAXDOP 1);

निष्पादन योजना बहुत सरल है, जैसा कि आप उम्मीद करेंगे:

इसमें लगभग 2900 ms . का समय लगता है मेरी मशीन पर चलने के लिए, और आवश्यकता है 43,406 तार्किक पढ़ता है:

MAXDOP (1) को हटाना क्वेरी संकेत एक समानांतर योजना बनाता है:

यह लगभग 1500 ms . में पूरा होता है (लेकिन 8,764 ms CPU समय की खपत के साथ), और 43,804 तार्किक पढ़ता है:

यदि हम GROUP BY . का उपयोग करते हैं तो वही योजनाएँ और प्रदर्शन परिणाम DISTINCT . के बजाय ।

बेहतर एल्गोरिथम

ऊपर दिखाए गए क्वेरी प्लान बेस टेबल से सभी मानों को पढ़ते हैं और उन्हें स्ट्रीम एग्रीगेट के माध्यम से प्रोसेस करते हैं। कार्य के बारे में समग्र रूप से सोचते हुए, सभी 10 मिलियन पंक्तियों को स्कैन करना अक्षम लगता है जब हम जानते हैं कि अपेक्षाकृत कुछ विशिष्ट मान हैं।

एक बेहतर रणनीति यह हो सकती है कि तालिका में एकल निम्नतम मान ज्ञात करें, फिर अगला उच्चतम ज्ञात करें, और इसी तरह जब तक हम मानों से बाहर नहीं निकल जाते। महत्वपूर्ण रूप से, यह दृष्टिकोण प्रत्येक पंक्ति को स्कैन करने के बजाय इंडेक्स में सिंगलटन की तलाश करने के लिए उधार देता है।

हम इस विचार को एक पुनरावर्ती CTE का उपयोग करके एकल क्वेरी में कार्यान्वित कर सकते हैं, जहां एंकर भाग निम्नतम पाता है विशिष्ट मान, फिर पुनरावर्ती भाग अगले विशिष्ट मान को ढूंढता है और इसी तरह। इस प्रश्न को लिखने का पहला प्रयास है:

RecursiveCTEAS के साथ (- एंकर सेलेक्ट डेटा =MIN(T.data) dbo से। T UNION ALL के रूप में टेस्ट करें - dbo से रिकर्सिव सेलेक्ट MIN (T.data) करें। R.data पर R के रूप में रिकर्सिवCTE के रूप में टेस्ट करें।  

दुर्भाग्य से, वह सिंटैक्स संकलित नहीं होता है:

ठीक है, इसलिए कुल कार्यों की अनुमति नहीं है। MIN . का उपयोग करने के बजाय , हम TOP (1) . का उपयोग करके एक ही तर्क लिख सकते हैं एक ORDER BY . के साथ :

RecursiveCTEAS के साथ (- एंकर सेलेक्ट टॉप (1) डीबीओ से टी.डेटा। टी.डेटा यूनियन द्वारा टी ऑर्डर के रूप में टेस्ट करें - रिकर्सिव सेलेक्ट टॉप (1) डीबीओ से टी.डेटा। टेस्ट एएस टी जॉइन रिकर्सिवसीटीई एएस R पर R.data 

अभी भी कोई खुशी नहीं है।

यह पता चला है कि हम उम्मीदवारों की पंक्तियों को आवश्यक क्रम में संख्या देने के लिए पुनरावर्ती भाग को फिर से लिखकर इन प्रतिबंधों को प्राप्त कर सकते हैं, फिर उस पंक्ति के लिए फ़िल्टर करें जिसे 'एक' क्रमांकित किया गया है। यह थोड़ा घुमावदार लग सकता है, लेकिन तर्क बिल्कुल वैसा ही है:

RecursiveCTEAS के साथ (- एंकर सेलेक्ट टॉप (1) डीबीओ से डेटा। टी.डेटा यूनियन द्वारा टेस्ट के रूप में टी। डेटा यूनियन सभी - रिकर्सिव सेलेक्ट आर.डेटा से (- पंक्तियों की संख्या चुनें टी। डेटा, आरएन =ROW_NUMBER) () ओवर (टी.डेटा द्वारा ऑर्डर) डीबीओ से। टेस्ट के रूप में टी जॉइन रिकर्सिवसीटीई के रूप में आर पर आर। डेटा <टी। डेटा) के रूप में आर जहां - केवल वह पंक्ति जो सबसे कम आर.आरएन =1) रिकर्सिव से डेटा का चयन करें। MAXRECURSION 0);

यह क्वेरी करती है निम्नलिखित पोस्ट-निष्पादन योजना संकलित करें और तैयार करें:

निष्पादन योजना (हाइलाइट) के पुनरावर्ती भाग में शीर्ष ऑपरेटर पर ध्यान दें। हम एक T-SQL TOP नहीं लिख सकते हैं एक पुनरावर्ती सामान्य तालिका अभिव्यक्ति के पुनरावर्ती भाग में, लेकिन इसका मतलब यह नहीं है कि अनुकूलक एक का उपयोग नहीं कर सकता है! ऑप्टिमाइज़र पंक्तियों की संख्या के बारे में तर्क के आधार पर शीर्ष का परिचय देता है जिसे एक नंबर '1' को खोजने के लिए जांचना होगा।

इस (गैर-समानांतर) योजना का प्रदर्शन स्ट्रीम एग्रीगेट दृष्टिकोण से काफी बेहतर है। यह लगभग 50 ms . में पूरा होता है , 3007 . के साथ पिछले सर्वश्रेष्ठ 1500ms की तुलना में स्रोत तालिका (और स्पूल वर्कटेबल से पढ़ी गई 6001 पंक्तियाँ) के विरुद्ध तार्किक पठन (डीओपी 8 पर 8764 एमएस सीपीयू समय) और 43,804 तार्किक पढ़ता है:

निष्कर्ष

व्यक्तिगत क्वेरी योजना तत्वों पर विचार करके क्वेरी प्रदर्शन में सफलता प्राप्त करना हमेशा संभव नहीं होता है। कभी-कभी, हमें संपूर्ण निष्पादन योजना के पीछे की रणनीति का विश्लेषण करने की आवश्यकता होती है, फिर बाद में एक अधिक कुशल एल्गोरिदम और कार्यान्वयन खोजने के लिए सोचें।


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. हेकाटन को मूल रूप से संकलित संग्रहीत प्रक्रियाओं को कैसे कॉल न करें

  2. अपाचे स्पार्क द्वारा प्रज्वलित हो जाओ - भाग 2

  3. एसक्यूएल चयन योग

  4. एग्रीगेट फंक्शन AVG के साथ रिकॉर्ड्स को कैसे फ़िल्टर करें?

  5. स्प्लिटिंग स्ट्रिंग्स:ए फॉलो-अप