संगणित संगणित स्तंभ पर अनुक्रमणिका को संगणित अभिव्यक्ति में कॉलम प्राप्त करने के लिए महत्वपूर्ण खोज की आवश्यकता होती है


24

मेरे पास एक मेज पर एक निरंतर कम्प्यूटेड कॉलम है, जो केवल संक्षिप्त कॉलम से बना है, जैसे

CREATE TABLE dbo.T 
(   
    ID INT IDENTITY(1, 1) NOT NULL CONSTRAINT PK_T_ID PRIMARY KEY,
    A VARCHAR(20) NOT NULL,
    B VARCHAR(20) NOT NULL,
    C VARCHAR(20) NOT NULL,
    D DATE NULL,
    E VARCHAR(20) NULL,
    Comp AS A + '-' + B + '-' + C PERSISTED NOT NULL 
);

यह Compअद्वितीय नहीं है, और D प्रत्येक संयोजन की तिथि से मान्य है A, B, C, इसलिए मैं प्रत्येक के लिए अंतिम तिथि A, B, C(मूल रूप से Comp के समान मूल्य के लिए अगली आरंभ तिथि) प्राप्त करने के लिए निम्नलिखित क्वेरी का उपयोग करता हूं :

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1
WHERE   t1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY t1.Comp;

फिर मैंने इस क्वेरी (और अन्य लोगों) में सहायता के लिए गणना किए गए कॉलम में एक इंडेक्स जोड़ा:

CREATE NONCLUSTERED INDEX IX_T_Comp_D ON dbo.T (Comp, D) WHERE D IS NOT NULL;

क्वेरी प्लान ने हालांकि मुझे चौंका दिया। मैंने सोचा होगा कि चूंकि मेरे पास एक क्लॉज है, जो कि D IS NOT NULLमैं और मैं छांट रहे हैं Comp, और सूचकांक के बाहर किसी भी कॉलम का उल्लेख नहीं कर रहे हैं कि गणना किए गए कॉलम पर सूचकांक को t1 और t2 स्कैन करने के लिए इस्तेमाल किया जा सकता है, लेकिन मैंने एक अनुक्रमणिका सूचकांक देखा स्कैन।

यहाँ छवि विवरण दर्ज करें

इसलिए मैंने इस सूचकांक के उपयोग को यह देखने के लिए मजबूर किया कि क्या यह एक बेहतर योजना है:

SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 t2.D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY t2.D
            )
FROM    dbo.T t1 WITH (INDEX (IX_T_Comp_D))
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;

जिससे यह योजना बनी

यहाँ छवि विवरण दर्ज करें

इससे पता चलता है कि कुंजी खोज का उपयोग किया जा रहा है, जिसके विवरण निम्नानुसार हैं:

यहाँ छवि विवरण दर्ज करें

अब SQL- सर्वर प्रलेखन के अनुसार:

आप एक गणना किए गए कॉलम पर एक इंडेक्स बना सकते हैं जिसे एक नियतात्मक, लेकिन अभेद्य, अभिव्यक्ति के साथ परिभाषित किया जाता है यदि कॉलम को क्रिएट टेबल या अन्य टेबल स्टेटमेंट में PERSISTED के रूप में चिह्नित किया गया है। इसका मतलब यह है कि डेटाबेस इंजन तालिका में गणना किए गए मानों को संग्रहीत करता है, और जब कोई अन्य कॉलम जिस पर गणना किए गए कॉलम को अद्यतन किया जाता है, तब उन्हें अपडेट करता है। डेटाबेस इंजन इन निरंतर मूल्यों का उपयोग करता है जब यह कॉलम पर एक इंडेक्स बनाता है, और जब किसी क्वेरी में इंडेक्स संदर्भित होता है। यह विकल्प आपको एक गणना किए गए कॉलम पर एक इंडेक्स बनाने में सक्षम बनाता है जब डेटाबेस इंजन सटीकता के साथ साबित नहीं कर सकता है कि क्या एक फ़ंक्शन जो कंप्यूटेड कॉलम एक्सप्रेशन देता है, विशेष रूप से एक CLR फ़ंक्शन जो .NET फ्रेमवर्क में बनाया गया है, दोनों नियतात्मक और सटीक है।

