अंतराल और द्वीप: क्लाइंट समाधान बनाम टी-एसक्यूएल क्वेरी


10

क्या ग्राहक के लिए चल रहे C # समाधान की तुलना में अंतराल और द्वीपों के लिए एक टी-एसक्यूएल समाधान तेजी से चल सकता है?

विशिष्ट होने के लिए, हम कुछ परीक्षण डेटा प्रदान करते हैं:

CREATE TABLE dbo.Numbers
  (
    n INT NOT NULL
          PRIMARY KEY
  ) ; 
GO 

INSERT  INTO dbo.Numbers
        ( n )
VALUES  ( 1 ) ; 
GO 
DECLARE @i INT ; 
SET @i = 0 ; 
WHILE @i < 21 
  BEGIN 
    INSERT  INTO dbo.Numbers
            ( n 
            )
            SELECT  n + POWER(2, @i)
            FROM    dbo.Numbers ; 
    SET @i = @i + 1 ; 
  END ;  
GO

CREATE TABLE dbo.Tasks
  (
    StartedAt SMALLDATETIME NOT NULL ,
    FinishedAt SMALLDATETIME NOT NULL ,
    CONSTRAINT PK_Tasks PRIMARY KEY ( StartedAt, FinishedAt ) ,
    CONSTRAINT UNQ_Tasks UNIQUE ( FinishedAt, StartedAt )
  ) ;
GO

INSERT  INTO dbo.Tasks
        ( StartedAt ,
          FinishedAt
        )
        SELECT  DATEADD(MINUTE, n, '20100101') AS StartedAt ,
                DATEADD(MINUTE, n + 2, '20100101') AS FinishedAt
        FROM    dbo.Numbers
        WHERE   ( n < 500000
                  OR n > 500005
                )
GO

परीक्षण डेटा के इस पहले सेट में एक अंतर है:

SELECT  StartedAt ,
        FinishedAt
FROM    dbo.Tasks
WHERE   StartedAt BETWEEN DATEADD(MINUTE, 499999, '20100101')
                  AND     DATEADD(MINUTE, 500006, '20100101')

परीक्षण डेटा के दूसरे सेट में 2M -1 अंतराल होते हैं, प्रत्येक दो आसन्न अंतराल के बीच एक अंतर:

TRUNCATE TABLE dbo.Tasks;
GO

INSERT  INTO dbo.Tasks
        ( StartedAt ,
          FinishedAt
        )
        SELECT  DATEADD(MINUTE, 3*n, '20100101') AS StartedAt ,
                DATEADD(MINUTE, 3*n + 2, '20100101') AS FinishedAt
        FROM    dbo.Numbers
        WHERE   ( n < 500000
                  OR n > 500005
                )
GO

वर्तमान में मैं 2008 R2 चला रहा हूं, लेकिन 2012 के समाधान बहुत स्वागत करते हैं। मैंने उत्तर के रूप में अपना C # समाधान पोस्ट किया है।

जवाबों:


4

और 1 सेकंड का समाधान ...

;WITH cteSource(StartedAt, FinishedAt)
AS (
    SELECT      s.StartedAt,
            e.FinishedAt
    FROM        (
                SELECT  StartedAt,
                    ROW_NUMBER() OVER (ORDER BY StartedAt) AS rn
                FROM    dbo.Tasks
            ) AS s
    INNER JOIN  (
                SELECT  FinishedAt,
                    ROW_NUMBER() OVER (ORDER BY FinishedAt) + 1 AS rn
                FROM    dbo.Tasks
            ) AS e ON e.rn = s.rn
    WHERE       s.StartedAt > e.FinishedAt

    UNION ALL

    SELECT  MIN(StartedAt),
        MAX(FinishedAt)
    FROM    dbo.Tasks
), cteGrouped(theTime, grp)
AS (
    SELECT  u.theTime,
        (ROW_NUMBER() OVER (ORDER BY u.theTime) - 1) / 2
    FROM    cteSource AS s
    UNPIVOT (
            theTime
            FOR theColumn IN (s.StartedAt, s.FinishedAt)
        ) AS u
)
SELECT      MIN(theTime),
        MAX(theTime)
FROM        cteGrouped
GROUP BY    grp
ORDER BY    grp

यह आपके अन्य समाधान की तुलना में लगभग 30% तेज है। 1 अंतर: (00: 00: 12.1355011 00: 00: 11.6406581), 2M-1 अंतराल (00: 00: 12.4526817 00: 00: 11.7442217)। फिर भी यह अपने सबसे खराब मामले में क्लाइंट साइड सॉल्यूशन से लगभग 25% धीमा है, जैसा कि ट्विटर पर एडम मैकहानिक ने भविष्यवाणी की थी।
एके

