गिनती के साथ कुल चल रहा है?


34

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

sum(count (distinct (customers))) 

कहो कि क्या मैं अकेले ही दौड़ गया, तो परिणाम होगा:

Day | CountCustomers
----------------------
5/1  |      1
5/2  |      0
5/3  |      5

मुझे होने के योग के साथ आउटपुट चाहिए:

Day | RunningTotalCustomers
----------------------
5/1  |      1
5/2  |      1
5/3  |      6

मैंने coalesceविधि का उपयोग करने से पहले कुल योग किया है , लेकिन एक गिनती के साथ कभी नहीं। मुझे यकीन नहीं है कि अब यह कैसे करना है कि मेरे पास गिनती है।


2
SQL सर्वर का कौन सा संस्करण कृपया? क्या आप डेटा के दायरे को साझा कर सकते हैं - क्या हम 1000 पंक्तियों, एक मिलियन, एक अरब के बारे में बात कर रहे हैं? क्या यह वास्तव में सिर्फ ये दो स्तंभ हैं, या आपने हमारे लिए स्कीमा को सरल बनाया है? अंत में, Dayएक कुंजी है, और क्या मूल्य सन्निहित हैं?
हारून बर्ट्रेंड

मैंने कुल चलने के बारे में एक व्यापक ब्लॉग बनाया (क्वर्की अपडेट बनाम हाइब्रिड रिकर्सिव सीटीई बनाम कर्सर): ienablemuch.com/2012/05/… मैंने शुद्ध रन-आधारित दृष्टिकोण का उपयोग करने वाले रनिंग कुल को शामिल नहीं किया, प्रदर्शन कुछ भी नहीं है वांछित: sqlblog.com/blogs/adam_machanic/archive/2006/07/12/…
माइकल

जवाबों:


53

यहां कुछ तरीके दिए गए हैं जिनकी आप तुलना कर सकते हैं। पहले कुछ डमी डेटा के साथ एक टेबल सेट करें। मैं इसे sys.all_columns से यादृच्छिक डेटा के एक समूह के साथ आबाद कर रहा हूं। ठीक है, यह एक तरह का यादृच्छिक है - मैं यह सुनिश्चित कर रहा हूं कि तिथियां सन्निहित हैं (जो वास्तव में केवल एक उत्तर के लिए महत्वपूर्ण है)।

CREATE TABLE dbo.Hits(Day SMALLDATETIME, CustomerID INT);

CREATE CLUSTERED INDEX x ON dbo.Hits([Day]);

INSERT dbo.Hits SELECT TOP (5000) DATEADD(DAY, r, '20120501'),
  COALESCE(ASCII(SUBSTRING(name, s, 1)), 86)
FROM (SELECT name, r = ROW_NUMBER() OVER (ORDER BY name)/10,
       s = CONVERT(INT, RIGHT(CONVERT(VARCHAR(20), [object_id]), 1))
FROM sys.all_columns) AS x;

SELECT 
  Earliest_Day   = MIN([Day]), 
  Latest_Day     = MAX([Day]), 
  Unique_Days    = DATEDIFF(DAY, MIN([Day]), MAX([Day])) + 1, 
  Total_Rows     = COUNT(*)
FROM dbo.Hits;

परिणाम:

Earliest_Day         Latest_Day           Unique_Days  Total_Days
-------------------  -------------------  -----------  ----------
2012-05-01 00:00:00  2013-09-13 00:00:00  501          5000

डेटा इस तरह दिखता है (5000 पंक्तियाँ) - लेकिन आपके सिस्टम पर संस्करण और बिल्ड # के आधार पर थोड़ा अलग दिखेगा:

Day                  CustomerID
-------------------  ---
2012-05-01 00:00:00  95
2012-05-01 00:00:00  97
2012-05-01 00:00:00  97
2012-05-01 00:00:00  117
2012-05-01 00:00:00  100
...
2012-05-02 00:00:00  110
2012-05-02 00:00:00  110
2012-05-02 00:00:00  95
...

और चल रहे योग परिणाम इस तरह दिखना चाहिए (501 पंक्तियाँ):

