मैंने इसे एक बहुत ही सरल कैलेंडर तालिका के द्वारा हल किया है - प्रत्येक वर्ष में एक पंक्ति प्रति समर्थित समय क्षेत्र है , जिसमें मानक ऑफसेट और डीएसटी और इसके ऑफसेट (यदि उस समय क्षेत्र इसका समर्थन करता है) के आरंभिक समय / अंत डेटाटाइम है। फिर एक इनलाइन, स्कीमा-बाउंड, टेबल-वैल्यू फ़ंक्शन जो स्रोत समय (यूटीसी के पाठ्यक्रम में) लेता है और ऑफसेट को जोड़ता / घटाता है।
यह स्पष्ट रूप से कभी भी बहुत अच्छा प्रदर्शन नहीं करेगा यदि आप डेटा के एक बड़े हिस्से के खिलाफ रिपोर्ट कर रहे हैं; विभाजन मदद करने के लिए लग सकता है, लेकिन आपके पास अभी भी ऐसे मामले होंगे जहां एक वर्ष में आखिरी कुछ घंटे या अगले वर्ष में पहले कुछ घंटे वास्तव में एक अलग वर्ष के होते हैं जब एक विशिष्ट समय क्षेत्र में परिवर्तित किया जाता है - ताकि आप कभी भी सही विभाजन प्राप्त न कर सकें अलगाव, जब आपकी रिपोर्टिंग रेंज में 31 दिसंबर या 1 जनवरी शामिल नहीं है।
कुछ अजीब से अजीब मामले हैं जिन पर आपको विचार करने की आवश्यकता है:
2014-11-02 05:30 UTC और 2014-11-02 06:30 UTC दोनों पूर्वी समय क्षेत्र में 01:30 पूर्वाह्न में परिवर्तित होते हैं, उदाहरण के लिए (पहली बार 01:30 स्थानीय रूप से हिट किया गया था, और फिर एक दूसरी बार जब घड़ियों 2:00 पूर्वाह्न से 1:00 पूर्वाह्न तक लुढ़की, और एक और आधा घंटा बीत गया)। इसलिए आपको यह तय करने की आवश्यकता है कि रिपोर्टिंग के उस घंटे को कैसे संभालना है - यूटीसी के अनुसार, आपको उन दो घंटों में एक बार में एक घंटे में मैप किए जाने वाले ट्रैफ़िक या वॉल्यूम को दोगुना देखना चाहिए, जो कि डीएसटी का निरीक्षण करता है। यह घटनाओं की अनुक्रमण के साथ मजेदार खेल भी खेल सकता है, क्योंकि कुछ ऐसा जो तार्किक रूप से घटित होना चाहिए था जब कुछ और दिखाई दे सकता थाएक बार होने से पहले होने वाली समयावधि को दो के बजाय एक घंटे में समायोजित किया जाता है। एक चरम उदाहरण एक पृष्ठ दृश्य है जो 05:59 UTC पर हुआ, फिर एक क्लिक जो 06:00 UTC पर हुआ। UTC के समय में ये एक मिनट के अलावा हुए, लेकिन जब पूर्वी समय में परिवर्तित किया गया, तो दृश्य 1:59 बजे हुआ, और क्लिक एक घंटे पहले हुआ।
2014-03-09 02:30 संयुक्त राज्य अमेरिका में कभी नहीं होता है। ऐसा इसलिए है क्योंकि 2:00 AM हम घड़ियों को 3:00 AM तक आगे बढ़ाते हैं। यदि उपयोगकर्ता ऐसे समय में प्रवेश करता है और आपको इसे UTC में परिवर्तित करने के लिए कहता है, या अपना फ़ॉर्म डिज़ाइन करने के लिए कहता है, तो संभवतः आप एक त्रुटि उठाना चाहेंगे।
यहां तक कि उन किनारे मामलों को ध्यान में रखते हुए, मुझे अभी भी लगता है कि आपके पास सही दृष्टिकोण है: डेटा को यूटीसी में संग्रहीत करें। UTC से अन्य समय क्षेत्रों में डेटा को मैप करने के लिए बहुत आसान है, कुछ समय क्षेत्र से किसी अन्य समय क्षेत्र की तुलना में, खासकर जब अलग-अलग समय क्षेत्र अलग-अलग तारीखों पर DST शुरू / समाप्त करते हैं, और यहां तक कि एक ही समय क्षेत्र अलग-अलग वर्षों में विभिन्न नियमों का उपयोग करके स्विच कर सकते हैं ( उदाहरण के लिए अमेरिका ने 6 साल पहले या तो नियमों को बदल दिया)।
आप इस सब के लिए एक कैलेंडर तालिका का उपयोग करना चाहेंगे, न कि कुछ अभिमानी CASE
अभिव्यक्ति ( कथन नहीं )। मैंने अभी इस पर MSSQLTips.com के लिए तीन-भाग श्रृंखला लिखी है; मुझे लगता है कि तीसरा भाग आपके लिए सबसे उपयोगी होगा:
http://www.mssqltips.com/sqlservertip/3173/handle-conversion-between-time-zones-in-sql-server--part-1/
http://www.mssqltips.com/sqlservertip/3174/handle-conversion-between-time-zones-in-sql-server--part-2/
http://www.mssqltips.com/sqlservertip/3175/handle-conversion-between-time-zones-in-sql-server--part-3/
एक वास्तविक जीवंत उदाहरण, इस बीच
मान लीजिए कि आपके पास एक बहुत ही सरल तथ्य तालिका है। इस मामले में मुझे जो एकमात्र तथ्य ध्यान में आता है, वह घटना का समय है, लेकिन मैं केवल तालिका को व्यापक बनाने के लिए एक व्यर्थ GUID जोड़ूंगा। फिर से, स्पष्ट होने के लिए, तथ्य तालिका केवल यूटीसी समय और यूटीसी समय में घटनाओं को संग्रहीत करती है। मैंने कॉलम को भी प्रत्यय दिया है _UTC
ताकि कोई भ्रम न हो।
CREATE TABLE dbo.Fact
(
EventTime_UTC DATETIME NOT NULL,
Filler UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID()
);
GO
CREATE CLUSTERED INDEX x ON dbo.Fact(EventTime_UTC);
GO
अब, हमारे फैक्ट टेबल को 10,000,000 पंक्तियों के साथ लोड करते हैं - 2013-12-30 से मध्यरात्रि यूटीसी पर हर 3 सेकंड (प्रति घंटे 1,200 पंक्तियों) का प्रतिनिधित्व करते हुए 2014-12-12 पर 5 बजे यूटीसी के कुछ समय बाद तक। यह सुनिश्चित करता है कि डेटा एक वर्ष की सीमा और साथ ही कई समय क्षेत्रों के लिए डीएसटी को आगे और पीछे बढ़ाता है। यह वास्तव में डरावना लग रहा है, लेकिन मेरे सिस्टम पर ~ 9 सेकंड लग गए। तालिका का अंत लगभग 325 एमबी होना चाहिए।
;WITH x(c) AS
(
SELECT TOP (10000000) DATEADD(SECOND,
3*(ROW_NUMBER() OVER (ORDER BY s1.[object_id])-1),
'20131230')
FROM sys.all_columns AS s1
CROSS JOIN sys.all_columns AS s2
ORDER BY s1.[object_id]
)
INSERT dbo.Fact WITH (TABLOCKX) (EventTime_UTC)
SELECT c FROM x;
और सिर्फ यह दिखाने के लिए कि इस 10MM पंक्ति तालिका के खिलाफ एक विशिष्ट खोज क्वेरी क्या दिखेगी, यदि मैं यह क्वेरी चलाता हूं:
SELECT DATEADD(HOUR, DATEDIFF(HOUR, 0, EventTime_UTC), 0),
COUNT(*)
FROM dbo.Fact
WHERE EventTime_UTC >= '20140308'
AND EventTime_UTC < '20140311'
GROUP BY DATEADD(HOUR, DATEDIFF(HOUR, 0, EventTime_UTC), 0);
मुझे यह योजना मिलती है, और यह 25 मिलीसेकंड * में रिटर्न करता है, 358 रीड करता है, 72 घंटे के योग को वापस करने के लिए:
* हमारे मुफ़्त एसक्यूएल संतरी प्लान एक्सप्लोरर द्वारा मापी गई अवधि , जो परिणामों को रोकती है, इसलिए इसमें डेटा के नेटवर्क हस्तांतरण समय, प्रतिपादन आदि शामिल नहीं हैं। अतिरिक्त अस्वीकरण के रूप में, मैं एसक्यूएल संतरी के लिए काम करता हूं।
यह थोड़ा लंबा लगता है, जाहिर है, अगर मैं अपनी सीमा को बहुत बड़ा बनाता हूं - एक महीने का डेटा 258ms लेता है, दो महीने 500ms से अधिक लेता है, और इसी तरह। समानांतरवाद में किक हो सकती है:
यह वह जगह है जहां आप रिपोर्टिंग प्रश्नों को संतुष्ट करने के लिए अन्य, बेहतर समाधानों के बारे में सोचना शुरू करते हैं, और इसका कुछ भी नहीं है कि आपका आउटपुट किस समय क्षेत्र के साथ प्रदर्शित होगा। मैं उस में नहीं जाऊंगा, मैं बस यह प्रदर्शित करना चाहता हूं कि समय क्षेत्र रूपांतरण वास्तव में आपके रिपोर्टिंग प्रश्नों को अधिक से अधिक चूसने वाला नहीं है, और वे पहले से ही चूसना कर सकते हैं यदि आपको बड़ी रेंज मिल रही है जो उचित रूप से समर्थित नहीं हैं। अनुक्रमित। मैं यह दिखाने के लिए कि यह तर्क सही है, छोटी तिथि सीमा पर छड़ी करने जा रहा हूं, और आपको यह सुनिश्चित करने के बारे में चिंता करने देता हूं कि आपके रेंज-आधारित रिपोर्टिंग क्वेरी पर्याप्त या बिना समय क्षेत्र रूपांतरणों के साथ प्रदर्शन करते हैं।
ठीक है, अब हमें अपने टाइम ज़ोन (ऑफसेट के साथ, मिनटों में, क्योंकि हर कोई यूटीसी से भी घंटे दूर नहीं है) और डीएसटी बदलने की तारीखों को प्रत्येक समर्थित वर्ष के लिए स्टोर करने के लिए तालिकाओं की आवश्यकता है। सादगी के लिए, मैं केवल कुछ समय क्षेत्रों में प्रवेश करने जा रहा हूं और उपरोक्त डेटा से मिलान करने के लिए एक ही वर्ष।
CREATE TABLE dbo.TimeZones
(
TimeZoneID TINYINT NOT NULL PRIMARY KEY,
Name VARCHAR(9) NOT NULL,
Offset SMALLINT NOT NULL, -- minutes
DSTName VARCHAR(9) NOT NULL,
DSTOffset SMALLINT NOT NULL -- minutes
);
विविधता के लिए कुछ समय क्षेत्र शामिल हैं, आधे घंटे के साथ कुछ, कुछ जो डीएसटी का पालन नहीं करते हैं। ध्यान दें कि दक्षिणी गोलार्ध में ऑस्ट्रेलिया हमारी सर्दियों के दौरान डीएसटी का निरीक्षण करता है, इसलिए उनकी घड़ियां अप्रैल में वापस चली जाती हैं और अक्टूबर में आगे बढ़ जाती हैं। (उपरोक्त तालिका नामों को फ़्लिप करती है, लेकिन मुझे यकीन नहीं है कि यह दक्षिणी गोलार्ध के समय क्षेत्र के लिए किसी भी कम भ्रामक कैसे बना सकता है।)
INSERT dbo.TimeZones VALUES
(1, 'UTC', 0, 'UTC', 0),
(2, 'GMT', 0, 'BST', 60),
-- London = UTC in winter, +1 in summer
(3, 'EST', -300, 'EDT', -240),
-- East coast US (-5 h in winter, -4 in summer)
(4, 'ACDT', 630, 'ACST', 570),
-- Adelaide (Australia) +10.5 h Oct - Apr, +9.5 Apr - Oct
(5, 'ACST', 570, 'ACST', 570);
-- Darwin (Australia) +9.5 h year round
अब, यह जानने के लिए कि कैलेंडर में एक टेबल टेबल कब बदल जाता है। मैं केवल ब्याज की पंक्तियाँ सम्मिलित करने जा रहा हूं (प्रत्येक समय क्षेत्र ऊपर, और 2014 के लिए केवल DST परिवर्तन)। आगे और पीछे की गणना में आसानी के लिए, मैं यूटीसी में दोनों पल को स्टोर करता हूं जहां एक समय क्षेत्र बदलता है, और स्थानीय समय में एक ही पल। समय क्षेत्र के लिए जो डीएसटी का पालन नहीं करते हैं, यह पूरे वर्ष मानक है, और डीएसटी 1 जनवरी को "शुरू" होता है।
CREATE TABLE dbo.Calendar
(
TimeZoneID TINYINT NOT NULL FOREIGN KEY
REFERENCES dbo.TimeZones(TimeZoneID),
[Year] SMALLDATETIME NOT NULL,
UTCDSTStart SMALLDATETIME NOT NULL,
UTCDSTEnd SMALLDATETIME NOT NULL,
LocalDSTStart SMALLDATETIME NOT NULL,
LocalDSTEnd SMALLDATETIME NOT NULL,
PRIMARY KEY (TimeZoneID, [Year])
);
आप निश्चित रूप से एल्गोरिदम के साथ इसे आबाद कर सकते हैं (और आगामी टिप श्रृंखला कुछ चतुर सेट-आधारित तकनीकों का उपयोग करती है, अगर मैं ऐसा खुद कहता हूं), लूप के बजाय, मैन्युअल रूप से पॉप्युलेट करें, आपके पास क्या है। इस उत्तर के लिए मैंने पाँच टाइम ज़ोन के लिए केवल एक वर्ष को मैन्युअल रूप से आबाद करने का निर्णय लिया, और मैं किसी भी फैंसी ट्रिक्स को परेशान नहीं करने वाला हूं।
INSERT dbo.Calendar VALUES
(1, '20140101', '20140101 00:00','20150101 00:00','20140101 00:00','20150101 00:00'),
(2, '20140101', '20140330 01:00','20141026 00:00','20140330 02:00','20141026 01:00'),
(3, '20140101', '20140309 07:00','20141102 06:00','20140309 03:00','20141102 01:00'),
(4, '20140101', '20140405 16:30','20141004 16:30','20140406 03:00','20141005 02:00'),
(5, '20140101', '20140101 00:00','20150101 00:00','20140101 00:00','20150101 00:00');
ठीक है, इसलिए हमारे पास हमारे तथ्य डेटा, और हमारे "आयाम" टेबल हैं (जब मैं कहता हूं कि मैं उखड़ जाता हूं), तो क्या तर्क है? खैर, मुझे लगता है कि आप उपयोगकर्ताओं को अपने समय क्षेत्र का चयन करने जा रहे हैं और क्वेरी के लिए तिथि सीमा दर्ज करेंगे। मैं यह भी मानूंगा कि तिथि सीमा अपने समय क्षेत्र में पूरे दिन होगी; कोई आंशिक दिन नहीं, कभी आंशिक घंटे नहीं। इसलिए वे एक आरंभ तिथि, एक अंतिम तिथि और एक टाइमजोनआईडी में पास होंगे। वहां से हम उस समय क्षेत्र से प्रारंभ / अंतिम तिथि को यूटीसी में परिवर्तित करने के लिए एक स्केलर फ़ंक्शन का उपयोग करेंगे, जो हमें यूटीसी रेंज के आधार पर डेटा को फ़िल्टर करने की अनुमति देगा। एक बार जब हम ऐसा कर लेते हैं, और उस पर हमारे एकत्रीकरण का प्रदर्शन करते हैं, तो हम उपयोगकर्ता को प्रदर्शित करने से पहले, समूहित समय के रूपांतरण को स्रोत समय क्षेत्र में वापस लागू कर सकते हैं।
स्केलर UDF:
CREATE FUNCTION dbo.ConvertToUTC
(
@Source SMALLDATETIME,
@SourceTZ TINYINT
)
RETURNS SMALLDATETIME
WITH SCHEMABINDING
AS
BEGIN
RETURN
(
SELECT DATEADD(MINUTE, -CASE
WHEN @Source >= src.LocalDSTStart
AND @Source < src.LocalDSTEnd THEN t.DSTOffset
WHEN @Source >= DATEADD(HOUR,-1,src.LocalDSTStart)
AND @Source < src.LocalDSTStart THEN NULL
ELSE t.Offset END, @Source)
FROM dbo.Calendar AS src
INNER JOIN dbo.TimeZones AS t
ON src.TimeZoneID = t.TimeZoneID
WHERE src.TimeZoneID = @SourceTZ
AND t.TimeZoneID = @SourceTZ
AND DATEADD(MINUTE,t.Offset,@Source) >= src.[Year]
AND DATEADD(MINUTE,t.Offset,@Source) < DATEADD(YEAR, 1, src.[Year])
);
END
GO
और टेबल-मूल्यवान फ़ंक्शन:
CREATE FUNCTION dbo.ConvertFromUTC
(
@Source SMALLDATETIME,
@SourceTZ TINYINT
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT
[Target] = DATEADD(MINUTE, CASE
WHEN @Source >= trg.UTCDSTStart
AND @Source < trg.UTCDSTEnd THEN tz.DSTOffset
ELSE tz.Offset END, @Source)
FROM dbo.Calendar AS trg
INNER JOIN dbo.TimeZones AS tz
ON trg.TimeZoneID = tz.TimeZoneID
WHERE trg.TimeZoneID = @SourceTZ
AND tz.TimeZoneID = @SourceTZ
AND @Source >= trg.[Year]
AND @Source < DATEADD(YEAR, 1, trg.[Year])
);
और एक प्रक्रिया जो इसका उपयोग करती है ( संपादित करें : 30 मिनट की ऑफ़सेट ग्रुपिंग को संभालने के लिए अपडेट की गई):
CREATE PROCEDURE dbo.ReportOnDateRange
@Start SMALLDATETIME, -- whole dates only please!
@End SMALLDATETIME, -- whole dates only please!
@TimeZoneID TINYINT
AS
BEGIN
SET NOCOUNT ON;
SELECT @Start = dbo.ConvertToUTC(@Start, @TimeZoneID),
@End = dbo.ConvertToUTC(@End, @TimeZoneID);
;WITH x(t,c) AS
(
SELECT DATEDIFF(MINUTE, @Start, EventTime_UTC)/60,
COUNT(*)
FROM dbo.Fact
WHERE EventTime_UTC >= @Start
AND EventTime_UTC < DATEADD(DAY, 1, @End)
GROUP BY DATEDIFF(MINUTE, @Start, EventTime_UTC)/60
)
SELECT
UTC = DATEADD(MINUTE, x.t*60, @Start),
[Local] = y.[Target],
[RowCount] = x.c
FROM x OUTER APPLY
dbo.ConvertFromUTC(DATEADD(MINUTE, x.t*60, @Start), @TimeZoneID) AS y
ORDER BY UTC;
END
GO
(आप चाहते हैं कि शॉर्ट सर्कुलेटिंग में, या एक अलग संग्रहित प्रक्रिया में, उपयोगकर्ता यूटीसी में रिपोर्टिंग करना चाहता है - जाहिर है, यूटीसी से और उसके लिए अनुवाद करना बहुत ही व्यस्त काम होने वाला है।)
नमूना कॉल:
EXEC dbo.ReportOnDateRange
@Start = '20140308',
@End = '20140311',
@TimeZoneID = 3;
41ms * में रिटर्न, और इस योजना को उत्पन्न करता है:
* फिर से, खारिज परिणामों के साथ।
2 महीने के लिए, यह 507ms में वापस आ जाता है, और यह योजना पंक्ति गणना के अलावा अन्य समान है:
जबकि थोड़ा और अधिक जटिल और बढ़ते रन समय थोड़ा, मुझे पूरा विश्वास है कि इस प्रकार का दृष्टिकोण ब्रिज टेबल दृष्टिकोण की तुलना में बहुत बेहतर काम करेगा। और यह एक dba.se उत्तर के लिए एक ऑफ-कफ उदाहरण है; मुझे यकीन है कि मेरे तर्क और कार्यकुशलता को मुझसे ज्यादा स्मार्ट लोगों द्वारा सुधारा जा सकता है।
आप उन आंकड़ों को देखने के लिए मना कर सकते हैं, जिनके बारे में मैं बात करता हूं - उस घंटे के लिए आउटपुट की कोई पंक्ति नहीं जहां घड़ियां आगे बढ़ती हैं, घंटे की दो पंक्तियां जहां वे वापस लुढ़कती हैं (और वह घंटा दो बार हुआ)। आप बुरे मूल्यों के साथ भी खेल सकते हैं; यदि आप उदाहरण के लिए 20140309 02:30 पूर्वी समय में पास होते हैं, तो यह बहुत अच्छा काम नहीं करेगा।
आपकी रिपोर्ट कैसे काम करेगी, इस बारे में मेरे पास सही अनुमान नहीं है, इसलिए आपको कुछ समायोजन करने पड़ सकते हैं। लेकिन मुझे लगता है कि यह मूल बातें शामिल करता है।