ISNULL () को WHERE क्लॉज में बदलने के विभिन्न तरीके क्या हैं जो केवल शाब्दिक मूल्यों का उपयोग करते हैं?


55

यह किस बारे में नहीं है:

यह उपयोगकर्ता द्वारा इनपुट स्वीकार करने या चर का उपयोग करने वाले कैच-सभी प्रश्नों के बारे में एक प्रश्न नहीं है ।

यह कड़ाई से उन प्रश्नों के बारे में है जहां एक विधेय की तुलना के लिए एक कैनरी मूल्य के साथ मूल्यों को बदलने के ISNULL()लिए WHEREक्लॉज में उपयोग किया जाता है NULL, और SQL सर्वर में SARGable होने के लिए उन प्रश्नों को फिर से लिखने के लिए अलग-अलग तरीके ।

आपके पास वहां पर सीट क्यों नहीं है?

हमारी उदाहरण क्वेरी SQL सर्वर 2016 पर स्टैक ओवरफ़्लो डेटाबेस की एक स्थानीय प्रति के विरुद्ध है, और एक उपयोगकर्ता NULLया 18 वर्ष की आयु के उपयोगकर्ताओं के लिए दिखता है ।

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

क्वेरी योजना एक बहुत ही विचारहीन गैर-अनुक्रमित सूचकांक की एक स्कैन दिखाती है।

पागल

स्कैन ऑपरेटर दिखाता है (SQL सर्वर के अधिक हाल के संस्करणों में वास्तविक निष्पादन योजना एक्सएमएल के लिए धन्यवाद) जिसे हम हर स्टिंकिन पंक्ति में पढ़ते हैं।

पागल

कुल मिलाकर, हम 9157 पढ़ते हैं और CPU समय के लगभग आधे हिस्से का उपयोग करते हैं:

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 485 ms,  elapsed time = 483 ms.

प्रश्न: इस क्वेरी को और अधिक कुशल बनाने के लिए, और शायद SARGable को फिर से लिखने के क्या तरीके हैं?

अन्य सुझावों की पेशकश करने के लिए स्वतंत्र महसूस करें। मुझे नहीं लगता कि मेरा उत्तर आवश्यक रूप से उत्तर है, और बेहतर होने के लिए विकल्पों के साथ आने के लिए पर्याप्त स्मार्ट लोग हैं।

यदि आप अपने स्वयं के कंप्यूटर पर खेलना चाहते हैं, तो एसओ डेटाबेस डाउनलोड करने के लिए यहां जाएं ।

धन्यवाद!

जवाबों:


57

उत्तर अनुभाग

विभिन्न टी-एसक्यूएल निर्माणों का उपयोग करके इसे फिर से लिखने के विभिन्न तरीके हैं। हम पेशेवरों और विपक्षों को देखेंगे और नीचे एक समग्र तुलना करेंगे।

पहला ऊपर : उपयोग करनाOR

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

उपयोग करने ORसे हमें एक अधिक कुशल सीक प्लान प्राप्त होता है, जो सटीक पंक्तियों को पढ़ता है, जिनकी हमें आवश्यकता होती है, हालांकि यह जोड़ता है कि तकनीकी दुनिया a whole mess of malarkeyक्वेरी प्लान को क्या कहती है।

पागल

यह भी ध्यान दें कि सीक को दो बार यहां निष्पादित किया गया है, जो वास्तव में ग्राफिकल ऑपरेटर से अधिक स्पष्ट होना चाहिए:

पागल

Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 469 ms,  elapsed time = 473 ms.

दूसरा अप : UNION ALL हमारी क्वेरी के साथ व्युत्पन्न तालिकाओं का उपयोग भी इस तरह से फिर से लिखा जा सकता है

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

यह उसी प्रकार की योजना तैयार करता है, जिसमें कम मल्करी होती है, और ईमानदारी की अधिक स्पष्ट डिग्री के बारे में कि कितनी बार सूचकांक खोजा गया (मांगी?)।

पागल

यह ORक्वेरी के रूप में रीड्स (8233) की समान मात्रा करता है , लेकिन लगभग 100ms सीपीयू समय को बंद कर देता है।

CPU time = 313 ms,  elapsed time = 315 ms.