इसलिए, यदि डॉक्स का कहना है कि "डेटाबेस इंजन तालिका में गणना किए गए मानों को संग्रहीत करता है" , और मूल्य भी मेरे सूचकांक में संग्रहीत किया जा रहा है, तो एक कुंजी लुकअप को ए, बी और सी प्राप्त करने की आवश्यकता क्यों होती है जब उन्हें संदर्भित नहीं किया जाता है सब पर प्रश्न? मुझे लगता है कि वे COMP की गणना करने के लिए इस्तेमाल किया जा रहा है, लेकिन क्यों? इसके अलावा, क्वेरी इंडेक्स का उपयोग क्यों कर सकती है t2, पर नहीं t1?

एसक्यूएल फिडल पर क्वेरी और डीडीएल

NB मैंने SQL Server 2008 को टैग किया है क्योंकि यह वह संस्करण है जिस पर मेरी मुख्य समस्या है, लेकिन मुझे 2012 में भी यही व्यवहार मिला।

जवाबों:


20

जब वे क्वेरी में संदर्भित नहीं होते हैं तो ए, बी और सी प्राप्त करने के लिए एक कुंजी लुकअप की आवश्यकता क्यों होती है? मुझे लगता है कि वे COMP की गणना करने के लिए इस्तेमाल किया जा रहा है, लेकिन क्यों?

कॉलम A, B, and C को क्वेरी योजना में संदर्भित किया जाता है - उनका उपयोग खोजकर्ता द्वारा किया जाता है T2

इसके अलावा, क्वेरी t2 पर सूचकांक का उपयोग क्यों कर सकती है, लेकिन t1 पर नहीं?

ऑप्टिमाइज़र ने तय किया कि क्लस्टर किए गए इंडेक्स को स्कैन करना फ़िल्टर किए गए नॉनक्लेस्टेड इंडेक्स को स्कैन करने और फिर कॉलम ए, बी और सी के लिए मान प्राप्त करने के लिए लुकअप प्रदर्शन करने की तुलना में सस्ता था।

व्याख्या

असली सवाल यह है कि आशावादी को सूचकांक की तलाश के लिए ए, बी और सी को फिर से प्राप्त करने की आवश्यकता महसूस हुई। हम उम्मीद करेंगे कि यह Compएक गैर-अनुक्रमित सूचकांक स्कैन का उपयोग करके कॉलम को पढ़े , और फिर शीर्ष 1 रिकॉर्ड का पता लगाने के लिए एक ही सूचकांक (अन्य नाम T2) पर तलाश करें।

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

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

पुनर्लेखन लागू करें

यह सिर्फ इतना होता है कि यह अनियंत्रित रूप से लागू होता है तार्किक क्वेरी ट्री को एक ऐसे रूप में रखता है जो परियोजना के सामान्यीकरण के साथ अच्छी तरह से काम नहीं करता है (बाद में एक चरण जो सामान्य अभिव्यक्तियों को गणना किए गए स्तंभों के साथ, अन्य चीजों के साथ दिखता है)।

आपके मामले में, जिस तरह से क्वेरी लिखा जाता है, वह ऑप्टिमाइज़र के आंतरिक विवरणों के साथ इंटरैक्ट करता है, जैसे कि एक्सटेंडेड एक्सप्रेशन की परिभाषा, गणना किए गए कॉलम से वापस मेल नहीं खाती है, और आप एक ऐसे साधक के साथ समाप्त A, B, and Cहोते हैं, जो कंप्यूटेड कॉलम के बजाय कॉलम को संदर्भित करता है Comp। यही मूल कारण है।

वैकल्पिक हल

इस साइड-इफ़ेक्ट को हल करने के लिए एक विचार यह है कि क्वेरी को मैन्युअल रूप से लागू करें:

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
CROSS APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

दुर्भाग्य से, यह क्वेरी फ़िल्टर किए गए इंडेक्स का उपयोग नहीं करेगी क्योंकि हम या तो उम्मीद करेंगे। Dलागू अस्वीकार के अंदर स्तंभ पर असमानता परीक्षण NULLs, इसलिए स्पष्ट रूप से निरर्थक विधेय WHERE T1.D IS NOT NULLको दूर अनुकूलित किया जाता है।

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

SELECT
    T1.ID,
    T1.Comp,
    T1.D,
    CA.D2
FROM dbo.T AS T1
OUTER APPLY
(  
    SELECT TOP (1)
        D2 = T2.D
    FROM dbo.T AS T2
    WHERE
        T2.Comp = T1.Comp
        AND T2.D > T1.D
    ORDER BY
        T2.D ASC
) AS CA
WHERE
    T1.D IS NOT NULL -- DON'T CARE ABOUT INACTIVE RECORDS
ORDER BY
    T1.Comp;