Day                  c   rt
-------------------  --  --
2012-05-01 00:00:00  6   6
2012-05-02 00:00:00  5   11
2012-05-03 00:00:00  4   15
2012-05-04 00:00:00  7   22
2012-05-05 00:00:00  6   28
...

इसलिए मैं जिन तरीकों की तुलना करने जा रहा हूं वे हैं:

  • "आत्म-जुड़ाव" - सेट-आधारित शुद्ध दृष्टिकोण
  • "तारीखों के साथ पुनरावर्ती सीटीई" - यह सन्निहित तिथियों (कोई अंतराल) पर निर्भर करता है
  • "पुनरावर्ती CTE को row_number के साथ" - ऊपर लेकिन धीमे के समान, ROW_NUMBER पर निर्भर
  • "रिकेटीव CTE #temp टेबल के साथ" - मिकेल के सुझाव के अनुसार चोरी हो गया
  • "quirky अद्यतन", जो कि असमर्थित और आशाजनक परिभाषित व्यवहार नहीं करते हुए, काफी लोकप्रिय लगता है
  • "कर्सर"
  • नई विंडोिंग कार्यक्षमता का उपयोग करते हुए SQL सर्वर 2012

आत्म में शामिल होने के

यह वह तरीका है जब लोग आपको ऐसा करने के लिए कहेंगे जब वे आपको कर्सर से दूर रहने की चेतावनी दे रहे हैं, क्योंकि "सेट-आधारित हमेशा तेज होता है।" हाल के कुछ प्रयोगों में मैंने पाया है कि कर्सर इस समाधान को बाहर कर देता है।

;WITH g AS 
(
  SELECT [Day], c = COUNT(DISTINCT CustomerID) 
    FROM dbo.Hits
    GROUP BY [Day]
)
SELECT g.[Day], g.c, rt = SUM(g2.c)
  FROM g INNER JOIN g AS g2
  ON g.[Day] >= g2.[Day]
GROUP BY g.[Day], g.c
ORDER BY g.[Day];

तारीखों के साथ पुनरावर्ती सीटी

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

;WITH g AS 
(
  SELECT [Day], c = COUNT(DISTINCT CustomerID) 
    FROM dbo.Hits
    GROUP BY [Day]
), x AS
(
    SELECT [Day], c, rt = c
        FROM g
        WHERE [Day] = '20120501'
    UNION ALL
    SELECT g.[Day], g.c, x.rt + g.c
        FROM x INNER JOIN g
        ON g.[Day] = DATEADD(DAY, 1, x.[Day])
)
SELECT [Day], c, rt
    FROM x
    ORDER BY [Day]
    OPTION (MAXRECURSION 10000);

row_number के साथ पुनरावर्ती cte

रो_नंबर गणना यहां थोड़ी महंगी है। फिर से यह 10000 के पुनरावृत्ति के अधिकतम स्तर का समर्थन करता है, लेकिन आपको एंकर को असाइन करने की आवश्यकता नहीं है।

;WITH g AS 
(
  SELECT [Day], rn = ROW_NUMBER() OVER (ORDER BY DAY), 
    c = COUNT(DISTINCT CustomerID) 
    FROM dbo.Hits
    GROUP BY [Day]
), x AS
(
    SELECT [Day], rn, c, rt = c
        FROM g
        WHERE rn = 1
    UNION ALL
    SELECT g.[Day], g.rn, g.c, x.rt + g.c
        FROM x INNER JOIN g
        ON g.rn = x.rn + 1
)
SELECT [Day], c, rt
    FROM x
    ORDER BY [Day]
    OPTION (MAXRECURSION 10000);

अस्थायी तालिका के साथ पुनरावर्ती सीटी

मिकेल के जवाब से चोरी, जैसा कि सुझाव दिया गया है, परीक्षणों में इसे शामिल करने के लिए।

CREATE TABLE #Hits
(
  rn INT PRIMARY KEY,
  c INT,
  [Day] SMALLDATETIME
);