हालांकि, आपको यहां वास्तव में सावधान रहना होगा, क्योंकि यदि यह योजना समानांतर चलने का प्रयास करती है, तो दो अलग-अलग COUNTसंचालन क्रमबद्ध होंगे, क्योंकि वे प्रत्येक एक वैश्विक स्केलर एग्रीगेट माने जाते हैं। यदि हम ट्रेस फ्लैग 8649 का उपयोग करते हुए समानांतर योजना बनाते हैं, तो समस्या स्पष्ट हो जाती है।

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);

पागल

हमारी क्वेरी को थोड़ा बदलकर इससे बचा जा सकता है।

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

अब जब तक हम कॉन्टेक्ट ऑपरेटर से नहीं टकराते हैं, तब तक सीक पर प्रदर्शन करने वाले दोनों नोड्स पूरी तरह से समानांतर हो जाते हैं।

पागल

इसके लायक क्या है, पूरी तरह से समानांतर संस्करण में कुछ अच्छा लाभ है। लगभग 100 और रीड की लागत पर, और लगभग 90ms अतिरिक्त CPU समय के साथ, बीता हुआ समय 93ms तक सिकुड़ जाता है।

Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 500 ms,  elapsed time = 93 ms.

CROSS APPLY के बारे में क्या? जादू के बिना कोई जवाब पूरा नहीं होता है CROSS APPLY!

दुर्भाग्य से, हम और अधिक समस्याओं में भाग लेते हैं COUNT

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

यह योजना भयानक है। जब आप सेंट पैट्रिक दिवस पर अंतिम शो करते हैं, तो इस तरह की योजना आपके द्वारा समाप्त होती है। हालांकि अच्छी तरह से समानांतर, किसी कारण से यह पीके / सीएक्स को स्कैन कर रहा है। Ew। योजना में 2198 क्वेरी रुपये की लागत है।

पागल

Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 29532 ms,  elapsed time = 5828 ms.

यह एक अजीब पसंद है, क्योंकि अगर हम इसे गैर-अनुक्रमित सूचकांक का उपयोग करने के लिए मजबूर करते हैं, तो लागत 1798 क्वेरी रुपये में काफी कम हो जाती है।

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

अरे, तलाश करता है! आप वहां देखें। यह भी ध्यान दें कि, के जादू के साथ CROSS APPLY, हम एक पूरी तरह से समानांतर योजना के लिए नासमझ कुछ भी करने की जरूरत नहीं है।

पागल

Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 27625 ms,  elapsed time = 4909 ms.

क्रॉस एप्लाइंसेस का अंत COUNTसामान के बिना बेहतर तरीके से होता है।

SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY 
(
    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

योजना अच्छी लग रही है, लेकिन रीड और सीपीयू एक सुधार नहीं हैं।

पागल

Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 4844 ms,  elapsed time = 863 ms.

क्रॉस को फिर से लागू करना एक व्युत्पन्न सम्मिलित परिणाम होने के लिए सटीक एक ही सब कुछ है। मैं क्वेरी प्लान और आँकड़े जानकारी को फिर से पोस्ट नहीं करने जा रहा हूँ - वे वास्तव में नहीं बदले।

SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN 
(
    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x ON x.Id = u.Id;

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

के साथ एक प्रयास INTERSECT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   INTERSECT
                   SELECT u.Age WHERE u.Age IS NOT NULL );

पागल

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 1094 ms,  elapsed time = 1090 ms.

और यहाँ के साथ एक प्रयास है EXCEPT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   EXCEPT
                   SELECT u.Age WHERE u.Age IS NULL);

पागल

Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 2126 ms,  elapsed time = 376 ms.

इनको लिखने के और भी तरीके हो सकते हैं, लेकिन मैं उन लोगों को छोड़ दूंगा जो शायद इस्तेमाल करते हैं EXCEPTऔर जो INTERSECTमैं करते हैं, उससे कहीं ज्यादा।

यदि आपको वास्तवCOUNT में थोड़े थोड़े समय के लिए मेरे प्रश्नों में मेरे द्वारा उपयोग की जाने वाली गिनती की आवश्यकता है (पढ़ें: मैं कभी-कभी अधिक सम्मिलित परिदृश्यों के साथ आने के लिए बहुत आलसी हूं)। यदि आपको केवल एक गिनती की आवश्यकता है, तो आप एक CASEही चीज़ के बारे में करने के लिए एक अभिव्यक्ति का उपयोग कर सकते हैं ।