अब ऑप्टिमाइज़र को खुद को फिर से लिखने के लिए उपयोग करने की आवश्यकता नहीं है (इसलिए उम्मीद के मुताबिक गणना किए गए कॉलम मिलान काम करता है) और विधेय को दूर भी अनुकूलित नहीं किया जाता है, इसलिए फ़िल्टर किए गए इंडेक्स का उपयोग डेटा एक्सेस संचालन दोनों के लिए किया जा सकता है, और Compशोधकर्ता कॉलम का उपयोग करता है दोनों तरफ:

बाहरी योजना लागू करें

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

संगणित संगणित स्तंभ

एक साइड नोट के रूप में PERSISTED, गणना किए गए कॉलम को चिह्नित करना आवश्यक नहीं है , अगर आपको CHECKबाधा में इसकी परिभाषा दोहराने में कोई दिक्कत नहीं है:

CREATE TABLE dbo.T 
(   
    ID integer IDENTITY(1, 1) NOT NULL,
    A varchar(20) NOT NULL,
    B varchar(20) NOT NULL,
    C varchar(20) NOT NULL,
    D date NULL,
    E varchar(20) NULL,
    Comp AS A + '-' + B + '-' + C,

    CONSTRAINT CK_T_Comp_NotNull
        CHECK (A + '-' + B + '-' + C IS NOT NULL),

    CONSTRAINT PK_T_ID 
        PRIMARY KEY (ID)
);

CREATE NONCLUSTERED INDEX IX_T_Comp_D
ON dbo.T (Comp, D) 
WHERE D IS NOT NULL;

गणना किए गए कॉलम को केवल PERSISTEDइस मामले में होना आवश्यक है यदि आप एक NOT NULLबाधा का उपयोग करना चाहते हैं या एक बाधा में Compसीधे स्तंभ को संदर्भित करना चाहते हैं (इसकी परिभाषा को दोहराने के बजाय) CHECK


2
+1 बीटीडब्लू मैं यह देखने के एक और मामले में आया था कि यह देखते हुए कि आपको ब्याज मिल सकता है (या नहीं)। एसक्यूएल फिडल
मार्टिन स्मिथ

@MartinSmith हाँ यह दिलचस्प है। एक और सामान्य नियम फिर से लिखना ( FOJNtoLSJNandLASJN) जिसके परिणामस्वरूप उन चीजों के रूप में काम नहीं किया जाता है जिनकी हम आशा करते हैं, और कबाड़ (बेसरॉव / चेकसम) छोड़ते हैं जो कुछ प्रकार की योजनाओं (जैसे कि शाप देने वाले) में उपयोगी है, लेकिन यहां जरूरत नहीं है।
पॉल व्हाइट GoFundMonica कहते

आह Chkचेकसम है! धन्यवाद मैं उस बारे में निश्चित नहीं था। मूल रूप से मैं सोच रहा था कि यह चेक बाधाओं के साथ कुछ करना हो सकता है।
मार्टिन स्मिथ

6

यद्यपि यह आपके परीक्षण डेटा की कृत्रिम प्रकृति के कारण थोड़ी सी सह-घटना हो सकती है, जैसा कि आपने SQL 2012 का उल्लेख किया है, मैंने एक फिर से लिखने की कोशिश की:

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;

यह आपके सूचकांक का उपयोग करते हुए और अन्य विकल्पों की तुलना में काफी कम पढ़ता है (और आपके परीक्षण डेटा के लिए समान परिणाम) के साथ एक अच्छी कम लागत वाली योजना प्राप्त करता है।

चार विकल्पों के लिए प्लान एक्सप्लोरर की लागत: मूल;  संकेत के साथ मूल;  बाहरी आवेदन और लीड

मुझे संदेह है कि आपका वास्तविक डेटा अधिक जटिल है, इसलिए कुछ ऐसे परिदृश्य हो सकते हैं जहां यह क्वेरी आपके लिए शब्दशः भिन्न व्यवहार करती है, लेकिन यह कभी-कभी दिखाता है कि नई सुविधाएँ वास्तविक अंतर ला सकती हैं।

मैंने कुछ अधिक विविध डेटा के साथ प्रयोग किया और कुछ परिदृश्यों का मिलान करने के लिए पाया और कुछ नहीं:

--Example 1: results matched
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn + b.rn, '1 Jan 2013')
FROM cte a
    CROSS JOIN cte b
WHERE a.rn % 3 = 0
 AND b.rn % 5 = 0
ORDER BY 1, 2, 3
GO


