कुशलतापूर्वक कई कॉलम पर EXISTS की जाँच कैसे करें?


26

यह एक ऐसा मुद्दा है जो मैं समय-समय पर उठाता हूं और अभी तक इसका कोई अच्छा समाधान नहीं खोज पाया हूं।

निम्न तालिका संरचना का समर्थन करना

CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)

और आवश्यकता यह निर्धारित करने के लिए है कि क्या या तो अशक्त स्तंभ हैं Bया Cवास्तव में कोई भी NULLमान हैं (और यदि ऐसा है तो कौन है)।

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

मैं इसके निकट आने के कुछ तरीकों के बारे में सोच सकता हूं लेकिन सभी में कमजोरियां हैं।

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

सिंगल एग्रीगेट क्वेरी

SELECT 
    MAX(CASE WHEN B IS NULL THEN 1 ELSE 0 END) AS B,
    MAX(CASE WHEN C IS NULL THEN 1 ELSE 0 END) AS C
FROM T

यह एक ही समय में दोनों स्तंभों को संसाधित कर सकता है इसलिए एक पूर्ण स्कैन का सबसे खराब मामला है। नुकसान यह है कि भले ही यह NULLक्वेरी पर बहुत जल्दी दोनों स्तंभों में सामना करता है , फिर भी तालिका के बाकी हिस्सों को स्कैन करना समाप्त कर देगा।

उपयोगकर्ता चर

मैं ऐसा करने के तीसरे तरीके के बारे में सोच सकता हूं

BEGIN TRY
DECLARE @B INT, @C INT, @D INT

SELECT 
    @B = CASE WHEN B IS NULL THEN 1 ELSE @B END,
    @C = CASE WHEN C IS NULL THEN 1 ELSE @C END,
    /*Divide by zero error if both @B and @C are 1.
    Might happen next row as no guarantee of order of
    assignments*/
    @D = 1 / (2 - (@B + @C))
FROM T  
OPTION (MAXDOP 1)       
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 /*Divide by zero*/
    BEGIN
    SELECT 'B,C both contain NULLs'
    RETURN;
    END
ELSE
    RETURN;
END CATCH

SELECT ISNULL(@B,0),
       ISNULL(@C,0)

लेकिन यह उत्पादन कोड के लिए उपयुक्त नहीं है क्योंकि समुच्चयबोधक क्वेरी के लिए सही व्यवहार अपरिभाषित है। और एक त्रुटि को फेंककर स्कैन को समाप्त करना वैसे भी काफी भयानक समाधान है।

क्या कोई अन्य विकल्प है जो ऊपर दिए गए दृष्टिकोणों की ताकत को जोड़ता है?

संपादित करें

अब तक प्रस्तुत किए गए उत्तरों के संदर्भ में मेरे द्वारा प्राप्त परिणामों के साथ इसे अपडेट करने के लिए (@ ypercube के परीक्षण डेटा का उपयोग करके)

+----------+------------+------+---------+----------+----------------------+----------+------------------+
|          | 2 * EXISTS | CASE | Kejser  |  Kejser  |        Kejser        | ypercube |       8kb        |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
|          |            |      |         | MAXDOP 1 | HASH GROUP, MAXDOP 1 |          |                  |
| No Nulls |      15208 | 7604 |    8343 | 7604     | 7604                 |    15208 | 8346 (8343+3)    |
| One Null |       7613 | 7604 |    8343 | 7604     | 7604                 |     7620 | 7630 (25+7602+3) |
| Two Null |         23 | 7604 |    8343 | 7604     | 7604                 |       30 | 30 (18+12)       |
+----------+------------+------+---------+----------+----------------------+----------+------------------+

@ थॉमस का जवाब मैं बदल के लिए TOP 3करने के लिए TOP 2यह पहले बाहर निकलने के लिए संभावित रूप से अनुमति देने के लिए। मुझे उस उत्तर के लिए डिफ़ॉल्ट रूप से एक समानांतर योजना मिली, इसलिए MAXDOP 1अन्य योजनाओं की तुलना में अधिक पढ़े जाने की संख्या को सुनिश्चित करने के लिए इसे एक संकेत के साथ आजमाया । मैं परिणामों से कुछ हद तक आश्चर्यचकित था क्योंकि मेरे पहले के परीक्षण में मैंने पूरी तालिका को पढ़े बिना उस शार्ट सर्किट को देखा था।

