मैं विंडो क्वेरी कैसे लिख सकता हूं, जो असतत बाल्टियां बनाने के लिए एक कॉलम का उपयोग करता है?


11

मेरे पास एक तालिका है जिसमें दशमलव मानों का एक कॉलम शामिल है, जैसे कि:

id value size
-- ----- ----
 1   100  .02
 2    99  .38
 3    98  .13
 4    97  .35
 5    96  .15
 6    95  .57
 7    94  .25
 8    93  .15

मुझे जो पूरा करने की आवश्यकता है, उसका वर्णन करना थोड़ा मुश्किल है, इसलिए कृपया मेरे साथ रहें। मैं जो करने की कोशिश कर रहा हूं वह sizeस्तंभ का एक समग्र मूल्य बनाता है जो 1 बार बढ़ाता है जब पूर्ववर्ती पंक्तियां 1 तक होती हैं, जब अवरोही क्रम में value। परिणाम कुछ इस तरह दिखेगा:

id value size bucket
-- ----- ---- ------
 1   100  .02      1
 2    99  .38      1
 3    98  .13      1
 4    97  .35      1
 5    96  .15      2
 6    95  .57      2
 7    94  .25      2
 8    93  .15      3

मेरा भोला पहला प्रयास एक चालू रखने SUMऔर फिर CEILINGउस मूल्य को रखने का था , हालांकि यह उस मामले को नहीं संभालता है जहां कुछ रिकॉर्ड sizeदो अलग बाल्टी के कुल में योगदान कर रहे हैं। नीचे दिए गए उदाहरण से यह स्पष्ट हो सकता है:

id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
 1   100  .02       .02            1          .02      1
 2    99  .38       .40            1          .40      1
 3    98  .13       .53            1          .53      1
 4    97  .35       .88            1          .88      1
 5    96  .15      1.03            2          .15      2
 6    95  .57      1.60            2          .72      2
 7    94  .25      1.85            2          .97      2
 8    93  .15      2.00            2          .15      3

जैसा कि आप देख सकते हैं, अगर मैं रिकॉर्ड CEILINGऑन crude_sum# 8 का उपयोग करने के लिए बाल्टी के लिए सौंपा जाएगा 2. यह sizeरिकॉर्ड # 5 और # 8 दो बाल्टी में विभाजित होने के कारण होता है । इसके बजाय, आदर्श समाधान यह है कि हर बार 1 तक पहुंचने वाले योग को रीसेट किया जाए, जो तब bucketकॉलम को बढ़ाता है और वर्तमान रिकॉर्ड SUMके sizeमूल्य पर शुरू होने वाला एक नया ऑपरेशन शुरू करता है । क्योंकि रिकॉर्ड्स का क्रम इस ऑपरेशन के लिए महत्वपूर्ण है, मैंने valueकॉलम को शामिल किया है, जिसका उद्देश्य अवरोही क्रम में सॉर्ट किया जाना है।

मेरे शुरुआती प्रयासों में डेटा पर कई बार पास करना शामिल है, एक बार SUMऑपरेशन करने के लिए , एक बार उससे अधिक के लिए CEILING, आदि। यहां एक उदाहरण है कि मैंने crude_sumकॉलम बनाने के लिए क्या किया था :