SELECT SUM(CASE WHEN u.Age < 18 THEN 1
                WHEN u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

इन दोनों को एक ही योजना मिलती है और एक ही सीपीयू और पढ़ने की विशेषताएँ होती हैं।

पागल

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 719 ms,  elapsed time = 719 ms.

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

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

धन्यवाद!


1
NOT EXISTS ( INTERSECT / EXCEPT )प्रश्नों के बिना काम कर सकते हैं INTERSECT / EXCEPTभागों: WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );एक और तरीका है - का उपयोग करता है EXCEPT: SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ; (जहां UserID पी या किसी अद्वितीय नहीं अशक्त स्तंभ (रों) है)।
ypercube y

क्या यह परीक्षण किया गया था? SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;क्षमा करें, यदि मैं आपके द्वारा परीक्षण किए गए मिलियन संस्करणों में चूक गया!
ypercube y

@ ypercube y यहाँ उस एक के लिए योजना है । यह थोड़ा अलग है, लेकिन UNION ALLयोजनाओं के समान विशेषताएं हैं (360ms CPU, 11k पढ़ता है)।
एरिक डार्लिंग

अरे एरिक, बस sql की दुनिया में घूम रहा था और आपको परेशान करने के लिए "कंप्यूटेड कॉलम" कहने के लिए पॉप अप कर रहा था। <3
क्रूसिबल

17

मैं सिर्फ एक टेबल के लिए 110 जीबी डेटाबेस को पुनर्स्थापित करने के लिए गेम नहीं था इसलिए मैंने अपना डेटा बनाया । आयु वितरण का मिलान स्टैक ओवरफ्लो पर होना चाहिए, लेकिन जाहिर है कि तालिका स्वयं से मेल नहीं खाएगी। मुझे नहीं लगता कि यह बहुत अधिक मुद्दा है क्योंकि क्वेरीज़ वैसे भी अनुक्रमित हिट करने जा रहे हैं। मैं SQL सर्वर 2016 SP1 के साथ 4 CPU कंप्यूटर पर परीक्षण कर रहा हूं। ध्यान देने वाली एक बात यह है कि इसे समाप्त करने वाले प्रश्नों के लिए वास्तविक निष्पादन योजना को शामिल नहीं करना महत्वपूर्ण है। जो कि चीजों को थोड़ा धीमा कर सकता है।

मैंने एरिक के उत्कृष्ट उत्तर में कुछ समाधानों के माध्यम से जाना शुरू किया। इसके लिए एक:

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

मुझे 10 से अधिक परीक्षणों में sysinos_exec_session से निम्नलिखित परिणाम मिले (क्वेरी स्वाभाविक रूप से मेरे लिए समानांतर चली गई):

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

एरिक के लिए बेहतर काम करने वाली क्वेरी वास्तव में मेरी मशीन पर खराब प्रदर्शन करती है:

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

10 परीक्षणों के परिणाम:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

मैं तुरंत यह नहीं बता पा रहा हूं कि यह इतना बुरा क्यों है, लेकिन यह स्पष्ट नहीं है कि हम समानांतर चलने के लिए क्वेरी प्लान में लगभग हर ऑपरेटर को बाध्य क्यों करना चाहते हैं। मूल योजना में हमारे पास एक सीरियल ज़ोन है जो सभी पंक्तियों को ढूंढता है AGE < 18। केवल कुछ हजार पंक्तियाँ हैं। मेरी मशीन पर मुझे क्वेरी के उस हिस्से के लिए 9 तार्किक रीड मिलते हैं और रिपोर्ट किए गए सीपीयू समय के 9 एमएस और बीता हुआ समय। पंक्तियों के लिए वैश्विक समुच्चय के लिए एक सीरियल ज़ोन भी है AGE IS NULLलेकिन यह केवल एक पंक्ति प्रति DOP प्रक्रिया करता है। मेरी मशीन पर यह सिर्फ चार पंक्तियाँ हैं।

मेरा मार्ग यह है कि उन पंक्तियों को खोजने के लिए सबसे महत्वपूर्ण है जो उन पंक्तियों को खोजते हैं जिनके NULLलिए Ageलाखों पंक्तियाँ हैं। मैं कम पृष्ठों के साथ एक सूचकांक बनाने में सक्षम नहीं था जो स्तंभ पर एक साधारण पृष्ठ-संपीड़ित की तुलना में डेटा को कवर करता था। मुझे लगता है कि प्रति पंक्ति में एक न्यूनतम सूचकांक आकार है या कि बहुत सारे सूचकांक स्थान को उन ट्रिक्स से बचा नहीं जा सकता है जो मैंने कोशिश की थी। इसलिए यदि हम डेटा प्राप्त करने के लिए लगभग समान संख्या में तार्किक पढ़ते हैं, तो इसे तेजी से बनाने का एकमात्र तरीका क्वेरी को अधिक समानांतर बनाना है, लेकिन यह एरिक के क्वेरी की तुलना में एक अलग तरीके से किया जाना चाहिए जो TF का उपयोग करता है 8649. ऊपर की क्वेरी में हमारे पास सीपीयू समय के लिए 3.62 का अनुपात है जो कि बहुत अच्छा है। आदर्श मेरी मशीन पर 4.0 का अनुपात होगा।