4

निम्नलिखित C # कोड समस्या हल करता है:

    var connString =
        "Initial Catalog=MyDb;Data Source=MyServer;Integrated Security=SSPI;Application Name=Benchmarks;";

    var stopWatch = new Stopwatch();
    stopWatch.Start();

    using (var conn = new SqlConnection(connString))
    {
        conn.Open();
        var command = conn.CreateCommand();
        command.CommandText = "dbo.GetAllTaskEvents";
        command.CommandType = CommandType.StoredProcedure;
        var gaps = new List<string>();
        using (var dr = command.ExecuteReader())
        {
            var currentEvents = 0;
            var gapStart = new DateTime();
            var gapStarted = false;
            while (dr.Read())
            {
                var change = dr.GetInt32(1);
                if (change == -1 && currentEvents == 1)
                {
                    gapStart = dr.GetDateTime(0);
                    gapStarted = true;
                }
                else if (change == 1 && currentEvents == 0 && gapStarted)
                {
                    gaps.Add(string.Format("({0},{1})", gapStart, dr.GetDateTime(0)));
                    gapStarted = false;
                }
                currentEvents += change;
            }
        }
        File.WriteAllLines(@"C:\Temp\Gaps.txt", gaps);
    }

    stopWatch.Stop();
    System.Console.WriteLine("Elapsed: " + stopWatch.Elapsed);

यह कोड इस संग्रहीत कार्यविधि को आमंत्रित करता है:

CREATE PROCEDURE dbo.GetAllTaskEvents
AS 
  BEGIN ;
    SELECT  EventTime ,
            Change
    FROM    ( SELECT  StartedAt AS EventTime ,
                      1 AS Change
              FROM    dbo.Tasks
              UNION ALL
              SELECT  FinishedAt AS EventTime ,
                      -1 AS Change
              FROM    dbo.Tasks
            ) AS TaskEvents
    ORDER BY EventTime, Change DESC ;
  END ;
GO

यह निम्न समय में 2M अंतराल में एक अंतराल को पाता है और प्रिंट करता है, गर्म कैश:

1 gap: Elapsed: 00:00:01.4852029 00:00:01.4444307 00:00:01.4644152

यह निम्न समय में 2M अंतराल में 2M-1 अंतराल पाता है और गर्म करता है:

2M-1 gaps Elapsed: 00:00:08.8576637 00:00:08.9123053 00:00:09.0372344 00:00:08.8545477

यह एक बहुत ही सरल उपाय है - इसे विकसित करने में मुझे 10 मिनट लगे। हाल ही में एक कॉलेज के स्नातक इसके साथ आ सकते हैं। डेटाबेस की ओर, निष्पादन योजना एक तुच्छ विलय है जिसमें बहुत कम सीपीयू और मेमोरी का उपयोग किया जाता है।

संपादित करें: यथार्थवादी होने के लिए, मैं अलग-अलग बॉक्स पर क्लाइंट और सर्वर चला रहा हूं।


हां, लेकिन क्या होगा अगर मैं रिजल्ट को डेटासेट के रूप में वापस चाहता हूं, फाइल के रूप में नहीं?
पीटर लार्सन

अधिकांश अनुप्रयोग IEnumerable <SomeClassOrStruct> का उपयोग करना चाहते हैं - इस मामले में हम सिर्फ एक सूची में एक पंक्ति को जोड़ने के बजाय रिटर्न देते हैं। इस उदाहरण को छोटा रखने के लिए, मैंने कच्चे प्रदर्शन को मापने के लिए बहुत सी चीजों को नहीं हटाया है।
एके

और वह सीपीयू से मुक्त है? या यह आपके समाधान के लिए समय जोड़ता है?
पीटर लार्सन

@PeterLarsson क्या आप बेंचमार्क को बेहतर तरीका सुझा सकते हैं? क्लाइंट द्वारा डेटा की काफी धीमी खपत के लिए एक फ़ाइल की नकल करना।
एके

3

मुझे लगता है कि मैंने इस पर SQL सर्वर में अपने ज्ञान की सीमा समाप्त कर दी है ...।