INSERT INTO #Hits (rn, c, Day)
SELECT ROW_NUMBER() OVER (ORDER BY DAY),
       COUNT(DISTINCT CustomerID),
       [Day]
FROM dbo.Hits
GROUP BY [Day];

WITH x AS
(
    SELECT [Day], rn, c, rt = c
        FROM #Hits as c
        WHERE rn = 1
    UNION ALL
    SELECT g.[Day], g.rn, g.c, x.rt + g.c
        FROM x INNER JOIN #Hits as g
        ON g.rn = x.rn + 1
)
SELECT [Day], c, rt
    FROM x
    ORDER BY [Day]
    OPTION (MAXRECURSION 10000);

DROP TABLE #Hits;

विचित्र अद्यतन

फिर मैं केवल पूर्णता के लिए इसमें शामिल हूं; मैं व्यक्तिगत रूप से इस समाधान पर भरोसा नहीं करेगा, जैसा कि मैंने एक अन्य उत्तर पर उल्लेख किया है, यह विधि बिल्कुल भी काम करने की गारंटी नहीं है, और SQL सर्वर के भविष्य के संस्करण में पूरी तरह से टूट सकती है। (मैं अनुक्रमणिका पसंद के लिए एक संकेत का उपयोग करते हुए, मुझे जो आदेश चाहिए, उसका पालन करने के लिए एसक्यूएल सर्वर का उपयोग करने की पूरी कोशिश कर रहा हूं।)

CREATE TABLE #x([Day] SMALLDATETIME, c INT, rt INT);
CREATE UNIQUE CLUSTERED INDEX x ON #x([Day]);

INSERT #x([Day], c) 
    SELECT [Day], c = COUNT(DISTINCT CustomerID) 
    FROM dbo.Hits
    GROUP BY [Day]
    ORDER BY [Day];

DECLARE @rt1 INT;
SET @rt1 = 0;

UPDATE #x
SET @rt1 = rt = @rt1 + c
FROM #x WITH (INDEX = x);

SELECT [Day], c, rt FROM #x ORDER BY [Day];

DROP TABLE #x;

कर्सर

"खबरदार, यहाँ पर शाप देने वाले हो! कर्सर दुष्ट हैं! आपको हर कीमत पर अभिशाप से बचना चाहिए!" नहीं, यह मैं बात नहीं कर रहा हूँ, यह सिर्फ सामान है जो मैंने बहुत सुना है। आम राय के विपरीत, कुछ मामले ऐसे होते हैं, जहां शाप देने वाले उचित होते हैं।

CREATE TABLE #x2([Day] SMALLDATETIME, c INT, rt INT);
CREATE UNIQUE CLUSTERED INDEX x ON #x2([Day]);

INSERT #x2([Day], c) 
    SELECT [Day], COUNT(DISTINCT CustomerID) 
    FROM dbo.Hits
    GROUP BY [Day]
    ORDER BY [Day];

DECLARE @rt2 INT, @d SMALLDATETIME, @c INT;
SET @rt2 = 0;

DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
  FOR SELECT [Day], c FROM #x2 ORDER BY [Day];

OPEN c;

FETCH NEXT FROM c INTO @d, @c;

WHILE @@FETCH_STATUS = 0
BEGIN
  SET @rt2 = @rt2 + @c;
  UPDATE #x2 SET rt = @rt2 WHERE [Day] = @d;
  FETCH NEXT FROM c INTO @d, @c;
END

SELECT [Day], c, rt FROM #x2 ORDER BY [Day];

DROP TABLE #x2;

SQL सर्वर 2012

यदि आप SQL सर्वर के सबसे हाल के संस्करण पर हैं, तो विंडोिंग कार्यक्षमता में वृद्धि हमें स्व-जुड़ने की घातीय लागत (एसयूएम एक पास में गणना की जाती है) के बिना आसानी से चल रहे योगों की गणना करने की अनुमति देती है, सीटीई की जटिलता (आवश्यकता सहित) बेहतर प्रदर्शन करने वाले CTE के लिए सन्निहित पंक्तियों), असमर्थित quirky अपडेट और निषिद्ध कर्सर। उपयोग करने RANGEऔर ROWS, या बिल्कुल भी निर्दिष्ट नहीं करने के बीच के अंतर से सावधान रहें - केवल ROWSएक डिस्क-स्पूल से बचा जाता है, जो अन्यथा प्रदर्शन में बाधा उत्पन्न करेगा।

