두 시스템간에 데이터를 변환해야합니다.
첫 번째 시스템은 일정을 일반 날짜 목록으로 저장합니다. 스케줄에 포함 된 각 날짜는 한 행입니다. 날짜 순서에 다양한 간격이있을 수 있습니다 (주말, 공휴일 및 더 이상 일시 중지, 일부 요일은 일정에서 제외 될 수 있음). 간격이 전혀 없으며 주말도 포함될 수 있습니다. 일정은 최대 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,?