मेरे परीक्षण डेटा की योजना है कि शॉर्ट सर्किट नीचे है

शॉर्ट सर्किट

Ypercube के डेटा की योजना है

शार्टसर्किट नहीं

तो यह योजना के लिए एक अवरुद्ध प्रकार ऑपरेटर जोड़ता है। मैंने भी HASH GROUPसंकेत के साथ प्रयास किया लेकिन वह अभी भी सभी पंक्तियों को पढ़ते हुए समाप्त होता है

शार्टसर्किट नहीं

ऐसा लगता है कि कुंजी को hash match (flow distinct)इस योजना को शॉर्ट सर्किट की अनुमति देने के लिए एक ऑपरेटर मिल रहा है क्योंकि अन्य विकल्प वैसे भी सभी पंक्तियों को अवरुद्ध और उपभोग करेंगे। मुझे नहीं लगता कि यह विशेष रूप से लेकिन जाहिरा तौर पर मजबूर करने के लिए संकेत है , "सामान्य तौर पर, ऑप्टिमाइज़र एक फ्लो डिस्टिक्ट चुनता है जहां यह निर्धारित करता है कि इनपुट सेट में अलग-अलग मान होने से कम आउटपुट पंक्तियों की आवश्यकता होती है।"

@ ypercube के डेटा में NULLमूल्यों के साथ प्रत्येक कॉलम में केवल 1 पंक्ति है (तालिका कार्डिनैलिटी = 30300) और ऑपरेटर के बाहर और बाहर जाने वाली अनुमानित पंक्तियाँ दोनों हैं 1। आशावादी को विधेय को थोड़ा और अधिक अपारदर्शी बनाकर उसने फ्लो डिस्टिक्ट ऑपरेटर के साथ एक योजना तैयार की।

SELECT TOP 2 *
FROM (SELECT DISTINCT 
        CASE WHEN b IS NULL THEN NULL ELSE 'foo' END AS b
      , CASE WHEN c IS NULL THEN NULL ELSE 'bar' END AS c
  FROM test T 
  WHERE LEFT(b,1) + LEFT(c,1) IS NULL
) AS DT 

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

एक आखिरी मोड़ जो मेरे साथ हुआ, वह यह है कि ऊपर की क्वेरी अभी भी उस घटना में आवश्यक से अधिक पंक्तियों को संसाधित करने में समाप्त हो सकती है जो पहली पंक्ति में एक NULLस्तंभ में NULLs के साथ सामना करती है Bऔर C। यह तुरंत बाहर निकलने के बजाय स्कैनिंग जारी रखेगा। इससे बचने का एक तरीका यह है कि पंक्तियों को स्कैन करने के बाद उन्हें खोल दिया जाए। इसलिए थॉमस केसर के जवाब में मेरा अंतिम संशोधन नीचे है

SELECT DISTINCT TOP 2 NullExists
FROM test T 
CROSS APPLY (VALUES(CASE WHEN b IS NULL THEN 'b' END),
                   (CASE WHEN c IS NULL THEN 'c' END)) V(NullExists)
WHERE NullExists IS NOT NULL

यह संभव है कि विधेय के लिए बेहतर होगा, WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULLलेकिन पिछले परीक्षण डेटा के खिलाफ कि कोई मुझे फ्लो डिस्टिक्ट के साथ योजना नहीं देता है, जबकि NullExists IS NOT NULLएक (नीचे योजना) करता है।

Unpivoted

जवाबों:


20

कैसा रहेगा:

SELECT TOP 3 *
FROM (SELECT DISTINCT 
        CASE WHEN B IS NULL THEN NULL ELSE 'foo' END AS B
        , CASE WHEN C IS NULL THEN NULL ELSE 'bar' END AS C
  FROM T 
  WHERE 
    (B IS NULL AND C IS NOT NULL) 
    OR (B IS NOT NULL AND C IS NULL) 
    OR (B IS NULL AND C IS NULL)
) AS DT

मुझे यह तरीका पसंद है। कुछ संभावित मुद्दे हैं जो मैं अपने प्रश्न के संपादन में संबोधित करता हूं। लिखित रूप में TOP 3बस हो सकता है TOP 2जब तक यह निम्न में से प्रत्येक से एक पाता है यह स्कैन करेगा वर्तमान में के रूप में (NOT_NULL,NULL), (NULL,NOT_NULL), (NULL,NULL)। उन 3 में से कोई भी 2 पर्याप्त होगा - और यदि यह (NULL,NULL)पहले मिल जाता है तो दूसरे की भी जरूरत नहीं होगी। इसके अलावा शॉर्ट सर्किट के लिए योजना को एक hash match (flow distinct)ऑपरेटर के बजाय विशिष्ट को लागू करने की आवश्यकता होगी hash match (aggregate)याdistinct sort
मार्टिन स्मिथ

