EXISTS आउटपरफॉर्म COUNT के साथ अस्तित्व की जाँच करें! … नहीं?


35

मैंने अक्सर पढ़ा है कि जब किसी को एक पंक्ति के अस्तित्व की जांच करनी होती है तो उसे हमेशा COUNT के बजाय EXISTS के साथ किया जाना चाहिए ।

फिर भी कई हालिया परिदृश्‍यों में मैंने गणना का उपयोग करते समय प्रदर्शन सुधार को मापा है।
पैटर्न इस प्रकार है:

LEFT JOIN (
    SELECT
        someID
        , COUNT(*)
    FROM someTable
    GROUP BY someID
) AS Alias ON (
    Alias.someID = mainTable.ID
)

मैं एसक्यूएल सर्वर के अंदर "क्या हो रहा है" बताने के तरीकों से परिचित नहीं हूं, इसलिए मैं सोच रहा था कि क्या EXISTS के साथ एक अनियंत्रित दोष था जो मेरे द्वारा किए गए मापों के लिए पूरी तरह से समझ में आता है (क्या EXISTS RBAR हो सकता है?)।

क्या आपके पास उस घटना के बारे में कुछ स्पष्टीकरण है?

संपादित करें:

यहां एक पूरी स्क्रिप्ट है जिसे आप चला सकते हैं:

SET NOCOUNT ON
SET STATISTICS IO OFF

DECLARE @tmp1 TABLE (
    ID INT UNIQUE
)


DECLARE @tmp2 TABLE (
    ID INT
    , X INT IDENTITY
    , UNIQUE (ID, X)
)

; WITH T(n) AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM master.dbo.spt_values AS S
) 
, tally(n) AS (
    SELECT
        T2.n * 100 + T1.n
    FROM T AS T1
    CROSS JOIN T AS T2
    WHERE T1.n <= 100
    AND T2.n <= 100
)
INSERT @tmp1
SELECT n
FROM tally AS T1
WHERE n < 10000


; WITH T(n) AS (
    SELECT
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM master.dbo.spt_values AS S
) 
, tally(n) AS (
    SELECT
        T2.n * 100 + T1.n
    FROM T AS T1
    CROSS JOIN T AS T2
    WHERE T1.n <= 100
    AND T2.n <= 100
)
INSERT @tmp2
SELECT T1.n
FROM tally AS T1
CROSS JOIN T AS T2
WHERE T1.n < 10000
AND T1.n % 3 <> 0
AND T2.n < 1 + T1.n % 15

PRINT '
COUNT Version:
'

WAITFOR DELAY '00:00:01'

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT
    T1.ID
    , CASE WHEN n > 0 THEN 1 ELSE 0 END AS DoesExist
FROM @tmp1 AS T1
LEFT JOIN (
    SELECT
        T2.ID
        , COUNT(*) AS n
    FROM @tmp2 AS T2
    GROUP BY T2.ID
) AS T2 ON (
    T2.ID = T1.ID
)
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (RECOMPILE) -- Required since table are filled within the same scope

SET STATISTICS TIME OFF

PRINT '

EXISTS Version:'

WAITFOR DELAY '00:00:01'

SET STATISTICS TIME ON

SELECT
    T1.ID
    , CASE WHEN EXISTS (
        SELECT 1
        FROM @tmp2 AS T2
        WHERE T2.ID = T1.ID
    ) THEN 1 ELSE 0 END AS DoesExist
FROM @tmp1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (RECOMPILE) -- Required since table are filled within the same scope

SET STATISTICS TIME OFF 

SQL Server 2008R2 (सात 64 बिट्स) पर मुझे यह परिणाम मिलता है

COUNT संस्करण:

तालिका '# 455F344D'। स्कैन काउंट 1, लॉजिकल रीड 8, फिजिकल
रीड्स 0, रीड- फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड- फॉरवर्ड रीड्स 0. टेबल '# 492FC531'। स्कैन काउंट 1, लॉजिकल रीड 30, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

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

EXISTS संस्करण:

टेबल '# 492FC531'। स्कैन काउंट 1, लॉजिकल रीड 96, फिजिकल
रीड्स 0, रीड- फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड- फॉरवर्ड रीड्स 0. टेबल '# 455F344D'। स्कैन काउंट 1, लॉजिकल रीड 8, फिजिकल रीड्स 0, रीड-फॉरवर्ड रीड्स 0, लॉब लॉजिकल रीड्स 0, लॉब फिजिकल रीड्स 0, लॉब रीड-फॉरवर्ड रीड्स 0।

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

जवाबों:


43

मैंने अक्सर पढ़ा है कि जब किसी को एक पंक्ति के अस्तित्व की जांच करनी होती है तो उसे हमेशा COUNT के बजाय EXISTS के साथ किया जाना चाहिए ।