;WITH g AS 
(
  SELECT [Day], c = COUNT(DISTINCT CustomerID) 
    FROM dbo.Hits
    GROUP BY [Day]
)
SELECT g.[Day], c, 
  rt = SUM(c) OVER (ORDER BY [Day] ROWS UNBOUNDED PRECEDING)
FROM g
ORDER BY g.[Day];

प्रदर्शन की तुलना

मैंने प्रत्येक दृष्टिकोण लिया और निम्नलिखित का उपयोग करके इसे एक बैच लपेट दिया:

SELECT SYSUTCDATETIME();
GO
DBCC DROPCLEANBUFFERS;DBCC FREEPROCCACHE;
-- query here
GO 10
SELECT SYSUTCDATETIME();

यहां मिलीसेकंड में कुल अवधि के परिणाम दिए गए हैं (याद रखें कि इसमें हर बार DBCC कमांड भी शामिल हैं):

method                          run 1     run 2
-----------------------------   --------  --------
self-join                        1296 ms   1357 ms -- "supported" non-SQL 2012 winner
recursive cte with dates         1655 ms   1516 ms
recursive cte with row_number   19747 ms  19630 ms
recursive cte with #temp table   1624 ms   1329 ms
quirky update                     880 ms   1030 ms -- non-SQL 2012 winner
cursor                           1962 ms   1850 ms
SQL Server 2012                   847 ms    917 ms -- winner if SQL 2012 available

और मैंने इसे बिना DBCC कमांड के फिर से किया:

method                          run 1     run 2
-----------------------------   --------  --------
self-join                        1272 ms   1309 ms -- "supported" non-SQL 2012 winner
recursive cte with dates         1247 ms   1593 ms
recursive cte with row_number   18646 ms  18803 ms
recursive cte with #temp table   1340 ms   1564 ms
quirky update                    1024 ms   1116 ms -- non-SQL 2012 winner
cursor                           1969 ms   1835 ms
SQL Server 2012                   600 ms    569 ms -- winner if SQL 2012 available

DBCC और लूप दोनों को हटाकर, केवल एक कच्चे पुनरावृत्ति को मापना:

method                          run 1     run 2
-----------------------------   --------  --------
self-join                         313 ms    242 ms
recursive cte with dates          217 ms    217 ms
recursive cte with row_number    2114 ms   1976 ms
recursive cte with #temp table     83 ms    116 ms -- "supported" non-SQL 2012 winner
quirky update                      86 ms     85 ms -- non-SQL 2012 winner
cursor                           1060 ms    983 ms
SQL Server 2012                    68 ms     40 ms -- winner if SQL 2012 available

अंत में, मैंने स्रोत तालिका में पंक्ति संख्या को 10 से गुणा किया (शीर्ष 50000 में बदलकर और एक क्रॉस ज्वाइन के रूप में दूसरी तालिका जोड़ते हुए)। इसके परिणाम, बिना DBCC कमांड के एक एकल पुनरावृत्ति (केवल समय के हितों में):

method                           run 1      run 2
-----------------------------    --------   --------
self-join                         2401 ms    2520 ms
recursive cte with dates           442 ms     473 ms
recursive cte with row_number   144548 ms  147716 ms
recursive cte with #temp table     245 ms     236 ms -- "supported" non-SQL 2012 winner
quirky update                      150 ms     148 ms -- non-SQL 2012 winner
cursor                            1453 ms    1395 ms
SQL Server 2012                    131 ms     133 ms -- winner

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


डेमो

मैंने एक वर्गफ़िल्ड जोड़ी है । परिणाम:

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


निष्कर्ष