-- Original query
SELECT  t1.ID,
        t1.Comp,
        t1.D,
        D2 = (  SELECT  TOP 1 D
                FROM    dbo.T t2
                WHERE   t2.Comp = t1.Comp
                AND     t2.D > t1.D
                ORDER BY D
            )
INTO #tmp1
FROM    dbo.T t1 
WHERE   t1.D IS NOT NULL
ORDER BY t1.Comp;
GO

SELECT  ID,
        Comp,
        D,
        D2 = LEAD(D) OVER(PARTITION BY COMP ORDER BY D)
INTO #tmp2
FROM    dbo.T 
WHERE   D IS NOT NULL
ORDER BY Comp;
GO


-- Checks ...
SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1


Example 2: results did not match
TRUNCATE TABLE dbo.t

-- Generate some more interesting test data
;WITH cte AS
(
SELECT TOP 1000 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT T (A, B, C, D)
SELECT  'A' + CAST( a.rn AS VARCHAR(5) ),
        'B' + CAST( a.rn AS VARCHAR(5) ),
        'C' + CAST( a.rn AS VARCHAR(5) ),
        DATEADD(DAY, a.rn, '1 Jan 2013')
FROM cte a

-- Add some more data
INSERT dbo.T (A, B, C, D)
SELECT A, B, C, D 
FROM dbo.T
WHERE DAY(D) In ( 3, 7, 9 )


INSERT dbo.T (A, B, C, D)
SELECT A, B, C, DATEADD( day, 1, D )
FROM dbo.T
WHERE DAY(D) In ( 12, 13, 17 )


SELECT * FROM #tmp1
EXCEPT
SELECT * FROM #tmp2

SELECT * FROM #tmp2
EXCEPT
SELECT * FROM #tmp1

SELECT * FROM #tmp2
INTERSECT
SELECT * FROM #tmp1


select * from #tmp1
where comp = 'A2-B2-C2'

select * from #tmp2
where comp = 'A2-B2-C2'

1
वैसे यह सूचकांक का उपयोग करता है लेकिन केवल एक बिंदु तक। यदि compआप एक संगणित कॉलम नहीं हैं, तो आप सॉर्ट नहीं देखते हैं।
मार्टिन स्मिथ

धन्यवाद। मेरा वास्तविक परिदृश्य बहुत अधिक जटिल नहीं है और LEADसमारोह ने ठीक उसी तरह काम किया जैसा कि मैं 2012 के एक्सप्रेस के अपने स्थानीय उदाहरण पर करना चाहूंगा। दुर्भाग्य से, मेरे लिए यह मामूली असुविधा अभी तक उत्पादन सर्वरों को अपग्रेड करने के लिए एक अच्छा पर्याप्त कारण नहीं समझा गया था ...
GarethD

-1

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

जैसा कि हम क्लस्टर इंडेक्स स्कैन (t2) से देख सकते हैं, विधेय का उपयोग आवश्यक पंक्तियों को वापस करने के लिए किया जाता है (क्योंकि हालत के कारण):

यहाँ छवि विवरण दर्ज करें

जब सूचकांक को जोड़ा गया था, तो कोई बात नहीं अगर यह ऑपरेटर या नहीं के द्वारा परिभाषित किया गया था, तो निष्पादन योजना निम्नानुसार हो गई:

यहाँ छवि विवरण दर्ज करें

जैसा कि हम देख सकते हैं, Clustered Index Scan को Index Scan द्वारा प्रतिस्थापित किया जाता है। जैसा कि हमने ऊपर देखा, SQL सर्वर नेस्टेड कॉलम के स्रोत कॉलम का उपयोग नेस्टेड क्वेरी के मिलान करने के लिए करता है। क्लस्टर किए गए इंडेक्स स्कैन के दौरान यह सभी मान एक ही समय में प्राप्त किए जा सकते हैं (कोई अतिरिक्त संचालन की आवश्यकता नहीं)। जब सूचकांक को जोड़ा गया था, तो तालिका से (मुख्य चयन में) आवश्यक पंक्तियों को फ़िल्टर करना सूचकांक के अनुसार प्रदर्शन कर रहा है, लेकिन गणना किए गए कॉलम के लिए स्रोत कॉलम के मूल्यों को compअभी भी प्राप्त करने की आवश्यकता है (अंतिम ऑपरेशन नेस्ट लूप) ।

यहाँ छवि विवरण दर्ज करें

इसकी वजह से कुंजी लुकअप ऑपरेशन का उपयोग किया जाता है - गणना किए गए एक के स्रोत कॉलम का डेटा प्राप्त करने के लिए।

PS SQL सर्वर में बग की तरह दिखता है।

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