सुधार का एक संभावित क्षेत्र धागे के बीच काम को अधिक समान रूप से विभाजित करना है। नीचे स्क्रीनशॉट में हम देख सकते हैं कि मेरे एक सीपीयू ने थोड़ा ब्रेक लेने का फैसला किया:

आलसी धागा

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

मैं DOP 4 के साथ क्वेरीज़ चला रहा हूं इसलिए मुझे NULLतालिका में पंक्तियों को समान रूप से चार बाल्टी में विभाजित करने की आवश्यकता है । ऐसा करने का एक तरीका यह है कि गणना किए गए स्तंभों पर अनुक्रमणिकाओं का एक समूह बनाया जाए:

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

मुझे पूरा यकीन नहीं है कि चार अलग-अलग इंडेक्स एक इंडेक्स की तुलना में थोड़ा तेज क्यों है, लेकिन यह वही है जो मैंने अपने परीक्षण में पाया था।

एक समानांतर नेस्टेड लूप योजना प्राप्त करने के लिए मैं अविवादित ट्रेस ध्वज 8649 का उपयोग करने जा रहा हूं । मैं कोड को थोड़ा अजीब तरीके से लिखने जा रहा हूं ताकि आशावादी को आवश्यकता से अधिक पंक्तियों को संसाधित न करने के लिए प्रोत्साहित किया जा सके। नीचे एक कार्यान्वयन है जो अच्छी तरह से काम करता है:

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

दस परीक्षणों के परिणाम:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

उस क्वेरी के साथ हमारे पास 3.85 के समय के अनुपात में सीपीयू है! हमने रनटाइम से 17 एमएस का मुंडन किया और इसे करने के लिए केवल 4 कम्प्यूटेड कॉलम और इंडेक्स लिए! प्रत्येक थ्रेड समग्र पंक्तियों की समान संख्या के बहुत करीब है, क्योंकि प्रत्येक अनुक्रमणिका समान पंक्तियों की संख्या के बहुत पास है और प्रत्येक थ्रेड केवल एक पंक्ति स्कैन करता है:

अच्छी तरह से विभाजित काम

अंतिम नोट पर हम आसान बटन को भी हिट कर सकते हैं और Ageकॉलम में एक nonclustered CCI जोड़ सकते हैं :

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

निम्नलिखित क्वेरी मेरी मशीन पर 3 एमएस में समाप्त होती है:

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

जिसे हराना मुश्किल हो रहा है।


7

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

मैंने प्रश्नों का परीक्षण करने के लिए स्टैक एक्सचेंज डेटा एक्सप्लोरर (साथ में SET STATISTICS TIME ON;और SET STATISTICS IO ON;) का उपयोग किया। एक संदर्भ के लिए, यहाँ कुछ प्रश्न और सीपीयू / आईओ आँकड़े हैं:

प्रश्न १

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 0 ms। (1 पंक्ति)

तालिका 'उपयोगकर्ता'। स्कैन काउंट 17, लॉजिकल रीड्स 201567, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड 2740, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लोब रीड-फॉरवर्ड रीड्स 0।

SQL सर्वर निष्पादन समय: CPU समय = 1829 ms, बीता हुआ समय = 296 ms।

प्रश्न २

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 0 ms। (1 पंक्ति)

तालिका 'उपयोगकर्ता'। स्कैन काउंट 17, लॉजिकल रीड्स 201567, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

SQL सर्वर निष्पादन समय: CPU समय = 2500 ms, बीता समय = 147 ms।

प्रश्न ३

--Erik's derived tables/UNION ALL query.
SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 0 ms। (1 पंक्ति)

तालिका 'उपयोगकर्ता'। स्कैन गिनती 34, तार्किक रीड 403134, भौतिक रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

SQL सर्वर निष्पादन समय: CPU समय = 3156 ms, बीता हुआ समय = 215 ms।