6

जैसा कि मैं इस प्रश्न को समझता हूं, आप जानना चाहते हैं कि क्या वास्तव में पंक्तियों को वापस करने का विरोध करने वाले स्तंभों में से किसी में एक नल मौजूद है या तो बी या सी शून्य है। अगर ऐसा है, तो क्यों नहीं:

Select Top 1 'B as nulls' As Col
From T
Where T.B Is Null
Union All
Select Top 1 'C as nulls'
From T
Where T.C Is Null

SQL 2008 R2 और एक मिलियन पंक्तियों के साथ अपने परीक्षण रिग पर, मुझे क्लाइंट सांख्यिकी टैब से एमएस में निम्नलिखित परिणाम मिले:

Kejser                          2907,2875,2829,3576,3103
ypercube                        2454,1738,1743,1765,2305
OP single aggregate solution    (stopped after 120,000 ms) Wouldn't even finish
My solution                     1619,1564,1665,1675,1674

यदि आप नोलॉक संकेत जोड़ते हैं, तो परिणाम और भी तेज़ होते हैं:

Select Top 1 'B as nulls' As Col
From T With(Nolock)
Where T.B Is Null
Union All
Select Top 1 'C as nulls'
From T With(Nolock)
Where T.C Is Null

My solution (with nolock)       42,70,94,138,120

संदर्भ के लिए मैंने डेटा उत्पन्न करने के लिए रेड-गेट के एसक्यूएल जेनरेटर का उपयोग किया। मेरी एक लाख पंक्तियों में से, 9,886 पंक्तियों में अशक्त B मान था और 10,019 में अशक्त C मान था।

परीक्षणों की इस श्रृंखला में, स्तंभ B की प्रत्येक पंक्ति का एक मूल्य है:

Kejser                          245200  Scan count 1, logical reads 367259, physical reads 858, read-ahead reads 367278
                                250540  Scan count 1, logical reads 367259, physical reads 860, read-ahead reads 367280

ypercube(1)                     249137  Scan count 2, logical reads 367276, physical reads 850, read-ahead reads 367278
                                248276  Scan count 2, logical reads 367276, physical reads 869, read-ahead reads 368765

My solution                     250348  Scan count 2, logical reads 367276, physical reads 858, read-ahead reads 367278
                                250327  Scan count 2, logical reads 367276, physical reads 854, read-ahead reads 367278

प्रत्येक परीक्षा (दोनों सेट) इससे पहले कि मैं भाग गया CHECKPOINTऔर DBCC DROPCLEANBUFFERS

यहां परिणाम हैं जब तालिका में कोई नल नहीं हैं। ध्यान दें कि 2 मौजूद समाधान ypercube द्वारा प्रदान किया गया है, यह रीड और निष्पादन समय के संदर्भ में मेरे लिए लगभग समान है। I (हम) का मानना ​​है कि यह एडवांस स्कैनिंग के उपयोग के एंटरप्राइज़ / डेवलपर संस्करण के फायदे के कारण है । यदि आप केवल मानक संस्करण या निम्न का उपयोग कर रहे हैं, तो केसर का समाधान बहुत तेजी से समाधान हो सकता है।

Kejser                          248875  Scan count 1, logical reads 367259, physical reads 860, read-ahead reads 367290

ypercube(1)                     243349  Scan count 2, logical reads 367265, physical reads 851, read-ahead reads 367278
                                242729  Scan count 2, logical reads 367265, physical reads 858, read-ahead reads 367276
                                242531  Scan count 2, logical reads 367265, physical reads 855, read-ahead reads 367278

My solution                     243094  Scan count 2, logical reads 367265, physical reads 857, read-ahead reads 367278
                                243444  Scan count 2, logical reads 367265, physical reads 857, read-ahead reads 367278

4

क्या IFकथन की अनुमति है?

इससे आपको तालिका के माध्यम से एक पास पर बी या सी के अस्तित्व की पुष्टि करने की अनुमति मिलनी चाहिए:

DECLARE 
  @A INT, 
  @B CHAR(10), 
  @C CHAR(10)