कुछ भी हमेशा सच होने के लिए यह बहुत दुर्लभ है, खासकर जब यह डेटाबेस की बात आती है। SQL में एक ही शब्दार्थ को व्यक्त करने के कई तरीके हैं। यदि अंगूठे का एक उपयोगी नियम है, तो यह उपलब्ध सबसे प्राकृतिक वाक्यविन्यास (और, हां, यह व्यक्तिपरक है) का उपयोग करके प्रश्न लिखना हो सकता है और केवल उस पुनर्लेखन पर विचार करें यदि आपके द्वारा प्राप्त क्वेरी योजना या प्रदर्शन अस्वीकार्य है।

इसके लायक क्या है, इस मुद्दे पर मेरा खुद का मानना ​​है कि अस्तित्व के प्रश्नों को सबसे स्वाभाविक रूप से उपयोग किया जाता है EXISTS। यह मेरा अनुभव भी रहा है कि अस्वीकार विकल्प से EXISTS बेहतर अनुकूलन करता है । उपयोग करना और फ़िल्टर करना एक अन्य विकल्प है, जो SQL सर्वर क्वेरी ऑप्टिमाइज़र में कुछ समर्थन के लिए होता है, लेकिन मैंने व्यक्तिगत रूप से इसे अधिक जटिल प्रश्नों में अविश्वसनीय पाया है। किसी भी मामले में, उन विकल्पों में से केवल अधिक स्वाभाविक (मुझे) लगता है।OUTER JOINNULLCOUNT(*)=0EXISTS

मैं सोच रहा था कि क्या EXISTS के साथ एक अनहेल्दी दोष था जो मेरे द्वारा किए गए मापों के लिए पूरी तरह से समझ में आता है

आपका विशेष उदाहरण दिलचस्प है, क्योंकि यह ऑप्टिमाइज़र जिस तरह से CASEअभिव्यक्तियों (और EXISTSविशेष रूप से परीक्षण) में उपश्रेणियों से निपटता है ।

CASE अभिव्यक्तियों में उपश्रेणियाँ

निम्नलिखित पर विचार करें (पूरी तरह से कानूनी) प्रश्न:

DECLARE @Base AS TABLE (a integer NULL);
DECLARE @When AS TABLE (b integer NULL);
DECLARE @Then AS TABLE (c integer NULL);
DECLARE @Else AS TABLE (d integer NULL);

SELECT
    CASE
        WHEN (SELECT W.b FROM @When AS W) = 1
            THEN (SELECT T.c FROM @Then AS T)
        ELSE (SELECT E.d FROM @Else AS E)
    END
FROM @Base AS B;

का शब्दार्थCASE यह है कि WHEN/ELSEखंडों का मूल्यांकन आमतौर पर पाठ्य क्रम में किया जाता है। उपर्युक्त प्रश्न में, SQL सर्वर के लिए यह गलत होगा कि ELSEयदि WHENउपवाक्य एक पंक्ति से अधिक हो, तो क्लॉउड संतुष्ट होने पर कोई त्रुटि देता है। इन शब्दार्थों का सम्मान करने के लिए, ऑप्टिमाइज़र एक योजना का निर्माण करता है जो पास-थ्रू विधेयकों का उपयोग करता है:

पास-थ्रू भविष्यवाणी

नेस्टेड लूप जॉइनर्स के आंतरिक पक्ष का मूल्यांकन केवल तब किया जाता है जब पास-थ्रू प्रेडेटेट झूठी हो जाता है। समग्र प्रभाव यह है कि CASEअभिव्यक्तियों का क्रम में परीक्षण किया जाता है, और उपश्रेणियों का मूल्यांकन केवल तभी किया जाता है यदि कोई पिछली अभिव्यक्ति संतुष्ट नहीं थी।

एक परीक्षा उपश्रेणी के साथ भाव व्यक्त करें

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

कार्यान्वयन का विवरण तार्किक उपकुंजी को एक संबद्ध स्तंभ के साथ एक सहसंबंधित जुड़ाव ('लागू') द्वारा प्रतिस्थापित किया जाता है। कार्य क्वेरी ऑप्टिमाइज़र में एक सरलीकरण नियम द्वारा किया जाता है जिसे RemoveSubqInPrj(प्रक्षेपण में उपश्रेणी हटाएं) कहा जाता है । हम ट्रेस ध्वज 8606 का उपयोग करके विवरण देख सकते हैं:

SELECT
    T1.ID,
    CASE
        WHEN EXISTS 
        (
            SELECT 1
            FROM #T2 AS T2
            WHERE T2.ID = T1.ID
        ) THEN 1 
    ELSE 0
    END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000
OPTION (QUERYTRACEON 3604, QUERYTRACEON 8606);

EXISTSपरीक्षण दिखाने वाले इनपुट ट्री का हिस्सा नीचे दिखाया गया है:

ScaOp_Exists 
    LogOp_Project
        LogOp_Select
            LogOp_Get TBL: #T2
            ScaOp_Comp x_cmpEq
                ScaOp_Identifier [T2].ID
                ScaOp_Identifier [T1].ID

यह RemoveSubqInPrjएक संरचना की अध्यक्षता कर रहा है:

LogOp_Apply (x_jtLeftSemi probe PROBE:COL: Expr1008)

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

इस क्वेरी के लिए संभावित निष्पादन योजना आकृतियों में से एक उस तार्किक संरचना का प्रत्यक्ष कार्यान्वयन है:

एनएलजे सेमी प्रोब के साथ जुड़ें

अंतिम गणना स्केलर CASEजांच कॉलम मान का उपयोग करते हुए अभिव्यक्ति के परिणाम का मूल्यांकन करता है :

गणना स्केलर अभिव्यक्ति

योजना वृक्ष के मूल आकार को संरक्षित किया जाता है जब अनुकूलन अर्ध सम्मिलित होने के लिए अन्य भौतिक प्रकारों पर विचार करता है। केवल मर्ज ज्वाइन एक जांच कॉलम का समर्थन करता है, इसलिए एक हैश अर्ध शामिल होता है, हालांकि तार्किक रूप से संभव है, माना नहीं जाता है:

जांच कॉलम के साथ विलय

ध्यान दें कि मर्ज एक अभिव्यक्ति लेबल करता है Expr1008(कि नाम पहले जैसा ही एक संयोग है) हालांकि इसके लिए कोई परिभाषा योजना में किसी भी ऑपरेटर पर दिखाई नहीं देती है। यह सिर्फ जांच स्तंभ है। पहले की तरह, अंतिम गणना स्केलर मूल्यांकन करने के लिए इस जांच मूल्य का उपयोग करता है CASE

समस्या यह है कि ऑप्टिमाइज़र पूरी तरह से उन विकल्पों का पता नहीं लगाता है जो केवल मर्ज (या हैश) अर्ध जुड़ने के साथ सार्थक हो जाते हैं। नेस्टेड लूप्स योजना में, यह जाँचने का कोई लाभ नहीं है कि पंक्तियाँ T2प्रत्येक पुनरावृत्ति पर श्रेणी से मेल खाती हैं या नहीं। मर्ज या हैश प्लान के साथ, यह एक उपयोगी अनुकूलन हो सकता है।

यदि हम क्वेरी में एक मिलान BETWEENविधेय जोड़ते हैं T2, तो यह सब होता है कि यह चेक प्रत्येक पंक्ति के लिए मर्ज सेमी जॉइन पर अवशिष्ट के रूप में किया जाता है (निष्पादन योजना में स्पॉट करने के लिए कठिन है, लेकिन यह वहां है):

SELECT
    T1.ID,
    CASE
        WHEN EXISTS 
        (
            SELECT 1
            FROM #T2 AS T2
            WHERE T2.ID = T1.ID
            AND T2.ID BETWEEN 5000 AND 7000 -- New
        ) THEN 1 
    ELSE 0
    END AS DoesExist
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;

अवशिष्ट विधेय

हम आशा करेंगे कि BETWEENविधेय को इसके T2परिणामस्वरूप नीचे ढकेल दिया जाएगा । आम तौर पर, ऑप्टिमाइज़र ऐसा करने पर विचार करेगा (क्वेरी में अतिरिक्त विधेय के बिना भी)। यह पहचानता गर्भित विधेय ( BETWEENपर T1और के बीच विधेय में शामिल होने T1और T2एक साथ संकेत BETWEENपर T2) के बिना उन्हें मूल प्रश्न टेक्स्ट में मौजूद जा रहा है। दुर्भाग्य से, लागू-जांच पैटर्न का मतलब यह नहीं है।

मर्ज अर्ध जुड़ने के लिए दोनों इनपुट पर खोज करने के लिए क्वेरी लिखने के तरीके हैं। एक तरीके में क्वेरी को बहुत ही अप्राकृतिक तरीके से लिखना शामिल है (कारण जो मैं आमतौर पर पसंद करता हूं उसे हरा देना EXISTS):

WITH T2 AS
(
    SELECT TOP (9223372036854775807) * 
    FROM #T2 AS T2 
    WHERE ID BETWEEN 5000 AND 7000
)
SELECT 
    T1.ID, 
    DoesExist = 
        CASE 
            WHEN EXISTS 
            (
                SELECT * FROM T2 
                WHERE T2.ID = T1.ID
            ) THEN 1 ELSE 0 END
FROM #T1 AS T1
WHERE T1.ID BETWEEN 5000 AND 7000;

टॉप ट्रिक प्लान

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


6

"COUNT (*) बनाम मौजूद है" तर्क की जाँच एक रिकार्ड मौजूद है या नहीं के साथ क्या करना है। उदाहरण के लिए:

WHERE (SELECT COUNT(*) FROM Table WHERE ID=@ID)>0

बनाम

WHERE EXISTS(SELECT ID FROM Table WHERE ID=@ID)

आपकी SQL स्क्रिप्ट COUNT(*)रिकॉर्ड मौजूद चेक के रूप में उपयोग नहीं कर रही है , और इसलिए मैं यह नहीं कहूंगा कि यह आपके परिदृश्य में लागू है।


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