पहला प्रयास

यह एरिक के सभी प्रश्नों की तुलना में धीमा था जो मैंने यहां सूचीबद्ध किया था ... कम से कम बीता समय के संदर्भ में।

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 0 ms। (1 पंक्ति)

टेबल 'वर्कटेबल'। स्कैन काउंट ०, लॉजिकल रीड्स ०, फिजिकल रीड्स ०, रीड-फॉरवर्ड रीड्स ०, लॉब लॉजिकल रीड्स ०, लॉब फिजिकल रीड्स ०, लॉब रीड-फॉरवर्ड रीड्स ०. टेबल rows सिट्रोसेट्स ’। स्कैन काउंट 2, लॉजिकल रीडिंग 10, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0. टेबल 'sysschobjs'। स्कैन काउंट 1, लॉजिकल रीड 4, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0. टेबल 'यूजर्स'। स्कैन काउंट 1, लॉजिकल रीड्स 201567, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

SQL सर्वर निष्पादन समय: CPU समय = 593 ms, बीता हुआ समय = 598 ms।

दूसरा प्रयास

यहां मैंने उपयोगकर्ताओं की कुल संख्या (उप-क्वेरी के बजाय) को संग्रहीत करने के लिए एक चर का विकल्प चुना। पहली कोशिश के मुकाबले स्कैन की गिनती 1 से बढ़कर 17 हो गई। लॉजिकल रीड वही रहे। हालांकि, बीता हुआ समय काफी कम हो गया।

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 0 ms। टेबल 'वर्कटेबल'। स्कैन काउंट ०, लॉजिकल रीड्स ०, फिजिकल रीड्स ०, रीड-फॉरवर्ड रीड्स ०, लॉब लॉजिकल रीड्स ०, लॉब फिजिकल रीड्स ०, लॉब रीड-फॉरवर्ड रीड्स ०. टेबल rows सिट्रोसेट्स ’। स्कैन काउंट 2, लॉजिकल रीडिंग 10, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0. टेबल 'sysschobjs'। स्कैन काउंट 1, लॉजिकल रीड 4, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

SQL सर्वर निष्पादन समय: CPU समय = 0 ms, बीता समय = 1 ms। (1 पंक्ति)

तालिका 'उपयोगकर्ता'। स्कैन काउंट 17, लॉजिकल रीड्स 201567, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

SQL सर्वर निष्पादन समय: CPU समय = 1471 ms, बीता हुआ समय = 98 ms।

अन्य नोट्स: स्टैक एक्सचेंज डेटा एक्सप्लोरर पर DBCC TRACEON की अनुमति नहीं है, जैसा कि नीचे दिया गया है:

उपयोगकर्ता 'STACKEXCHANGE \ svc_sede' के पास DBCC TRACEON को चलाने की अनुमति नहीं है।


1
उनके पास संभवतः समान सूचकांक नहीं हैं जो मैं करता हूं, इसलिए मतभेद। और कौन जानता है? हो सकता है कि मेरा होम सर्वर बेहतर हार्डवेयर पर हो;) हालांकि शानदार जवाब!
एरिक डार्लिंग

आपको अपने पहले अटैच के लिए निम्नलिखित क्वेरी का उपयोग करना चाहिए था (यह बहुत तेज़ होगा, क्योंकि यह sys.objects- ओवरहेड के बहुत से हिस्से पर होता है): SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
थॉमस फ्रांज

पुनश्च: ध्यान रखें कि इन-मेमोरी-इंडेक्स (NONCLUSTERED HASH) के पास इंडेक्स आईडी नहीं है = 0/1 एक सामान्य ढेर के रूप में / क्लस्टर इंडेक्स होगा)
थॉमस फ्रांज

1

चरों का उपयोग करें?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

प्रति टिप्पणी चर को छोड़ सकते हैं

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
इसके अलावा:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
सीपीयू और आईओ की जाँच करते समय यह कोशिश कर सकते हैं। संकेत: यह एरिक के जवाबों में से एक है।
ब्रेंट ओजर

0

अच्छी तरह से उपयोग करना SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

यह कुछ मेरे दिमाग में बस गया है। बस इसे https://data.stackexchange.com में निष्पादित किया गया है

लेकिन @blitz_erik जितना कुशल नहीं है


0

एक तुच्छ समाधान गणना (*) - गणना (आयु> = 18) की गणना करने के लिए है:

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

या:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

यहाँ परिणाम

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.