SET @B = 'X'
SET @C = 'X'

SELECT TOP 1 
  @A = A, 
  @B = B, 
  @C = C
FROM T 
WHERE B IS NULL OR C IS NULL 

IF @@ROWCOUNT = 0 
BEGIN 
  SELECT 'No nulls'
  RETURN
END

IF @B IS NULL AND @C IS NULL
BEGIN
  SELECT 'Both null'
  RETURN
END 

IF @B IS NULL 
BEGIN
  SELECT TOP 1 
    @C = C
  FROM T
  WHERE A > @A
  AND C IS NULL

  IF @B IS NULL AND @C IS NULL 
  BEGIN
    SELECT 'Both null'
    RETURN
  END
  ELSE
  BEGIN
    SELECT 'B is null'
    RETURN
  END
END

IF @C IS NULL 
BEGIN
  SELECT TOP 1 
    @B = B
  FROM T 
  WHERE A > @A
  AND B IS NULL

  IF @C IS NULL AND @B IS NULL
  BEGIN
    SELECT 'Both null'
    RETURN
  END
  ELSE
  BEGIN
    SELECT 'C is null'
    RETURN
  END
END      

4

संस्करणों में SQL-Fiddle में परीक्षण किया गया: 30K पंक्तियों के साथ 2008 r2 और 2012

  • EXISTSक्वेरी शो दक्षता में एक विशाल लाभ जब यह Nulls जल्दी पाता है - जो की उम्मीद है।
  • मुझे EXISTS2012 में सभी मामलों में क्वेरी के साथ बेहतर प्रदर्शन मिला , जिसे मैं समझा नहीं सकता।
  • 2008R2 में, जब कोई नल नहीं हैं, यह अन्य 2 प्रश्नों की तुलना में धीमा है। जितनी जल्दी यह नल को पाता है, उतनी ही तेजी से मिलता है और जब दोनों स्तंभों में नल जल्दी होता है, तो यह अन्य 2 प्रश्नों की तुलना में बहुत तेज होता है।
  • मार्टिन की CASEक्वेरी की तुलना में थॉमस केसर की क्वेरी 2012 में लगातार बेहतर और लगातार बेहतर प्रदर्शन करती हुई और 2008 की तुलना में 2008 की तुलना में बेहतर है ।
  • लगता है कि 2012 के संस्करण का प्रदर्शन बेहतर है। यह एसक्यूएल-फिडेल सर्वर की सेटिंग्स के साथ करना पड़ सकता है, न कि केवल ऑप्टिमाइज़र पर सुधार के साथ।

प्रश्न और समय। समय कहाँ किया गया:

  • 1 बिल्कुल नहीं नल के साथ
  • स्तंभ के साथ 2 Bएक होने NULLएक छोटा सा पर id
  • दोनों कॉलम के साथ NULLछोटी आईडी पर एक दूसरे के साथ 3 डी।

यहां हम जाते हैं (योजनाओं के साथ एक समस्या है, मैं बाद में फिर से कोशिश करूंगा। अभी के लिए लिंक का पालन करें):


2 परीक्षा उपश्रेणियों के साथ प्रश्न

SELECT 
      CASE WHEN EXISTS (SELECT * FROM test WHERE b IS NULL)
             THEN 1 ELSE 0 
      END AS B,
      CASE WHEN EXISTS (SELECT * FROM test WHERE c IS NULL)
             THEN 1 ELSE 0 
      END AS C ;

-------------------------------------
Times in ms (2008R2): 1344 - 596 -  1  
Times in ms   (2012):   26 -  14 -  2

मार्टिन स्मिथ की सिंगल एग्रीगेट क्वेरी

SELECT 
    MAX(CASE WHEN b IS NULL THEN 1 ELSE 0 END) AS B,
    MAX(CASE WHEN c IS NULL THEN 1 ELSE 0 END) AS C
FROM test ;

--------------------------------------
Times in ms (2008R2):  558 - 553 - 516  
Times in ms   (2012):   37 -  35 -  36

थॉमस केजर की क्वेरी

SELECT TOP 3 *
FROM (SELECT DISTINCT 
        CASE WHEN B IS NULL THEN NULL ELSE 'foo' END AS b
      , CASE WHEN C IS NULL THEN NULL ELSE 'bar' END AS c
  FROM test T 
  WHERE 
    (B IS NULL AND C IS NOT NULL) 
    OR (B IS NOT NULL AND C IS NULL) 
    OR (B IS NULL AND C IS NULL)
) AS DT ;

