두 시스템간에 데이터를 변환해야합니다.
첫 번째 시스템은 일정을 일반 날짜 목록으로 저장합니다. 스케줄에 포함 된 각 날짜는 한 행입니다. 날짜 순서에 다양한 간격이있을 수 있습니다 (주말, 공휴일 및 더 이상 일시 중지, 일부 요일은 일정에서 제외 될 수 있음). 간격이 전혀 없으며 주말도 포함될 수 있습니다. 일정은 최대 2 년입니다. 보통 몇 주입니다.
주말을 제외하고 2 주간 진행되는 간단한 일정 예는 다음과 같습니다 (아래 스크립트에는 더 복잡한 예가 있음).
+----+------------+------------+---------+--------+
| ID | ContractID | dt | dowChar | dowInt |
+----+------------+------------+---------+--------+
| 10 | 1 | 2016-05-02 | Mon | 2 |
| 11 | 1 | 2016-05-03 | Tue | 3 |
| 12 | 1 | 2016-05-04 | Wed | 4 |
| 13 | 1 | 2016-05-05 | Thu | 5 |
| 14 | 1 | 2016-05-06 | Fri | 6 |
| 15 | 1 | 2016-05-09 | Mon | 2 |
| 16 | 1 | 2016-05-10 | Tue | 3 |
| 17 | 1 | 2016-05-11 | Wed | 4 |
| 18 | 1 | 2016-05-12 | Thu | 5 |
| 19 | 1 | 2016-05-13 | Fri | 6 |
+----+------------+------------+---------+--------+
ID
고유하지만 반드시 순차적 일 필요는 없습니다 (기본 키임). 날짜는 각 계약 내에서 고유합니다 (에 고유 인덱스가 있음 (ContractID, dt)
).
두 번째 시스템은 스케줄의 일부인 요일 목록과 함께 간격으로 스케줄을 저장합니다. 각 간격은 시작 및 종료 날짜 (포함)와 일정에 포함 된 요일 목록으로 정의됩니다. 이 형식을 사용하면 Mon-Wed와 같은 반복적 인 주별 패턴을 효율적으로 정의 할 수 있지만 공휴일 등의 패턴이 중단되면 고통이됩니다.
위의 간단한 예는 다음과 같습니다.
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 1 | 2016-05-02 | 2016-05-13 | 10 | Mon,Tue,Wed,Thu,Fri, |
+------------+------------+------------+----------+----------------------+
[StartDT;EndDT]
동일한 계약에 속하는 간격이 겹치지 않아야합니다.
첫 번째 시스템의 데이터를 두 번째 시스템에서 사용하는 형식으로 변환해야합니다. 현재 단일 계약의 C #에서 클라이언트 측 에서이 문제를 해결하고 있지만 서버 측의 대량 처리 및 내보내기 / 가져 오기를 위해 서버 측의 T-SQL 에서이 작업을 수행하고 싶습니다. 대부분 CLR UDF를 사용하여 수행 할 수 있지만이 단계에서는 SQLCLR을 사용할 수 없습니다.
여기서의 과제는 가능한 한 짧고 인간 친화적 인 간격 목록을 만드는 것입니다.
예를 들어이 일정은 다음과 같습니다.
+-----+------------+------------+---------+--------+
| ID | ContractID | dt | dowChar | dowInt |
+-----+------------+------------+---------+--------+
| 223 | 2 | 2016-05-05 | Thu | 5 |
| 224 | 2 | 2016-05-06 | Fri | 6 |
| 225 | 2 | 2016-05-09 | Mon | 2 |
| 226 | 2 | 2016-05-10 | Tue | 3 |
| 227 | 2 | 2016-05-11 | Wed | 4 |
| 228 | 2 | 2016-05-12 | Thu | 5 |
| 229 | 2 | 2016-05-13 | Fri | 6 |
| 230 | 2 | 2016-05-16 | Mon | 2 |
| 231 | 2 | 2016-05-17 | Tue | 3 |
+-----+------------+------------+---------+--------+
이되어야한다 :
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 2 | 2016-05-05 | 2016-05-17 | 9 | Mon,Tue,Wed,Thu,Fri, |
+------------+------------+------------+----------+----------------------+
,이거 말고:
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 2 | 2016-05-05 | 2016-05-06 | 2 | Thu,Fri, |
| 2 | 2016-05-09 | 2016-05-13 | 5 | Mon,Tue,Wed,Thu,Fri, |
| 2 | 2016-05-16 | 2016-05-17 | 2 | Mon,Tue, |
+------------+------------+------------+----------+----------------------+
gaps-and-islands
이 문제에 대한 접근 방식 을 적용하려고했습니다 . 나는 두 번에 그것을 시도했다. 첫 번째 패스에서 나는 간단한 연속 일의 섬을 발견합니다. 즉, 섬의 끝은 주말, 공휴일 또는 다른 일과 같이 일의 순서에 차이가 있습니다. 이렇게 발견 된 섬마다 쉼표로 구분 된 distinct 목록을 작성합니다 WeekDays
. 두 번째 패스에서 I 그룹은 일련의 주 번호 간격 또는 WeekDays
.
이 방법을 사용하면 주 번호가 연속적이지만 WeekDays
변경 사항 이 있기 때문에 각 부분주는 위에 표시된 것처럼 추가 간격으로 끝납니다 . 또한 일주일 내에 규칙적인 간격이있을 수 있으며 (에 대한 ContractID=3
데이터 만있는 샘플 데이터 참조 Mon,Wed,Fri,
)이 방식은 해당 일정에서 매일 별도의 간격을 생성합니다. 밝은면에서 일정에 간격이 전혀 없으면 ( ContractID=7
주말을 포함하는 샘플 데이터 참조) 한 번의 간격을 생성 하며이 경우 시작 또는 종료 주가 부분적인지 중요하지 않습니다.
내가 원하는 것을 더 잘 이해하려면 아래 스크립트의 다른 예제를 참조하십시오. 주말이 제외되는 경우가 많지만 다른 요일도 제외 될 수 있습니다. 예를 들면 3 단으로 Mon
, Wed
그리고 Fri
스케줄의 일부이다. 또한, 예 7에서와 같이 주말이 포함될 수 있습니다. 솔루션은 모든 요일을 동일하게 처리해야합니다. 요일은 일정에 포함하거나 제외 할 수 있습니다.
생성 된 간격 목록이 주어진 스케줄을 올바르게 설명하는지 확인하기 위해 다음 의사 코드를 사용할 수 있습니다.
- 모든 간격을 반복
- 각 간격에 대해 시작 날짜와 종료 날짜 사이의 모든 달력 날짜를 포함합니다 (포함).
- 각 날짜에 요일이에 표시되어 있는지 확인하십시오
WeekDays
. 그렇다면이 날짜가 일정에 포함됩니다.
바라건대, 어떤 경우에 새로운 간격을 만들어야하는지 명확하게 설명합니다. 예제 4와 5에서 한 월요일 ( 2016-05-09
)이 스케줄의 중간에서 제거되며 이러한 스케줄을 단일 간격으로 표시 할 수 없습니다. 예 6에서는 스케줄에 간격이 길기 때문에 두 개의 간격이 필요합니다.
간격은 일람표에서 주별 패턴을 나타내며 패턴이 중단 / 변경 될 때 새 간격을 추가해야합니다. 예 11에서 처음 3 주 동안 패턴 Tue
이 있으면이 패턴이로 바뀝니다 Thu
. 결과적으로 우리는 그러한 일정을 설명하기 위해 두 개의 간격이 필요합니다.
현재 SQL Server 2008을 사용하고 있으므로 솔루션이이 버전에서 작동해야합니다. 이후 버전의 기능을 사용하여 SQL Server 2008 용 솔루션을 단순화 / 개선 할 수 있다면 보너스이기도합니다.
나는이 Calendar
테이블 (날짜 목록)과 Numbers
이 필요한 경우,이를 사용하려면 확인하므로, 테이블 (1부터 정수 번호의 전체 목록을). 임시 테이블을 작성하고 여러 단계로 데이터를 처리하는 여러 쿼리를 갖는 것도 좋습니다. 알고리즘의 단계 수는 고정되어야하지만 커서와 명시 적 WHILE
루프는 정상이 아닙니다.
샘플 데이터 및 예상 결과를위한 스크립트
-- @Src is sample data
-- @Dst is expected result
DECLARE @Src TABLE (ID int PRIMARY KEY, ContractID int, dt date, dowChar char(3), dowInt int);
INSERT INTO @Src (ID, ContractID, dt, dowChar, dowInt) VALUES
-- simple two weeks (without weekend)
(110, 1, '2016-05-02', 'Mon', 2),
(111, 1, '2016-05-03', 'Tue', 3),
(112, 1, '2016-05-04', 'Wed', 4),
(113, 1, '2016-05-05', 'Thu', 5),
(114, 1, '2016-05-06', 'Fri', 6),
(115, 1, '2016-05-09', 'Mon', 2),
(116, 1, '2016-05-10', 'Tue', 3),
(117, 1, '2016-05-11', 'Wed', 4),
(118, 1, '2016-05-12', 'Thu', 5),
(119, 1, '2016-05-13', 'Fri', 6),
-- a partial end of the week, the whole week, partial start of the week (without weekends)
(223, 2, '2016-05-05', 'Thu', 5),
(224, 2, '2016-05-06', 'Fri', 6),
(225, 2, '2016-05-09', 'Mon', 2),
(226, 2, '2016-05-10', 'Tue', 3),
(227, 2, '2016-05-11', 'Wed', 4),
(228, 2, '2016-05-12', 'Thu', 5),
(229, 2, '2016-05-13', 'Fri', 6),
(230, 2, '2016-05-16', 'Mon', 2),
(231, 2, '2016-05-17', 'Tue', 3),
-- only Mon, Wed, Fri are included across two weeks plus partial third week
(310, 3, '2016-05-02', 'Mon', 2),
(311, 3, '2016-05-04', 'Wed', 4),
(314, 3, '2016-05-06', 'Fri', 6),
(315, 3, '2016-05-09', 'Mon', 2),
(317, 3, '2016-05-11', 'Wed', 4),
(319, 3, '2016-05-13', 'Fri', 6),
(330, 3, '2016-05-16', 'Mon', 2),
-- a whole week (without weekend), in the second week Mon is not included
(410, 4, '2016-05-02', 'Mon', 2),
(411, 4, '2016-05-03', 'Tue', 3),
(412, 4, '2016-05-04', 'Wed', 4),
(413, 4, '2016-05-05', 'Thu', 5),
(414, 4, '2016-05-06', 'Fri', 6),
(416, 4, '2016-05-10', 'Tue', 3),
(417, 4, '2016-05-11', 'Wed', 4),
(418, 4, '2016-05-12', 'Thu', 5),
(419, 4, '2016-05-13', 'Fri', 6),
-- three weeks, but without Mon in the second week (no weekends)
(510, 5, '2016-05-02', 'Mon', 2),
(511, 5, '2016-05-03', 'Tue', 3),
(512, 5, '2016-05-04', 'Wed', 4),
(513, 5, '2016-05-05', 'Thu', 5),
(514, 5, '2016-05-06', 'Fri', 6),
(516, 5, '2016-05-10', 'Tue', 3),
(517, 5, '2016-05-11', 'Wed', 4),
(518, 5, '2016-05-12', 'Thu', 5),
(519, 5, '2016-05-13', 'Fri', 6),
(520, 5, '2016-05-16', 'Mon', 2),
(521, 5, '2016-05-17', 'Tue', 3),
(522, 5, '2016-05-18', 'Wed', 4),
(523, 5, '2016-05-19', 'Thu', 5),
(524, 5, '2016-05-20', 'Fri', 6),
-- long gap between two intervals
(623, 6, '2016-05-05', 'Thu', 5),
(624, 6, '2016-05-06', 'Fri', 6),
(625, 6, '2016-05-09', 'Mon', 2),
(626, 6, '2016-05-10', 'Tue', 3),
(627, 6, '2016-05-11', 'Wed', 4),
(628, 6, '2016-05-12', 'Thu', 5),
(629, 6, '2016-05-13', 'Fri', 6),
(630, 6, '2016-05-16', 'Mon', 2),
(631, 6, '2016-05-17', 'Tue', 3),
(645, 6, '2016-06-06', 'Mon', 2),
(646, 6, '2016-06-07', 'Tue', 3),
(647, 6, '2016-06-08', 'Wed', 4),
(648, 6, '2016-06-09', 'Thu', 5),
(649, 6, '2016-06-10', 'Fri', 6),
(655, 6, '2016-06-13', 'Mon', 2),
(656, 6, '2016-06-14', 'Tue', 3),
(657, 6, '2016-06-15', 'Wed', 4),
(658, 6, '2016-06-16', 'Thu', 5),
(659, 6, '2016-06-17', 'Fri', 6),
-- two weeks, no gaps between days at all, even weekends are included
(710, 7, '2016-05-02', 'Mon', 2),
(711, 7, '2016-05-03', 'Tue', 3),
(712, 7, '2016-05-04', 'Wed', 4),
(713, 7, '2016-05-05', 'Thu', 5),
(714, 7, '2016-05-06', 'Fri', 6),
(715, 7, '2016-05-07', 'Sat', 7),
(716, 7, '2016-05-08', 'Sun', 1),
(725, 7, '2016-05-09', 'Mon', 2),
(726, 7, '2016-05-10', 'Tue', 3),
(727, 7, '2016-05-11', 'Wed', 4),
(728, 7, '2016-05-12', 'Thu', 5),
(729, 7, '2016-05-13', 'Fri', 6),
-- no gaps between days at all, even weekends are included, with partial weeks
(805, 8, '2016-04-30', 'Sat', 7),
(806, 8, '2016-05-01', 'Sun', 1),
(810, 8, '2016-05-02', 'Mon', 2),
(811, 8, '2016-05-03', 'Tue', 3),
(812, 8, '2016-05-04', 'Wed', 4),
(813, 8, '2016-05-05', 'Thu', 5),
(814, 8, '2016-05-06', 'Fri', 6),
(815, 8, '2016-05-07', 'Sat', 7),
(816, 8, '2016-05-08', 'Sun', 1),
(825, 8, '2016-05-09', 'Mon', 2),
(826, 8, '2016-05-10', 'Tue', 3),
(827, 8, '2016-05-11', 'Wed', 4),
(828, 8, '2016-05-12', 'Thu', 5),
(829, 8, '2016-05-13', 'Fri', 6),
(830, 8, '2016-05-14', 'Sat', 7),
-- only Mon-Wed included, two weeks plus partial third week
(910, 9, '2016-05-02', 'Mon', 2),
(911, 9, '2016-05-03', 'Tue', 3),
(912, 9, '2016-05-04', 'Wed', 4),
(915, 9, '2016-05-09', 'Mon', 2),
(916, 9, '2016-05-10', 'Tue', 3),
(917, 9, '2016-05-11', 'Wed', 4),
(930, 9, '2016-05-16', 'Mon', 2),
(931, 9, '2016-05-17', 'Tue', 3),
-- only Thu-Sun included, three weeks
(1013,10,'2016-05-05', 'Thu', 5),
(1014,10,'2016-05-06', 'Fri', 6),
(1015,10,'2016-05-07', 'Sat', 7),
(1016,10,'2016-05-08', 'Sun', 1),
(1018,10,'2016-05-12', 'Thu', 5),
(1019,10,'2016-05-13', 'Fri', 6),
(1020,10,'2016-05-14', 'Sat', 7),
(1021,10,'2016-05-15', 'Sun', 1),
(1023,10,'2016-05-19', 'Thu', 5),
(1024,10,'2016-05-20', 'Fri', 6),
(1025,10,'2016-05-21', 'Sat', 7),
(1026,10,'2016-05-22', 'Sun', 1),
-- only Tue for first three weeks, then only Thu for the next three weeks
(1111,11,'2016-05-03', 'Tue', 3),
(1116,11,'2016-05-10', 'Tue', 3),
(1131,11,'2016-05-17', 'Tue', 3),
(1123,11,'2016-05-19', 'Thu', 5),
(1124,11,'2016-05-26', 'Thu', 5),
(1125,11,'2016-06-02', 'Thu', 5),
-- one week, then one week gap, then one week
(1210,12,'2016-05-02', 'Mon', 2),
(1211,12,'2016-05-03', 'Tue', 3),
(1212,12,'2016-05-04', 'Wed', 4),
(1213,12,'2016-05-05', 'Thu', 5),
(1214,12,'2016-05-06', 'Fri', 6),
(1215,12,'2016-05-16', 'Mon', 2),
(1216,12,'2016-05-17', 'Tue', 3),
(1217,12,'2016-05-18', 'Wed', 4),
(1218,12,'2016-05-19', 'Thu', 5),
(1219,12,'2016-05-20', 'Fri', 6);
SELECT ID, ContractID, dt, dowChar, dowInt
FROM @Src
ORDER BY ContractID, dt;
DECLARE @Dst TABLE (ContractID int, StartDT date, EndDT date, DayCount int, WeekDays varchar(255));
INSERT INTO @Dst (ContractID, StartDT, EndDT, DayCount, WeekDays) VALUES
(1, '2016-05-02', '2016-05-13', 10, 'Mon,Tue,Wed,Thu,Fri,'),
(2, '2016-05-05', '2016-05-17', 9, 'Mon,Tue,Wed,Thu,Fri,'),
(3, '2016-05-02', '2016-05-16', 7, 'Mon,Wed,Fri,'),
(4, '2016-05-02', '2016-05-06', 5, 'Mon,Tue,Wed,Thu,Fri,'),
(4, '2016-05-10', '2016-05-13', 4, 'Tue,Wed,Thu,Fri,'),
(5, '2016-05-02', '2016-05-06', 5, 'Mon,Tue,Wed,Thu,Fri,'),
(5, '2016-05-10', '2016-05-20', 9, 'Mon,Tue,Wed,Thu,Fri,'),
(6, '2016-05-05', '2016-05-17', 9, 'Mon,Tue,Wed,Thu,Fri,'),
(6, '2016-06-06', '2016-06-17', 10, 'Mon,Tue,Wed,Thu,Fri,'),
(7, '2016-05-02', '2016-05-13', 12, 'Sun,Mon,Tue,Wed,Thu,Fri,Sat,'),
(8, '2016-04-30', '2016-05-14', 15, 'Sun,Mon,Tue,Wed,Thu,Fri,Sat,'),
(9, '2016-05-02', '2016-05-17', 8, 'Mon,Tue,Wed,'),
(10,'2016-05-05', '2016-05-22', 12, 'Sun,Thu,Fri,Sat,'),
(11,'2016-05-03', '2016-05-17', 3, 'Tue,'),
(11,'2016-05-19', '2016-06-02', 3, 'Thu,'),
(12,'2016-05-02', '2016-05-06', 5, 'Mon,Tue,Wed,Thu,Fri,'),
(12,'2016-05-16', '2016-05-20', 5, 'Mon,Tue,Wed,Thu,Fri,');
SELECT ContractID, StartDT, EndDT, DayCount, WeekDays
FROM @Dst
ORDER BY ContractID, StartDT;
답변 비교
실제 테이블 @Src
에는 distinct 403,555
행이 15,857
있습니다 ContractIDs
. 모든 답변은 (적어도 내 데이터에 대해서는) 올바른 결과를 생성하며 모두 빠르지 만 최적 성은 다릅니다. 간격이 적을수록 좋습니다. 나는 호기심을 위해 런타임을 포함시켰다. 주요 초점은 속도가 아니라 정확하고 최적의 결과입니다 (시간이 오래 걸리지 않는 한-Ziggy Crueltyfree Zeitgeister의 비 재귀 쿼리를 10 분 후에 중지했습니다).
+--------------------------------------------------------+-----------+---------+
| Answer | Intervals | Seconds |
+--------------------------------------------------------+-----------+---------+
| Ziggy Crueltyfree Zeitgeister | 25751 | 7.88 |
| While loop | | |
| | | |
| Ziggy Crueltyfree Zeitgeister | 25751 | 8.27 |
| Recursive | | |
| | | |
| Michael Green | 25751 | 22.63 |
| Recursive | | |
| | | |
| Geoff Patterson | 26670 | 4.79 |
| Weekly gaps-and-islands with merging of partial weeks | | |
| | | |
| Vladimir Baranov | 34560 | 4.03 |
| Daily, then weekly gaps-and-islands | | |
| | | |
| Mikael Eriksson | 35840 | 0.65 |
| Weekly gaps-and-islands | | |
+--------------------------------------------------------+-----------+---------+
| Vladimir Baranov | 25751 | 121.51 |
| Cursor | | |
+--------------------------------------------------------+-----------+---------+
@Dst
합니다. 일정의 처음 2 주에는 시간 이 있으므로이 주 동안 Tue
가질 수 없습니다 WeekDays=Tue,Thu,
. 스케쥴의 마지막 2 주만이므로이 주 Thu
를 다시 가질 수 없습니다 WeekDays=Tue,Thu,
. 단지 : 그것을 위해 하위 최적의 솔루션은 세 개의 행이 될 것입니다 Tue
후 첫 2 주 동안 Tue,Thu,
모두 가지고 셋째 주 Tue
와 Thu
다음, 단지 Thu
지난 2 주 동안.
ContractID
변경하는 경우 간격 예정된 요일 목록에 차이가있는 경우 7 일을 초과하여 새 요일이 표시되지 않았습니다.
(11,'2016-05-03', '2016-05-17', 3, 'Tue,'), (11,'2016-05-19', '2016-06-02', 3, 'Thu,');
@Dst에서 하나의 행 수Tue, Thu,
?