SELECT
  id,
  value,
  size,
  (SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
  table t1

जिसका उपयोग UPDATEबाद में काम करने के लिए एक तालिका में मूल्य डालने के लिए एक ऑपरेशन में किया गया था ।

संपादित करें: मैं इसे समझाने के लिए एक और छुरा लेना चाहता हूं, इसलिए यहां जाता है। कल्पना कीजिए कि प्रत्येक रिकॉर्ड एक भौतिक वस्तु है। उस आइटम का मूल्य उसके साथ जुड़ा हुआ है, और भौतिक आकार एक से कम है। मेरे पास बिलकुल 1 की वॉल्यूम क्षमता वाली बाल्टियों की एक श्रृंखला है, और मुझे यह निर्धारित करने की आवश्यकता है कि इनमें से कितनी बाल्टियों की मुझे आवश्यकता होगी और कौन सी बकेट प्रत्येक आइटम के मूल्य के अनुसार जाती है, उच्चतम से सबसे कम क्रमबद्ध।

एक भौतिक वस्तु एक साथ दो स्थानों पर मौजूद नहीं हो सकती है, इसलिए इसे एक बाल्टी या दूसरे में होना चाहिए। यही कारण है कि मैं एक रनिंग टोटल + CEILINGसॉल्यूशन नहीं कर सकता , क्योंकि इससे रिकॉर्ड दो बाल्टी में अपने आकार में योगदान कर सकते हैं।


आपको अपनी एसक्यूएल को यह स्पष्ट करने के लिए जोड़ना चाहिए कि आपके प्रारंभिक प्रयास में क्या शामिल था।
mdahlman

क्या आप अपने द्वारा गणना की जा रही बाल्टी के अनुसार डेटा एकत्र करने जा रहे हैं, या बाल्टी नंबर अंतिम उत्तर है जिसकी आप तलाश कर रहे हैं?
जॉन सिगेल

2
एसीके। मैं शायद एक क्लाइंट-साइड ऐप के साथ जाऊंगा क्योंकि यह एक कर्सर लूप के विपरीत रिकॉर्ड के बेहतर स्ट्रीमिंग-इन का समर्थन करेगा जो एक समय में एक पंक्ति प्राप्त करता है। मुझे लगता है कि जब तक सभी अपडेट बैचों में किए जाते हैं, तब तक यह उचित प्रदर्शन करना चाहिए।
जॉन सिगेल

1
जैसा कि दूसरों ने पहले ही उल्लेख किया है, distinct_countचीजों को जटिल करने पर बाल्टी लगाने की आवश्यकता । इस तरह की खिड़की के काम के लिए एरॉन बर्ट्रेंड के पास SQL सर्वर पर आपके विकल्पों का एक बड़ा सारांश है । मैंने गणना करने के लिए "quirky अपडेट" विधि का उपयोग किया है distinct_sum, जिसे आप SQL फिडल पर यहां देख सकते हैं , लेकिन यह अविश्वसनीय है।
निक चामास

1
@JonSeigel हमें ध्यान देना चाहिए कि कम से कम बाल्टी में एक्स आइटम रखने की समस्या को SQL भाषा की पंक्ति एल्गोरिदम द्वारा पंक्ति का उपयोग करके कुशलता से हल नहीं किया जा सकता है। जैसे कि आकार का आइटम 0.7; 0.8; 0.3 में 2 बाल्टी की आवश्यकता होगी, लेकिन अगर आईडी के आधार पर उन्हें 3 बाल्टी की आवश्यकता होगी।
स्टोलग

जवाबों:


9

मुझे यकीन नहीं है कि आप किस प्रकार के प्रदर्शन की तलाश कर रहे हैं, लेकिन अगर सीएलआर या बाहरी ऐप एक विकल्प नहीं है, तो एक कर्सर वह सब छोड़ दिया जाता है। अपने वृद्ध लैपटॉप पर मैं निम्नलिखित समाधान का उपयोग करके लगभग 100 सेकंड में 1,000,000 पंक्तियों के माध्यम से प्राप्त करता हूं। इसके बारे में अच्छी बात यह है कि यह रैखिक रूप से तराजू है, इसलिए मैं पूरी चीज के माध्यम से चलाने के लिए लगभग 20 मिनट देखूंगा। एक सभ्य सर्वर के साथ आप तेज़ होंगे, लेकिन परिमाण का क्रम नहीं, इसलिए इसे पूरा करने में कई मिनट लगेंगे। यदि यह एक बंद प्रक्रिया है, तो आप संभवतः सुस्ती का जोखिम उठा सकते हैं। यदि आपको इसे रिपोर्ट के रूप में या नियमित रूप से चलाने की आवश्यकता है, तो हो सकता है कि आप मानों को उसी तालिका में संग्रहीत करना चाहें जो उन्हें नई पंक्तियों के रूप में अपडेट करती है, जैसे कि एक ट्रिगर में।

वैसे भी, यहाँ कोड है:

IF OBJECT_ID('dbo.MyTable') IS NOT NULL DROP TABLE dbo.MyTable;

CREATE TABLE dbo.MyTable(
 Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
 v NUMERIC(5,3) DEFAULT ABS(CHECKSUM(NEWID())%100)/100.0
);


MERGE dbo.MyTable T
USING (SELECT TOP(1000000) 1 X FROM sys.system_internals_partition_columns A,sys.system_internals_partition_columns B,sys.system_internals_partition_columns C,sys.system_internals_partition_columns D)X
ON(1=0)
WHEN NOT MATCHED THEN
INSERT DEFAULT VALUES;

--SELECT * FROM dbo.MyTable

DECLARE @st DATETIME2 = SYSUTCDATETIME();
DECLARE cur CURSOR FAST_FORWARD FOR
  SELECT Id,v FROM dbo.MyTable
  ORDER BY Id;

DECLARE @id INT;
DECLARE @v NUMERIC(5,3);
DECLARE @running_total NUMERIC(6,3) = 0;
DECLARE @bucket INT = 1;

CREATE TABLE #t(
 id INT PRIMARY KEY CLUSTERED,
 v NUMERIC(5,3),
 bucket INT,
 running_total NUMERIC(6,3)
);