एसक्यूएल सर्वर (सी # कोड क्या करता है) में एक अंतर खोजने के लिए, और आप अंतराल को शुरू करने या समाप्त करने के बारे में परवाह नहीं करते हैं (पहली शुरुआत से पहले, या अंतिम समाप्ति के बाद), फिर निम्न क्वेरी (या वेरिएंट) है सबसे तेज़ मुझे मिल सकता है:

SELECT e.FinishedAt as GapStart, s.StartedAt as GapEnd
FROM 
(
    SELECT StartedAt, ROW_NUMBER() OVER (ORDER BY StartedAt) AS rn
    FROM dbo.Tasks
) AS s
INNER JOIN  
(
    SELECT  FinishedAt, ROW_NUMBER() OVER (ORDER BY FinishedAt) + 1 AS rn
    FROM    dbo.Tasks
) AS e ON e.rn = s.rn and s.StartedAt > e.FinishedAt

हालांकि, हाथ से थोड़ा सा काम करता है कि प्रत्येक स्टार्ट-फिनिश सेट के लिए, आप स्टार्ट और फिनिश को अलग-अलग दृश्यों के रूप में मान सकते हैं, एक से फिनिश को ऑफसेट कर सकते हैं और अंतराल दिखाए जाते हैं।

उदाहरण के लिए, (S1, F1), (S2, F2), (S3, F3), और आदेश इस प्रकार लें: {S1, S2, S3, null} और {null, F1, F2, F3} फिर पंक्ति n से पंक्ति n की तुलना करें प्रत्येक सेट में, और अंतराल ऐसे हैं जहां F सेट मान S सेट मान से कम है ... मुझे लगता है कि समस्या यह है कि SQL सर्वर में दो अलग-अलग सेटों को शामिल करने या तुलना करने का कोई तरीका नहीं है, जो कि मूल्यों के क्रम पर है सेट ... इसलिए row_number फ़ंक्शन का उपयोग हमें पंक्ति संख्या के आधार पर शुद्ध रूप से मर्ज करने की अनुमति देता है ... लेकिन SQL सर्वर को यह बताने का कोई तरीका नहीं है कि ये मान अद्वितीय हैं (उन्हें किसी इंडेक्स के साथ टेबल संस्करण में सम्मिलित किए बिना) इस पर - जो अधिक समय लेता है - मैंने इसकी कोशिश की), इसलिए मुझे लगता है कि विलय में शामिल होना इष्टतम से कम है? (हालांकि यह साबित करना मुश्किल है कि यह किसी और चीज से ज्यादा तेज है)

मैं LAG / लीड कार्यों का उपयोग करके समाधान प्राप्त करने में सक्षम था:

select * from
(
    SELECT top (100) percent StartedAt, FinishedAt, LEAD(StartedAt, 1, null) OVER (Order by FinishedAt) as NextStart
    FROM dbo.Tasks
) as x
where NextStart > FinishedAt

(जिस तरह से, मैं परिणामों की गारंटी नहीं देता - यह काम करने लगता है, लेकिन मुझे लगता है कि StartedAt पर निर्भर करता है ताकि कार्य तालिका में हो ... और यह धीमा था)

राशि परिवर्तन का उपयोग:

select * from
(
    SELECT EventTime, Change, SUM(Change) OVER (ORDER BY EventTime, Change desc ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as RunTotal --, x.*
    FROM    
    ( 
        SELECT StartedAt AS EventTime, 1 AS Change
        FROM dbo.Tasks
    UNION ALL
        SELECT  FinishedAt AS EventTime, -1 AS Change
        FROM dbo.Tasks
    ) AS TaskEvents
) as x
where x.RunTotal = 0 or (x.RunTotal = 1 and x.Change = 1)
ORDER BY EventTime, Change DESC

(कोई आश्चर्य नहीं, धीमी भी)

मैंने सीएलआर एग्रीगेट फंक्शन की भी कोशिश की (योग को प्रतिस्थापित करने के लिए - यह राशि की तुलना में धीमा था और डेटा के क्रम को बनाए रखने के लिए row_number () पर निर्भर था), और CLR एक टेबल वैल्यू फंक्शन (दो रिजल्ट सेट खोलने के लिए और विशुद्ध रूप से मानों की तुलना करने के लिए) अनुक्रम पर) ... और यह बहुत धीमा था। मैंने कई बार SQL, और CLR सीमाओं पर अपना सिर धमाका किया, कई अन्य तरीके आजमाए ...

और किस लिए?

