संग्रहीत कार्यविधि का उपयोग करके दिनांक सीमा में प्रत्येक दिन के लिए एक पंक्ति कैसे बनाएं?


11

मैं एक संग्रहित प्रक्रिया बनाना चाहूंगा जो दी गई तिथि सीमा में हर दिन के लिए एक तालिका में एक पंक्ति बनाएगी। संग्रहित प्रक्रिया दो इनपुटों को स्वीकार करती है - उपयोगकर्ता द्वारा वांछित तिथि सीमा की एक प्रारंभ तिथि और समाप्ति तिथि।

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

SELECT Day, Currency
FROM ConversionTable

दिन एक तारीख समय है, और मुद्रा सिर्फ एक पूर्णांक है।

चीजों को सरल रखने के लिए, मान लें कि मैं हमेशा यह चाहता हूं कि इन सम्मिलित पंक्तियों में से प्रत्येक के लिए करेंसी कॉलम 1 हो। इसलिए, यदि कोई व्यक्ति अंतिम तिथि के रूप में '5 मार्च, 2017' और अंतिम तिथि के रूप में '11 अप्रैल, 2017' को इनपुट करता है, तो मैं बनाई गई निम्न पंक्तियों को पसंद करूंगा:

2017-03-05 00:00:00, 1
2017-03-06 00:00:00, 1
...
2017-04-11 00:00:00, 1

ऐसा करने के लिए संग्रहीत प्रक्रिया को कोड करने का सबसे अच्छा तरीका क्या है? मैं अपने परीक्षण वातावरण में SQL Server 2008 R2 का उपयोग कर रहा हूं, लेकिन हमारा वास्तविक वातावरण SQL सर्वर 2012 का उपयोग करता है, इसलिए मैं अपनी परीक्षण मशीन को अपग्रेड कर सकता हूं यदि 2012 में नई कार्यक्षमता पेश की गई है जो इस कार्य को आसान बनाती है।

जवाबों:


15

एक विकल्प एक पुनरावर्ती CTE है:

DECLARE @StartDate datetime = '2017-03-05'
       ,@EndDate   datetime = '2017-04-11'
;

WITH theDates AS
     (SELECT @StartDate as theDate
      UNION ALL
      SELECT DATEADD(day, 1, theDate)
        FROM theDates
       WHERE DATEADD(day, 1, theDate) <= @EndDate
     )
SELECT theDate, 1 as theValue
  FROM theDates
OPTION (MAXRECURSION 0)
;

( MAXRECURSIONसंकेत स्कॉट होडिन की टिप्पणी के लिए धन्यवाद जोड़ा गया, नीचे)


अरे हाँ! मैंने एक "संख्या" तालिका दृष्टिकोण (स्कॉट हॉजिन का उत्तर) के बारे में सोचा था। बहुत अधिक दिनों तक, यह बेहतर प्रदर्शन कर सकता है। और एक अच्छा पुन: प्रयोज्य दृष्टिकोण (जॉन सी का जवाब) हमेशा अच्छा होता है। यह सिर्फ सबसे सरल और सबसे सीधा-सीधा जवाब था जिसके बारे में मैं सोच सकता था।
RDFozz

1
बस याद रखें - जब आपके पास MAXRECURSION निर्दिष्ट नहीं होता है, तो पुनरावर्तन की डिफ़ॉल्ट सीमा 100 होती है - मुझे आपके SP के लिए घृणा होती है जब व्यापक तिथि सीमाएं पास हो जाती हैं :)
स्कॉट हॉजगिन

2
@ सही Hodgin आप सही हैं। MAXRECURSIONहल करने के लिए क्वेरी में संकेत जोड़ा गया ।
RDFozz

इस पद्धति का एक जानबूझकर या अनजाने में व्यवहार होता है जिसमें एंडडेट मान के अगले दिन शामिल होता है। यदि यह व्यवहार अवांछनीय है, तो WHATE को DATEADD (DAY, 1, theDate) में परिवर्तित करें <@EndDate
क्रिस पोर्टर

1
@ क्रिसपॉर्टर - उत्कृष्ट बिंदु! हालाँकि, यदि आप इसे बनाते हैं, तो आपको DATEADD(DAY, 1, theDate) < @EndDateरेंज का अंत नहीं मिलता है जब दोनों डेटाटाइम मानों में समान समय घटक होता है। मैंने उत्तर को उचित रूप से संशोधित किया, लेकिन उपयोग कर रहा था <= @EndDate। यदि आप रेंज वैल्यू के अंत को शामिल नहीं करना चाहते हैं , तो < @EndDateवास्तव में सही होगा।
RDFozz

6

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

उदाहरण के लिए

Select * from [dbo].[udf-Range-Date]('2017-03-05','2017-04-11','DD',1) 

रिटर्न

RetSeq  RetVal
1   2017-03-05 00:00:00.000
2   2017-03-06 00:00:00.000
3   2017-03-07 00:00:00.000
4   2017-03-08 00:00:00.000
5   2017-03-09 00:00:00.000
...
36  2017-04-09 00:00:00.000
37  2017-04-10 00:00:00.000
38  2017-04-11 00:00:00.000

यूडीएफ अगर इच्छुक है

CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
    with cte0(M)   As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
         cte1(N)   As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
         cte2(N)   As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
         cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )

    Select RetSeq = N+1
          ,RetVal = D 
     From  cte3,cte0 
     Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1) 
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1) 
*/

@RobV बहुत सच है। एक लंबे समय से पहले एक असली जवाब कभी नहीं सीखा है।
जॉन कैप्पलेलेटी

उत्तर के लिए धन्यवाद। ऐसा लगता है कि ऐसा करने के कई तरीके हैं :) दूसरे आदमी ने मेरे सवाल का इस तरह से जवाब दिया कि ठीक से वांछित आउटपुट को हल किया गया, लेकिन आप सही हैं ऐसा लगता है कि यह अधिक लचीला है।
रोब वी

@JohnCappelletti मुझे जो करने की आवश्यकता है, उसके लिए यह सही है, लेकिन मुझे सप्ताहांत और छुट्टियों को निकालने के लिए इसकी आवश्यकता थी ... मैंने अंतिम पंक्ति को निम्न में बदलकर ऐसा किया जहाँ D <@ @ R2 और DATEPART (सप्ताहांत, D) नहीं है। (1,7) और डी नॉट इन (सेलेक्ट होलीडे_डे हॉलिडे से)। छुट्टियाँ एक तालिका है जिसमें छुट्टी की तारीखें होती हैं।
मैट

@ मैट अच्छा किया! सप्ताहांत और छुट्टियों को बाहर करने के लिए, मैंने ऐसा ही किया होगा। :)
जॉन कैप्पेल्लेटी

3

उदाहरण के रूप में डेट आयाम तालिका बनाने के तरीके पर हारून बर्ट्रेंड की पोस्ट का उपयोग करते हुए , मैं इस के साथ आया:

DECLARE @StartDate DATE ='2017-03-05 00:00:00'
DECLARE @EndDate DATE ='2017-04-11 00:00:00'

Declare @DateTable table ([date]       DATE PRIMARY KEY);

-- use the catalog views to generate as many rows as we need

INSERT @DateTable ([date])
SELECT d
FROM (
    SELECT d = DATEADD(DAY, rn - 1, @StartDate)
    FROM (
        SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate)) rn = ROW_NUMBER() OVER (
                ORDER BY s1.[object_id]
                )
        FROM sys.all_objects AS s1
        CROSS JOIN sys.all_objects AS s2
        -- on my system this would support > 5 million days
        ORDER BY s1.[object_id]
        ) AS x
    ) AS y;

SELECT *
FROM @DateTable
ORDER BY [date]

आपको अपनी संग्रहीत प्रक्रिया में इस प्रकार के तर्क रखने में सक्षम होना चाहिए और आपको जो कुछ भी चाहिए, उसे जोड़ना चाहिए।


2

मुझे हाल ही में Redshift पर एक ऐसी ही समस्या का समाधान करना था जहाँ मैंने केवल पहुँच पढ़ी थी और इस प्रकार मुझे अपने परिणाम सेट के लिए एक प्रारंभिक बिंदु के रूप में डेट रेंज में हर घंटे के लिए एक पंक्ति प्राप्त करने के लिए विशुद्ध रूप से SQL- आधारित समाधान (कोई संग्रहीत प्रक्रियाएं) की आवश्यकता नहीं थी। मुझे यकीन है कि अन्य लोग इसे और अधिक सुरुचिपूर्ण बना सकते हैं और इसे अपने उद्देश्यों के लिए संशोधित कर सकते हैं, लेकिन उन लोगों के लिए, यहां मेरा हैक किया गया समाधान है:

with hours as
   (select 0 clockhour union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 10 union select 11 union select 12 
    union select 13 union select 14 union select 15 union select 16 union select 17 union select 18 union select 19 union select 20 union select 21 union select 22 union select 23)
, days as
   (select *
    from 
       (select to_number(n0.number || n1.number, '99') daynum
        from
           (select 0 as number union select 1 union select 2 union select 3) as n0
           cross join
           (select 1 as number union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0) as n1)
    where daynum between 1 and 31)
, months as
   (select 1 as monthnum, 'jan' as themonth, 31 as numdays union select 2, 'feb', 28 union select 3, 'mar', 31 union select 4, 'apr', 30 union select 5, 'may', 31 union select 6, 'jun', 30 
    union select 7, 'jul', 31 union select 8, 'aug', 31 union select 9, 'sep', 30 union select 10, 'oct', 31 union select 11, 'nov', 30 union select 12, 'dec', 31)
, years as
   (select century || decade || yr as yr
    from 
       (select 19 century union select 20) 
    cross join
       (select 0 decade union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) 
    cross join
       (select 0 yr union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9))
select cast(daynum || '-' || themonth || '-' || yr || ' ' || clockhour || ':00:00' as timestamp) dayhour
from hours
cross join days
cross join months
cross join years
where cast(daynum || '-' || themonth || '-' || yr || ' ' || clockhour || ':00:00' as timestamp) 
between date_trunc('month', dateadd('month', -$MONTHS_AGO, getdate()))
and     date_trunc('month', dateadd('month', $MONTHS_AHEAD, getdate()))
and   daynum <= numdays
order by yr, monthnum, daynum, clockhour;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.