OPEN cur;
WHILE(1=1)
BEGIN
  FETCH NEXT FROM cur INTO @id,@v;
  IF(@@FETCH_STATUS <> 0) BREAK;
  IF(@running_total + @v > 1)
  BEGIN
    SET @running_total = 0;
    SET @bucket += 1;
  END;
  SET @running_total += @v;
  INSERT INTO #t(id,v,bucket,running_total)
  VALUES(@id,@v,@bucket, @running_total);
END;
CLOSE cur;
DEALLOCATE cur;
SELECT DATEDIFF(SECOND,@st,SYSUTCDATETIME());
SELECT * FROM #t;

GO 
DROP TABLE #t;

यह ड्रॉप करता है और तालिका MyTable को फिर से बनाता है, इसे 1000000 पंक्तियों के साथ भरता है और फिर काम पर जाता है।

गणना चलाते समय कर्सर प्रत्येक पंक्ति को एक अस्थायी तालिका में कॉपी करता है। अंत में सेलेक्टेड परिकलित परिणाम देता है। यदि आप डेटा को चारों ओर से कॉपी नहीं करते हैं, तो आप थोड़ा तेज़ हो सकते हैं, लेकिन इसके बजाय इन-प्लेस अपडेट करें।

यदि आपके पास SQL ​​2012 में अपग्रेड करने का विकल्प है तो आप नई विंडो-स्पूल सपोर्टेड मूविंग विंडो एग्रीगेट्स को देख सकते हैं, जिससे आपको बेहतर प्रदर्शन मिल सकता है।

एक तरफ ध्यान दें, यदि आपके पास अनुमति_सेट = सुरक्षित के साथ एक असेंबली स्थापित है, तो आप असेंबली के साथ मानक टी-एसक्यूएल के साथ सर्वर पर अधिक खराब सामान कर सकते हैं, इसलिए मैं उस बाधा को दूर करने पर काम करता रहूंगा - आपके पास एक अच्छा उपयोग है यहां मामला जहां सीएलआर वास्तव में आपकी मदद करेगा।


मैंने इसे स्वीकार कर लिया कि इसे लागू करना कितना आसान था, और बाद में जरूरत पड़ने पर मैं इसे कितनी आसानी से बदल और डिबग कर सकता हूं। @ NickChammas का उत्तर भी सही है और शायद अधिक कुशलता से चलता है, इसलिए मुझे लगता है कि यह किसी और के लिए समान मुद्दे के खिलाफ आने वाली वरीयता का मामला है।
ज़िक्स

9

SQL Server 2012 में नए विंडोिंग फ़ंक्शंस को अनुपस्थित करें, पुनरावर्ती CTE के उपयोग से जटिल विंडोिंग को पूरा किया जा सकता है। मुझे आश्चर्य है कि यह लाखों पंक्तियों के खिलाफ कितना अच्छा प्रदर्शन करेगा।

निम्न समाधान आपके द्वारा वर्णित सभी मामलों को कवर करता है। आप इसे यहां SQL Fiddle पर कार्रवाई में देख सकते हैं ।

