매우 간단한 달력 테이블을 사용 하여이 문제를 해결했습니다. 매년 표준 시간대 와 표준 날짜 오프셋과 DST의 시작 날짜 시간 / 종료 날짜 시간 및 오프셋 (해당 시간대가 지원하는 경우)과 함께 지원되는 시간대 당 하나의 행이 있습니다. 그런 다음 소스 시간 (UTC 기준)을 가져오고 오프셋을 더하거나 빼는 인라인 스키마 바인딩 테이블 반환 함수입니다.
많은 양의 데이터에 대해보고하는 경우 성능이 크게 향상되지 않습니다. 파티셔닝은 도움이 될 수 있지만 특정 시간대로 변환 할 때 1 년의 마지막 몇 시간 또는 다음 해의 첫 몇 시간이 실제로 다른 연도에 속하는 경우가 있으므로 실제 파티션을 얻을 수 없습니다. 보고 범위에 12 월 31 일 또는 1 월 1 일이 포함되지 않은 경우를 제외하고
고려해야 할 몇 가지 이상한 경우가 있습니다.
예를 들어 2014-11-02 05:30 UTC와 2014-11-02 06:30 UTC는 동부 표준시에서 오전 1시 30 분으로 변환됩니다 (예 : 처음으로 01:30은 로컬로 충돌 한 후 하나). 시계가 오전 2:00에서 오전 1:00로 롤백되고 두 번째 반 시간이 경과 한 두 번째 시간). 따라서 해당 시간의보고 처리 방법을 결정해야합니다. UTC에 따르면 DST를 관찰하는 시간대에서 두 시간이 단일 시간에 매핑되면 측정하는 트래픽의 양이나 트래픽이 두 배가됩니다. 다른 이벤트가 나타날 때 논리적으로 일어난 일이 있기 때문에 이벤트 순서 지정으로 재미있는 게임을 할 수도 있습니다.타이밍이 2 시간 대신 1 시간으로 조정되면 그 전에 발생합니다. 극단적 인 예는 05:59 UTC에 발생한 페이지 조회수와 06:00 UTC에 발생한 클릭 수입니다. UTC 시간은 1 분 간격으로 발생했지만 동부 시간으로 변환하면 오전 1시 59 분에 조회가 발생했으며 1 시간 전에 클릭이 발생했습니다.
2014-03-09 02:30 미국에서는 절대 발생하지 않습니다. 오전 2시에 시계를 오전 3 시로 롤 포워드하기 때문입니다. 따라서 사용자가 그러한 시간을 입력하고 UTC로 변환하도록 요청하거나 사용자가 그러한 시간을 선택할 수 없도록 양식을 설계하도록 요청하면 오류가 발생하기를 원할 것입니다.
이러한 경우를 염두에두고도 여전히 올바른 접근 방법이 있다고 생각합니다. UTC로 데이터를 저장하십시오. 특히 다른 시간대가 다른 날짜에 DST를 시작 / 종료하고 동일한 시간대조차도 다른 연도에 다른 규칙을 사용하여 전환 할 수있는 경우, 일부 시간대에서 다른 시간대로 UTC에서 다른 시간대로 데이터를 매핑하는 것이 훨씬 쉽습니다 ( 예를 들어 미국은 6 년 전 규칙을 변경했습니다).
이 아니라 어떤 거대한의 모든 달력 테이블을 사용하는 것이 좋습니다 CASE
표현 (안 문 ). 방금 MSSQLTips.com 에 대한 3 부로 구성된 시리즈를 작성 했습니다. 세 번째 부분이 가장 유용하다고 생각합니다.
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 시간과 UTC 시간으로 만 이벤트를 저장합니다. 심지어 열에 접미사를 _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
이제 2013-12-30부터 자정 UTC의 2013-12-30부터 2014-12-12의 UTC 오전 5 시까 지 3 초마다 (1 시간에 1,200 행) 10,000,000 개의 행으로 팩트 테이블을로드 해 보겠습니다. 이를 통해 데이터가 연도 경계를 넘어 여러 표준 시간대의 DST 앞뒤로 이동합니다. 이것은 정말로 무서워 보이지만 내 시스템에서 ~ 9 초가 걸렸습니다. 테이블의 크기는 약 325MB입니다.
;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);
이 계획을 세우고 358 회 읽기를 수행하여 25 밀리 초 *에 반환되어 총 72 시간을 반환합니다.
* 무료 SQL Sentry Plan Explorer 에서 측정 한 결과는 버려 지므로 결과를 버리므로 데이터의 네트워크 전송 시간, 렌더링 등은 포함되지 않습니다. 추가 면책 조항으로 SQL Sentry에서 일합니다.
범위를 너무 크게하면 한 달의 데이터에 258ms, 2 개월에 500ms 이상이 걸리는 등 시간이 조금 더 걸립니다. 병렬 처리가 시작될 수 있습니다.
여기서는보고 쿼리를 만족시키기위한 다른 더 나은 솔루션에 대해 생각하기 시작하며 출력이 표시되는 시간대와는 아무런 관련이 없습니다. 나는 그것에 동의하지 않을 것입니다. 저는 시간대 변환이 실제로보고 쿼리를 훨씬 더 많이 빨아 들일 것이 아니며 적절한 지원되지 않는 넓은 범위를 얻는다면 이미 빨려들 수 있음을 보여주고 싶습니다. 색인. 논리가 올바르다는 것을 보여주기 위해 작은 날짜 범위를 고수하고, 시간대 변환 유무에 관계없이 범위 기반보고 쿼리가 제대로 수행되는지 걱정할 수 있습니다.
이제 시간대를 저장하기위한 테이블이 필요합니다 (오프셋은 분 단위, 모든 사람이 UTC를 벗어난 시간이 아니기 때문에). 그리고 지원되는 각 연도의 DST 변경 날짜가 필요합니다. 간단히하기 위해 위의 데이터와 일치시키기 위해 몇 개의 시간대와 1 년만 입력합니다.
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
);
다양한 표준 시간대가 포함되어 있으며 일부는 30 분 오프셋이 있으며 일부는 DST를 준수하지 않습니다. 자신의 시계를 갈 수 있도록 호주는 남반구에, 우리의 겨울 동안 DST를 준수합니다 다시 4 월과 앞으로 10월있다. (위의 표는 이름을 바꾸지 만 남반구 시간대에 대해 덜 혼란스럽게 만드는 방법을 모르겠습니다.)
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
이제 TZ가 언제 변경되는지 알 수있는 달력 테이블. 관심있는 행만 삽입합니다 (위의 각 시간대 및 2014 년의 DST 만 변경). 계산을 쉽게하기 위해 시간대가 변경되는 UTC와 현지 시간의 동일한 순간을 모두 저장합니다. DST를 준수하지 않는 시간대의 경우 표준은 1 년 내내 표준이며 DST는 1 월 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])
);
루프로 수동으로 채우는 대신 알고리즘으로 알고리즘을 채울 수 있습니다. 이 답변을 위해 5 개의 시간대에 대해 1 년을 수동으로 채우기로 결정했으며 멋진 트릭을 귀찮게하지 않을 것입니다.
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');
자, 사실 데이터와 "치수"테이블 (내가 말할 때 울부 짖음)이 있습니다. 그렇다면 논리는 무엇입니까? 글쎄, 사용자가 시간대를 선택하고 쿼리의 날짜 범위를 입력하게 할 것이라고 가정합니다. 또한 날짜 범위는 자체 시간대로 하루 종일로 가정합니다. 부분적인 일이 없으며 부분적인 시간을 신경 쓰지 마십시오. 따라서 시작 날짜, 종료 날짜 및 TimeZoneID가 전달됩니다. 여기에서 스칼라 함수를 사용하여 시작 / 종료 날짜를 해당 시간대에서 UTC로 변환하여 UTC 범위를 기준으로 데이터를 필터링 할 수 있습니다. 이 작업을 수행하고 집계를 수행 한 후 사용자에게 표시하기 전에 그룹화 된 시간의 변환을 소스 시간대에 다시 적용 할 수 있습니다.
스칼라 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
(사용자가 UTC로보고하려는 경우 단락을 중단하거나 별도의 저장 프로 시저를 원할 수 있습니다. 분명히 UTC로 /에서 번역하는 것은 낭비스러운 작업입니다.)
샘플 통화 :
EXEC dbo.ReportOnDateRange
@Start = '20140308',
@End = '20140311',
@TimeZoneID = 3;
41ms *로 반환하고이 계획을 생성합니다.
* 다시, 폐기 된 결과.
2 개월 동안 507ms 후에 반환되며 계획은 행 개수와 다릅니다.
약간 더 복잡하고 런타임이 약간 증가하지만이 유형의 접근 방식이 브리지 테이블 접근 방식보다 훨씬 더 잘 작동 할 것이라고 확신합니다. 그리고 이것은 dba.se 답변의 커프스 예입니다. 저보다 훨씬 똑똑한 사람들이 내 논리와 효율성을 향상시킬 수 있다고 확신합니다.
시계를 롤 포워드하는 시간에 대한 행의 행이없고 롤백 한 시간에 대한 두 행의 행이 없습니다 (그리고 그 시간이 두 번 발생했습니다). 나쁜 가치를 가지고 놀 수도 있습니다. 예를 들어 20140309 02:30 동부 시간을 지나면 제대로 작동하지 않습니다.
보고의 작동 방식에 대한 모든 가정이 없을 수도 있으므로 일부 조정을 수행해야 할 수도 있습니다. 그러나 나는 이것이 기본 사항을 다루고 있다고 생각합니다.