मेरे परीक्षणों में, पसंद होगी:

  1. SQL सर्वर 2012 विधि, यदि मेरे पास SQL ​​सर्वर 2012 उपलब्ध है।
  2. यदि SQL Server 2012 उपलब्ध नहीं है, और मेरी तारीखें सन्निहित हैं, तो मैं तारीख पद्धति के साथ पुनरावर्ती cte के साथ जाऊंगा।
  3. यदि न तो 1. और न ही 2. लागू होते हैं, तो मैं क्वर्की अपडेट पर सेल्फ-जॉइन के साथ जाऊंगा, भले ही प्रदर्शन करीब था, सिर्फ इसलिए कि व्यवहार का दस्तावेजीकरण और गारंटी है। मैं भविष्य की अनुकूलता के बारे में कम चिंतित हूं क्योंकि उम्मीद है कि अगर quirky अपडेट टूट जाता है तो मैं अपने पहले कोड को 1. में परिवर्तित कर दूंगा। :-)

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


अद्यतन करें

मैंने इस बारे में यहाँ और अधिक ब्लॉग किया है:

कुल योग चलाने के लिए सबसे अच्छा तरीका - SQL सर्वर 2012 के लिए अद्यतन किया गया


1

यह, जाहिरा तौर पर, इष्टतम समाधान है

DECLARE @dailyCustomers TABLE (day smalldatetime, CountCustomers int, RunningTotal int)

DECLARE @RunningTotal int

SET @RunningTotal = 0

INSERT INTO @dailyCustomers 
SELECT day, CountCustomers, null
FROM Sales
ORDER BY day

UPDATE @dailyCustomers
SET @RunningTotal = RunningTotal = @RunningTotal + CountCustomers
FROM @dailyCustomers

SELECT * FROM @dailyCustomers

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

इसे सेल्फ ज्वाइन या नेस्टेड सबकुछ के साथ भी किया जा सकता है, लेकिन ये विकल्प लगभग नहीं करते हैं। यह भी संभावना है कि आप कुछ स्पूलिंग या वर्कटेबल्स के साथ इन विकल्पों के साथ वैसे भी tempdb मार रहे होंगे।

3
बस इस बात से अवगत रहें कि यह "quirky अपडेट" विधि काम करने की गारंटी नहीं है - यह वाक्यविन्यास असमर्थित है और इसका व्यवहार अपरिभाषित है, और यह भविष्य के संस्करण, हॉट फ़िक्स या सर्विस पैक में टूट सकता है। इसलिए हाँ, यह कुछ समर्थित विकल्पों की तुलना में तेज़ है, जो संभावित भविष्य की अनुकूलता लागत पर आता है।
हारून बर्ट्रेंड

6
इस दृष्टिकोण के लिए बहुत सारे चेतावनी हैं जो जेफ मोदेन ने कहीं लिखा है। dayउदाहरण के लिए आपके पास एक संकुल सूचकांक होना चाहिए ।
मार्टिन स्मिथ

2
@MartinSmith यह sqlservercentral.com पर एक बहुत बड़ा लेख है (लेखक के पेज पर जाएँ और विचित्र अपडेट पर उनके लेख देखें)।
फैब्रिकियो अरुजो

-2

बस एक और तरीका, महंगा, लेकिन संस्करण स्वतंत्र। यह अस्थायी टेबल या चर का उपयोग नहीं करता है।

select T.dday, T.CustomersByDay + 
    (select count(A.customer) from NewCustomersByDate A 
      where A.dday < T.dday) as TotalCustomerTillNow 
from (select dday, count(customer) as CustomersByDay 
        from NewCustomersByDate group by dday) T 

2
यह अच्छा नहीं है, यह बहुत धीमी है। यहां तक ​​कि आपके पास बस 100 पंक्तियां हैं, यह 5,050 बार तालिकाओं के बीच पिंग-पोंग पढ़ेगा। 200 पंक्तियों, 20,100 बार है। केवल 1,000 पंक्तियों के साथ, यह तेजी से 500,500 तक पहुंचता है sqlblog.com/blogs/adam_machanic/archive/2006/07/12/……
माइकल ब्यून

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