-- schema setup
CREATE TABLE raw_data (
    id    INT PRIMARY KEY
  , value INT NOT NULL
  , size  DECIMAL(8,2) NOT NULL
);

INSERT INTO raw_data 
    (id, value, size)
VALUES 
   ( 1,   100,  .02) -- new bucket here
 , ( 2,    99,  .99) -- and here
 , ( 3,    98,  .99) -- and here
 , ( 4,    97,  .03)
 , ( 5,    97,  .04)
 , ( 6,    97,  .05)
 , ( 7,    97,  .40)
 , ( 8,    96,  .70) -- and here
;

अब गहरी सांस लें। यहाँ दो प्रमुख CTE हैं, प्रत्येक एक संक्षिप्त टिप्पणी से पहले थे। बाकी हम "क्लीनअप" सीटीई हैं, उदाहरण के लिए, हमने उन्हें रैंक करने के बाद सही पंक्तियों को खींचने के लिए।

-- calculate the distinct sizes recursively
WITH distinct_size AS (
  SELECT
      id
    , size
    , 0 as level
  FROM raw_data

  UNION ALL

  SELECT 
      base.id
    , CAST(base.size + tower.size AS DECIMAL(8,2)) AS distinct_size
    , tower.level + 1 as level
  FROM 
                raw_data AS base
    INNER JOIN  distinct_size AS tower
      ON base.id = tower.id + 1
  WHERE base.size + tower.size <= 1
)
, ranked_sum AS (
  SELECT 
      id
    , size AS distinct_size
    , level
    , RANK() OVER (PARTITION BY id ORDER BY level DESC) as rank
  FROM distinct_size  
)
, top_level_sum AS (
  SELECT
      id
    , distinct_size
    , level
    , rank
  FROM ranked_sum
  WHERE rank = 1
)
-- every level reset to 0 means we started a new bucket
, bucket AS (
  SELECT
      base.id
    , COUNT(base.id) AS bucket
  FROM 
               top_level_sum base
    INNER JOIN top_level_sum tower
      ON base.id >= tower.id
  WHERE tower.level = 0
  GROUP BY base.id
)
-- join the bucket info back to the original data set
SELECT
    rd.id
  , rd.value
  , rd.size
  , tls.distinct_size
  , b.bucket
FROM 
             raw_data rd
  INNER JOIN top_level_sum tls
    ON rd.id = tls.id
  INNER JOIN bucket   b
    ON rd.id = b.id
ORDER BY
  rd.id
;

यह समाधान मानता है कि idएक अंतरहीन अनुक्रम है। यदि नहीं, तो आपको शुरुआत में एक अतिरिक्त CTE जोड़कर अपना अंतर रहित अनुक्रम उत्पन्न करना होगा ROW_NUMBER()जो वांछित क्रम (उदाहरण के अनुसार) के साथ पंक्तियों को संख्या देता है ROW_NUMBER() OVER (ORDER BY value DESC)

फैंकली, यह काफी वर्बोज़ है।


1
यह समाधान उस मामले को संबोधित नहीं करता है जहां एक पंक्ति कई बाल्टी में अपने आकार का योगदान दे सकती है। एक रोलिंग राशि काफी आसान है, लेकिन मुझे प्रत्येक बार पहुंचने पर इसे रीसेट करने के लिए उस राशि की आवश्यकता है। 1. मेरे प्रश्न में अंतिम उदाहरण तालिका देखें और मेरे संबंधित कॉलम की तुलना crude_sumकरें distinct_sumऔर bucketदेखें कि मेरा क्या मतलब है।
Zikes

2
@Zikes - मैंने इस मामले को अपने अद्यतन समाधान के साथ संबोधित किया है।
निक चामास

ऐसा लग रहा है कि यह अब काम करना चाहिए। मैं इसका परीक्षण करने के लिए इसे अपने डेटाबेस में एकीकृत करने पर काम करूँगा।
ज़िक्स

@ बाइक - बस जिज्ञासु, यहाँ पोस्ट किए गए विभिन्न समाधान आपके बड़े डेटा सेट के खिलाफ कैसे प्रदर्शन करते हैं? मैं अनुमान लगा रहा हूं कि एंड्री सबसे तेज है।
निक चामास