एक ही मशीन पर चल रहा है, और एक सी (# मूल कोड # कोड) के अनुसार सी # डेटा, और एसक्यूएल डेटा दोनों को थूक रहा है, समय लगभग समान हैं .... 1 अंतर डेटा (सी # आमतौर पर तेजी से लगभग 2 सेकंड) ), मल्टी-गैप डेटा सेट के लिए 8-10 सेकंड (एसक्यूएल आमतौर पर तेज)।

नोट : समय की तुलना के लिए SQL सर्वर डेवलपमेंट एनवायरनमेंट का उपयोग न करें, क्योंकि यह प्रदर्शित होता है ग्रिड में समय लगता है। SQL 2012, VS2010, .net 4.0 क्लाइंट प्रोफाइल के साथ परीक्षण किया गया

मैं यह बताऊंगा कि दोनों समाधान SQL सर्वर पर डेटा की समान छँटाई करते हैं, इसलिए लाने के लिए सर्वर लोड समान होगा, जो भी समाधान आप उपयोग करते हैं, केवल ग्राहक पर प्रसंस्करण होने का अंतर (सर्वर के बजाय) , और नेटवर्क पर स्थानांतरण।

मुझे नहीं पता कि विभिन्न स्टाफ सदस्यों द्वारा विभाजन करते समय क्या अंतर हो सकता है, या जब आपको गैप सूचना के साथ अतिरिक्त डेटा की आवश्यकता हो सकती है (हालांकि मैं स्टाफ आईडी के अलावा किसी और के बारे में नहीं सोच सकता हूं), या निश्चित रूप से SQL सर्वर और क्लाइंट मशीन (या एक धीमी क्लाइंट) के बीच एक धीमा डेटा कनेक्शन है ... न ही मैंने कई उपयोगकर्ताओं के लिए लॉक-टाइम, या विवाद के मुद्दों, या CPU / NETWORK मुद्दों की तुलना की है ... इसलिए मैंने पता नहीं कौन सा इस मामले में अड़चन होने की अधिक संभावना है।

मुझे क्या पता है, हां, SQL सर्वर इस तरह की सेट तुलनाओं में अच्छा नहीं है, और यदि आप क्वेरी नहीं लिखते हैं तो आप इसके लिए भुगतान करेंगे।

क्या सी # संस्करण लिखना आसान या कठिन है? मुझे पूरा यकीन नहीं है, चेंज +/- 1, टोटल सॉल्यूशन रन करना पूरी तरह से सहज भी नहीं है, और मैं लेकिन यह पहला सॉल्यूशन नहीं है कि एक औसत ग्रेजुएट आएगा ... एक बार किया तो यह कॉपी करना काफी आसान है, लेकिन यह पहली जगह में लिखने के लिए अंतर्दृष्टि लेता है ... वही SQL संस्करण के लिए कहा जा सकता है। कौन सा कठिन है? डेटा को दुष्ट करने के लिए कौन सा अधिक मजबूत है? समानांतर संचालन के लिए कौन सी क्षमता अधिक है? क्या यह वास्तव में मायने रखता है जब प्रोग्रामिंग प्रयास की तुलना में अंतर इतना छोटा है?

एक आखिरी नोट; डेटा पर एक अस्थिर बाधा है - StartAAt समाप्तएट से कम होना चाहिए, या आपको खराब परिणाम मिलेगा।


3

यहां एक समाधान है जो 4 सेकंड में चलता है।

WITH cteRaw(ts, type, e, s)
AS (
    SELECT  StartedAt,
        1 AS type,
        NULL,
        ROW_NUMBER() OVER (ORDER BY StartedAt)
    FROM    dbo.Tasks

    UNION ALL

    SELECT  FinishedAt,
        -1 AS type, 
        ROW_NUMBER() OVER (ORDER BY FinishedAt),
        NULL
    FROM    dbo.Tasks
), cteCombined(ts, e, s, se)
AS (
    SELECT  ts,
        e,
        s,
        ROW_NUMBER() OVER (ORDER BY ts, type DESC)
    FROM    cteRaw
), cteFiltered(ts, grpnum)
AS (
    SELECT  ts, 
        (ROW_NUMBER() OVER (ORDER BY ts) - 1) / 2 AS grpnum
    FROM    cteCombined
    WHERE   COALESCE(s + s - se - 1, se - e - e) = 0
)
SELECT      MIN(ts) AS starttime,
        MAX(ts) AS endtime
FROM        cteFiltered
GROUP BY    grpnum;

पीटर, एक अंतर के साथ एक डेटा सेट पर यह 10 गुना से अधिक धीमा है: (00: 00: 18.1016745 - 00: 00: 17.8190959) 2M-1 अंतराल के साथ डेटा पर, यह 2 गुना धीमा (00:00) है : 17.2409640 00: 00: 17.6068879)
AK
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.