--------------------------------------
Times in ms (2008R2):  859 - 705 - 668  
Times in ms   (2012):   24 -  19 -  18

मेरा सुझाव (1)

WITH tmp1 AS
  ( SELECT TOP (1) 
        id, b, c
    FROM test
    WHERE b IS NULL OR c IS NULL
    ORDER BY id 
  ) 

  SELECT 
      tmp1.*, 
      NULL AS id2, NULL AS b2, NULL AS c2
  FROM tmp1
UNION ALL
  SELECT *
  FROM
    ( SELECT TOP (1)
          tmp1.id, tmp1.b, tmp1.c,
          test.id AS id2, test.b AS b2, test.c AS c2 
      FROM test
        CROSS JOIN tmp1
      WHERE test.id >= tmp1.id
        AND ( test.b IS NULL AND tmp1.c IS NULL
           OR tmp1.b IS NULL AND test.c IS NULL
            )
      ORDER BY test.id
    ) AS x ;

--------------------------------------
Times in ms (2008R2): 1089 - 572 -  16   
Times in ms   (2012):   28 -  15 -   1

यह आउटपुट पर कुछ चमकाने की जरूरत है लेकिन दक्षता EXISTSक्वेरी के समान है । मुझे लगा कि यह बेहतर होगा जब कोई नल नहीं होगा लेकिन परीक्षण से पता चलता है कि यह नहीं है।


सुझाव (2)

तर्क को आसान बनाने की कोशिश कर रहा है:

CREATE TABLE tmp
( id INT
, b CHAR(1000)
, c CHAR(1000)
) ;

DELETE  FROM tmp ;

INSERT INTO tmp 
    SELECT TOP (1) 
        id, b, c
    FROM test
    WHERE b IS NULL OR c IS NULL
    ORDER BY id  ; 

INSERT INTO tmp 
    SELECT TOP (1)
        test.id, test.b, test.c 
      FROM test
        JOIN tmp 
          ON test.id >= tmp.id
      WHERE ( test.b IS NULL AND tmp.c IS NULL
           OR tmp.b IS NULL AND test.c IS NULL
            )
      ORDER BY test.id ;

SELECT *
FROM tmp ;

यह पिछले सुझाव की तुलना में 2008R2 में बेहतर प्रदर्शन करता है, लेकिन 2012 में इससे भी बदतर (शायद INSERT2kb का उपयोग करके दोबारा लिखा जा सकता है IF, जैसे @ 8kb का उत्तर):

------------------------------------------
Times in ms (2008R2): 416+6 - 1+127 -  1+1   
Times in ms   (2012):  14+1 - 0+27  -  0+29

0

जब आप EXISTS का उपयोग करते हैं, तो SQL सर्वर जानता है कि आप एक अस्तित्व की जाँच कर रहे हैं। जब यह पहला मिलान मूल्य पाता है, तो यह TRUE देता है और देखना बंद कर देता है।

जब आप 2 कॉलम संक्षिप्त करते हैं और यदि कोई शून्य है तो परिणाम शून्य होगा

जैसे

null + 'a' = null

इसलिए इस कोड की जाँच करें

IF EXISTS (SELECT 1 FROM T WHERE B+C is null)
SELECT Top 1 ISNULL(B,'B ') + ISNULL(C,'C') as [Nullcolumn] FROM T WHERE B+C is null

-3

कैसा रहेगा:

select 
    exists(T.B is null) as 'B is null',
    exists(T.C is null) as 'C is null'
from T;

यदि यह काम करता है (मैंने इसका परीक्षण नहीं किया है), तो यह 2 कॉलमों के साथ एक-पंक्ति तालिका, प्रत्येक TRUE या FALSE होगा। मैंने दक्षता का परीक्षण नहीं किया।


2
यहां तक ​​कि अगर यह किसी अन्य DBMS में मान्य है, तो मुझे संदेह है कि इसमें सही शब्दार्थ है। यह मानते हुए कि T.B is nullएक बूलियन परिणाम के रूप में व्यवहार किया जाता है EXISTS(SELECT true)और EXISTS(SELECT false)क्या दोनों सच वापस आएंगे।यह MySQL उदाहरण बताता है कि दोनों कॉलम में NULL होता है जब न तो वास्तव में होता है
मार्टिन स्मिथ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.