5

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

with bar as (
select
  id
  ,value
  ,size
  from foo
union all
select
  f.id
  ,value = null
  ,size = 1 - sum(f2.size) % 1
  from foo f
  inner join foo f2
    on f2.id < f.id
  group by f.id
    ,f.value
    ,f.size
  having cast(sum(f2.size) as int) <> cast(sum(f2.size) + f.size as int)
)
select
  f.id
  ,f.value
  ,f.size
  ,bucket = cast(sum(b.size) as int) + 1
  from foo f
  inner join bar b
    on b.id <= f.id
  group by f.id
    ,f.value
    ,f.size

http://sqlfiddle.com/#!3/72ad4/14/0


1
+ 1 मुझे लगता है कि यदि उपयुक्त अनुक्रमणिकाएं हैं तो यह क्षमता है।
जॉन सिगेल

3

निम्नलिखित एक और पुनरावर्ती CTE समाधान है, हालांकि मैं कहूंगा कि यह निक के सुझाव से अधिक सीधा है । यह वास्तव में @ सेबस्टियन के कर्सर के करीब है , केवल मैंने टोटल चलाने के बजाय अंतर का उपयोग किया। (पहले तो मैंने यह भी सोचा था कि @ निक का उत्तर यहाँ जो मैं सुझा रहा हूँ, उसकी तर्ज पर ही होगा, और यह सीखने के बाद कि वास्तव में उसकी एक बहुत ही अलग क्वेरी थी, जिसे मैंने अपना प्रस्ताव देने का फैसला किया था।)

WITH rec AS (
  SELECT TOP 1
    id,
    value,
    size,
    bucket        = 1,
    room_left     = CAST(1.0 - size AS decimal(5,2))
  FROM atable
  ORDER BY value DESC
  UNION ALL
  SELECT
    t.id,
    t.value,
    t.size,
    bucket        = r.bucket + x.is_new_bucket,
    room_left     = CAST(CASE x.is_new_bucket WHEN 1 THEN 1.0 ELSE r.room_left END - t.size AS decimal(5,2))
  FROM atable t
  INNER JOIN rec r ON r.value = t.value + 1
  CROSS APPLY (
    SELECT CAST(CASE WHEN t.size > r.room_left THEN 1 ELSE 0 END AS bit)
  ) x (is_new_bucket)
)
SELECT
  id,
  value,
  size,
  bucket
FROM rec
ORDER BY value DESC
;

नोट: यह क्वेरी मानती है कि valueकॉलम में बिना अंतराल के अद्वितीय मूल्य हैं। यदि ऐसा नहीं है, तो आपको एंकर के साथ पुनरावर्ती भाग में शामिल होने के valueबजाय पुनरावर्ती CTE में अवरोही क्रम के आधार पर एक गणना रैंकिंग कॉलम शुरू करना होगा value

इस क्वेरी के लिए एक SQL फिडेल डेमो यहां पाया जा सकता है


यह मैंने जो लिखा है, उससे बहुत कम है। अच्छा काम। क्या कोई कारण है कि आप गिनती के बजाय बाल्टी में बचे कमरे को गिनते हैं?
निक चामास

हां, यह निश्चित नहीं है कि अगर यह उस संस्करण के लिए बहुत मायने रखता है जो मैंने यहां पोस्ट किया था, हालांकि। वैसे भी, कारण यह है कि इसे और अधिक एक एकल मूल्य (के साथ एक एकल मूल्य की तुलना करने के लिए प्राकृतिक / आसान लग रहा था था sizeके साथ room_leftके रूप में (एक अभिव्यक्ति के साथ एक एकल मूल्य की तुलना के खिलाफ) 1के साथ running_size+ size)। मैंने is_new_bucketपहले एक ध्वज का उपयोग नहीं किया , लेकिन CASE WHEN t.size > r.room_left ...इसके बजाय कई ("कई" क्योंकि मैं भी कुल आकार की गणना कर रहा था (और वापस लौट रहा था), लेकिन फिर सादगी के लिए इसके खिलाफ सोचा), इसलिए मुझे लगा कि यह अधिक सुरुचिपूर्ण होगा उस तरफ।
एंड्री एम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.