จัดตารางเวลารายวันเป็น [วันที่เริ่ม; วันที่สิ้นสุด] ช่วงเวลาพร้อมรายการวันในสัปดาห์


18

ฉันต้องการแปลงข้อมูลระหว่างสองระบบ

ระบบแรกจัดเก็บตารางเวลาเป็นรายการธรรมดาของวันที่ แต่ละวันที่รวมอยู่ในกำหนดการคือหนึ่งแถว อาจมีช่องว่างต่าง ๆ ในลำดับของวันที่ (วันหยุดสุดสัปดาห์วันหยุดราชการและหยุดอีกต่อไปบางวันของสัปดาห์อาจไม่รวมอยู่ในตาราง) ไม่มีช่องว่างเลยแม้แต่วันหยุดสุดสัปดาห์ก็สามารถรวม กำหนดการอาจนานถึง 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วิธีแก้ไขปัญหานี้ ฉันพยายามทำสองรอบ ในรอบแรกฉันพบเกาะของวันที่เรียบง่ายติดต่อกันนั่นคือจุดสิ้นสุดของเกาะคือช่องว่างในลำดับของวันไม่ว่าจะเป็นวันหยุดสุดสัปดาห์วันหยุดราชการหรืออย่างอื่น WeekDaysสำหรับแต่ละเกาะพบเช่นผมสร้างรายการคั่นด้วยเครื่องหมายจุลภาคที่แตกต่างกัน WeekDaysในกลุ่มที่สองผมผ่านพบเกาะต่อไปโดยมองไปที่ช่องว่างในลำดับของตัวเลขสัปดาห์หรือการเปลี่ยนแปลงใน

ด้วยวิธีนี้แต่ละสัปดาห์บางส่วนจะสิ้นสุดลงในช่วงเวลาพิเศษดังที่แสดงไว้ด้านบนเนื่องจากแม้ว่าตัวเลขของสัปดาห์จะต่อเนื่องกัน แต่การWeekDaysเปลี่ยนแปลง นอกจากนี้อาจมีช่องว่างปกติภายในหนึ่งสัปดาห์ (ดูContractID=3ในตัวอย่างข้อมูลซึ่งมีข้อมูลเพียงอย่างเดียวMon,Wed,Fri,) และวิธีการนี้จะสร้างช่วงเวลาแยกต่างหากสำหรับแต่ละวันในตารางดังกล่าว ในด้านสว่างมันสร้างหนึ่งช่วงเวลาถ้ากำหนดการไม่มีช่องว่างใด ๆ เลย (ดูContractID=7ในตัวอย่างข้อมูลที่มีวันหยุดสุดสัปดาห์) และในกรณีนั้นมันไม่สำคัญว่าสัปดาห์เริ่มต้นหรือสิ้นสุดจะเป็นบางส่วน

โปรดดูตัวอย่างอื่น ๆ ในสคริปต์ด้านล่างเพื่อทำความเข้าใจเกี่ยวกับสิ่งที่ฉันทำ คุณจะเห็นว่าไม่รวมวันหยุดสุดสัปดาห์บ่อยนัก แต่วันอื่น ๆ ในสัปดาห์ก็ไม่สามารถยกเว้นได้ ในตัวอย่างที่ 3 เท่านั้นMon, WedและFriเป็นส่วนหนึ่งของตาราง นอกจากนี้ยังสามารถรวมวันหยุดสุดสัปดาห์ได้ในตัวอย่างที่ 7 การแก้ปัญหาควรปฏิบัติต่อทุกวันในสัปดาห์อย่างเท่าเทียมกัน สามารถรวมหรือแยกวันใดก็ได้ในสัปดาห์ออกจากกำหนดการ

ในการตรวจสอบว่ารายการช่วงเวลาที่สร้างขึ้นอธิบายกำหนดการที่กำหนดอย่างถูกต้องคุณสามารถใช้รหัสหลอกต่อไปนี้:

  • วนซ้ำทุกช่วงเวลา
  • สำหรับแต่ละช่วงเวลาวนซ้ำผ่านวันที่ในปฏิทินทั้งหมดระหว่างวันที่เริ่มต้นและวันที่สิ้นสุด (รวมอยู่ด้วย)
  • สำหรับการตรวจสอบแต่ละวันถ้าวันของสัปดาห์ที่เป็น บริษัท WeekDaysจดทะเบียนใน ถ้าใช่แล้ววันนี้จะรวมอยู่ในกำหนดการ

หวังว่านี่จะอธิบายในกรณีที่ควรสร้างช่วงเวลาใหม่ ในตัวอย่าง 4 และ 5 หนึ่งวันจันทร์ ( 2016-05-09) จะถูกลบออกจากกลางตารางและไม่สามารถแสดงช่วงเวลาดังกล่าวได้ในช่วงเวลาเดียว ในตัวอย่างที่ 6 มีช่องว่างยาวในกำหนดการดังนั้นจึงจำเป็นต้องใช้ช่วงเวลาสองช่วง

ช่วงเวลาแสดงรูปแบบรายสัปดาห์ในกำหนดเวลาและเมื่อรูปแบบถูกรบกวน / เปลี่ยนช่วงเวลาใหม่จะต้องมีการเพิ่ม ในตัวอย่าง 11 สามสัปดาห์แรกมีรูปแบบนั้นแบบนี้เปลี่ยนแปลงไป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มี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                                                 |           |         |
+--------------------------------------------------------+-----------+---------+

ไม่ควร(11,'2016-05-03', '2016-05-17', 3, 'Tue,'), (11,'2016-05-19', '2016-06-02', 3, 'Thu,');อยู่ใน @Dst ด้วยTue, Thu,หรือไม่
Kin Shah

@ คินตัวอย่างที่ 11 จะต้องมี (อย่างน้อย) สองช่วงเวลา (สองแถว@Dst) สองสัปดาห์แรกของตารางมีเพียงTueคุณจึงไม่สามารถมีได้WeekDays=Tue,Thu,ในสัปดาห์นี้ สองสัปดาห์สุดท้ายของตารางมีเพียงThuคุณจึงไม่สามารถมีได้อีกWeekDays=Tue,Thu,ในสัปดาห์นี้ วิธีแก้ปัญหาย่อยที่เหมาะสมสำหรับมันจะเป็นสามแถว: เพียงแค่Tueสองสัปดาห์แรกจากนั้นTue,Thu,สำหรับสัปดาห์ที่สามที่มีทั้งสองTueและThuจากนั้นThuในช่วงสองสัปดาห์ที่ผ่านมา
Vladimir Baranov

1
คุณช่วยอธิบายอัลกอริธึมที่สัญญา 11 เป็น "เหมาะสมที่สุด" แยกออกเป็นสองช่วง คุณประสบความสำเร็จในการสมัคร C # หรือไม่? อย่างไร?
Michael Green

@MichaelGreen ขอโทษฉันไม่สามารถตอบกลับก่อนหน้านี้ ใช่รหัส C # แยกสัญญา 11 ออกเป็นสองช่วง อัลกอริทึมคร่าวๆ: ฉันวนซ้ำตามวันที่กำหนดหนึ่งต่อหนึ่งจดวันไหนของสัปดาห์ที่ฉันพบจนถึงจุดเริ่มต้นของช่วงเวลาและพิจารณาว่าฉันควรเริ่มช่วงเวลาใหม่: ถ้ามีContractIDการเปลี่ยนแปลงถ้าช่วงเวลา ไปเกิน 7 วันและไม่เห็นวันสัปดาห์ใหม่มาก่อนหากมีช่องว่างในรายการวันที่กำหนด
Vladimir Baranov

@MichaelGreen ฉันแปลงโค้ด C # ของฉันเป็นอัลกอริทึมแบบเคอร์เซอร์เพื่อดูว่ามันเปรียบเทียบกับโซลูชันอื่น ๆ ในข้อมูลจริงได้อย่างไร ฉันเพิ่มซอร์สโค้ดลงในคำตอบและผลลัพธ์ในตารางสรุปในคำถาม
Vladimir Baranov

คำตอบ:


6

อันนี้ใช้ CTE แบบเรียกซ้ำ ผลของมันเป็นเหมือนตัวอย่างในคำถาม มันเป็นฝันร้ายที่จะเกิดขึ้นกับ ... รหัสรวมถึงความคิดเห็นเพื่อความสะดวกผ่านตรรกะที่ซับซ้อนของมัน

SET DATEFIRST 1 -- Make Monday weekday=1

DECLARE @Ranked TABLE (RowID int NOT NULL IDENTITY PRIMARY KEY,                   -- Incremental uninterrupted sequence in the right order
                       ID int NOT NULL UNIQUE, ContractID int NOT NULL, dt date,  -- Original relevant values (ID is not really necessary)
                       WeekNo int NOT NULL, dowBit int NOT NULL);                 -- Useful to find gaps in days or weeks
INSERT INTO @Ranked
SELECT ID, ContractID, dt,
       DATEDIFF(WEEK, '1900-01-01', DATEADD(DAY, 1-DATEPART(dw, dt), dt)) AS WeekNo,
       POWER(2, DATEPART(dw, dt)-1) AS dowBit
FROM @Src
ORDER BY ContractID, WeekNo, dowBit

/*
Each evaluated date makes part of the carried sequence if:
  - this is not a new contract, and
    - sequence started this week, or
    - same day last week was part of the sequence, or
    - sequence started last week and today is a lower day than the accumulated weekdays list
  - and there are no sequence gaps since previous day
(otherwise it does not make part of the old sequence, so it starts a new one) */

DECLARE @RankedRanges TABLE (RowID int NOT NULL PRIMARY KEY, WeekDays int NOT NULL, StartRowID int NULL);

WITH WeeksCTE AS -- Needed for building the sequence gradually, and comparing the carried sequence (and previous day) with a current evaluated day
( 
    SELECT RowID, ContractID, dowBit, WeekNo, RowID AS StartRowID, WeekNo AS StartWN, dowBit AS WeekDays, dowBit AS StartWeekDays
    FROM @Ranked
    WHERE RowID = 1 
    UNION ALL
    SELECT RowID, ContractID, dowBit, WeekNo, StartRowID,
           CASE WHEN StartRowID IS NULL THEN StartWN ELSE WeekNo END AS WeekNo,
           CASE WHEN StartRowID IS NULL THEN WeekDays | dowBit ELSE dowBit END AS WeekDays,
           CASE WHEN StartRowID IS NOT NULL THEN dowBit WHEN WeekNo = StartWN THEN StartWeekDays | dowBit ELSE StartWeekDays END AS StartWeekDays
    FROM (
        SELECT w.*, pre.StartWN, pre.WeekDays, pre.StartWeekDays,
               CASE WHEN w.ContractID <> pre.ContractID OR     -- New contract always break the sequence
                         NOT (w.WeekNo = pre.StartWN OR        -- Same week as a new sequence always keeps the sequence
                              w.dowBit & pre.WeekDays > 0 OR   -- Days in the sequence keep the sequence (provided there are no gaps, checked later)
                              (w.WeekNo = pre.StartWN+1 AND (w.dowBit-1) & pre.StartWeekDays = 0)) OR -- Days in the second week when less than a week passed since the sequence started remain in sequence
                         (w.WeekNo > pre.StartWN AND -- look for gap after initial week
                          w.WeekNo > pre.WeekNo+1 OR -- look for full-week gaps
                          (w.WeekNo = pre.WeekNo AND                            -- when same week as previous day,
                           ((w.dowBit-1) ^ (pre.dowBit*2-1)) & pre.WeekDays > 0 -- days between this and previous weekdays, compared to current series
                          ) OR
                          (w.WeekNo > pre.WeekNo AND                                   -- when following week of previous day,
                           ((-1 ^ (pre.dowBit*2-1)) | (w.dowBit-1)) & pre.WeekDays > 0 -- days between this and previous weekdays, compared to current series
                          )) THEN w.RowID END AS StartRowID
        FROM WeeksCTE pre
        JOIN @Ranked w ON (w.RowID = pre.RowID + 1)
        ) w
) 
INSERT INTO @RankedRanges -- days sequence and starting point of each sequence
SELECT RowID, WeekDays, StartRowID
--SELECT *
FROM WeeksCTE
OPTION (MAXRECURSION 0)

--SELECT * FROM @RankedRanges

DECLARE @Ranges TABLE (RowNo int NOT NULL IDENTITY PRIMARY KEY, RowID int NOT NULL);

INSERT INTO @Ranges       -- @RankedRanges filtered only by start of each range, with numbered rows to easily find the end of each range
SELECT StartRowID
FROM @RankedRanges
WHERE StartRowID IS NOT NULL
ORDER BY 1

-- Final result putting everything together
SELECT rs.ContractID, rs.dt AS StartDT, re.dt AS EndDT, re.RowID-rs.RowID+1 AS DayCount,
       CASE WHEN rr.WeekDays & 64 > 0 THEN 'Sun,' ELSE '' END +
       CASE WHEN rr.WeekDays & 1 > 0 THEN 'Mon,' ELSE '' END +
       CASE WHEN rr.WeekDays & 2 > 0 THEN 'Tue,' ELSE '' END +
       CASE WHEN rr.WeekDays & 4 > 0 THEN 'Wed,' ELSE '' END +
       CASE WHEN rr.WeekDays & 8 > 0 THEN 'Thu,' ELSE '' END +
       CASE WHEN rr.WeekDays & 16 > 0 THEN 'Fri,' ELSE '' END +
       CASE WHEN rr.WeekDays & 32 > 0 THEN 'Sat,' ELSE '' END AS WeekDays
FROM (
    SELECT r.RowID AS StartRowID, COALESCE(pos.RowID-1, (SELECT MAX(RowID) FROM @Ranked)) AS EndRowID
    FROM @Ranges r
    LEFT JOIN @Ranges pos ON (pos.RowNo = r.RowNo + 1)
    ) g
JOIN @Ranked rs ON (rs.RowID = g.StartRowID)
JOIN @Ranked re ON (re.RowID = g.EndRowID)
JOIN @RankedRanges rr ON (rr.RowID = re.RowID)


กลยุทธ์อื่น

อันนี้ควรจะเร็วกว่าอย่างมากก่อนหน้านี้เพราะมันไม่ได้ขึ้นอยู่กับ CTE แบบเรียกซ้ำที่ช้าใน SQL Server 2008 ถึงแม้ว่ามันจะใช้กลยุทธ์เดียวกันมากกว่าหรือน้อยกว่าก็ตาม

มีWHILEลูป (ฉันไม่สามารถหาวิธีที่จะหลีกเลี่ยงได้) แต่จะลดจำนวนการวนซ้ำลงซ้ำ (ลำดับที่มากที่สุด (ลบหนึ่ง) ในสัญญาใด ๆ ก็ตาม

มันเป็นกลยุทธ์ที่ง่ายและสามารถใช้สำหรับลำดับไม่ว่าจะสั้นกว่าหรือยาวกว่าหนึ่งสัปดาห์ (แทนที่การเกิดขึ้นของค่าคงที่ 7 สำหรับหมายเลขอื่นใด ๆ และdowBitคำนวณจาก MODULUS x DayNoมากกว่าDATEPART(wk)) และสูงสุด 32

SET DATEFIRST 1 -- Make Monday weekday=1

-- Get the minimum information needed to calculate sequences
DECLARE @Days TABLE (ContractID int NOT NULL, dt date, DayNo int NOT NULL, dowBit int NOT NULL, PRIMARY KEY (ContractID, DayNo));
INSERT INTO @Days
SELECT ContractID, dt, CAST(CAST(dt AS datetime) AS int) AS DayNo, POWER(2, DATEPART(dw, dt)-1) AS dowBit
FROM @Src

DECLARE @RangeStartFirstPass TABLE (ContractID int NOT NULL, DayNo int NOT NULL, PRIMARY KEY (ContractID, DayNo))

-- Calculate, from the above list, which days are not present in the previous 7
INSERT INTO @RangeStartFirstPass
SELECT r.ContractID, r.DayNo
FROM @Days r
LEFT JOIN @Days pr ON (pr.ContractID = r.ContractID AND pr.DayNo BETWEEN r.DayNo-7 AND r.DayNo-1) -- Last 7 days
GROUP BY r.ContractID, r.DayNo, r.dowBit
HAVING r.dowBit & COALESCE(SUM(pr.dowBit), 0) = 0

-- Update the previous list with all days that occur right after a missing day
INSERT INTO @RangeStartFirstPass
SELECT *
FROM (
    SELECT DISTINCT ContractID, (SELECT MIN(DayNo) FROM @Days WHERE ContractID = d.ContractID AND DayNo > d.DayNo + 7) AS DayNo
    FROM @Days d
    WHERE NOT EXISTS (SELECT 1 FROM @Days WHERE ContractID = d.ContractID AND DayNo = d.DayNo + 7)
    ) d
WHERE DayNo IS NOT NULL AND
      NOT EXISTS (SELECT 1 FROM @RangeStartFirstPass WHERE ContractID = d.ContractID AND DayNo = d.DayNo)

DECLARE @RangeStart TABLE (ContractID int NOT NULL, DayNo int NOT NULL, PRIMARY KEY (ContractID, DayNo));

-- Fetch the first sequence for each contract
INSERT INTO @RangeStart
SELECT ContractID, MIN(DayNo)
FROM @RangeStartFirstPass
GROUP BY ContractID

-- Add to the list above the next sequence for each contract, until all are added
-- (ensure no sequence is added with less than 7 days)
WHILE @@ROWCOUNT > 0
  INSERT INTO @RangeStart
  SELECT f.ContractID, MIN(f.DayNo)
  FROM (SELECT ContractID, MAX(DayNo) AS DayNo FROM @RangeStart GROUP BY ContractID) s
  JOIN @RangeStartFirstPass f ON (f.ContractID = s.ContractID AND f.DayNo > s.DayNo + 7)
  GROUP BY f.ContractID

-- Summarise results
SELECT ContractID, StartDT, EndDT, DayCount,
       CASE WHEN WeekDays & 64 > 0 THEN 'Sun,' ELSE '' END +
       CASE WHEN WeekDays & 1 > 0 THEN 'Mon,' ELSE '' END +
       CASE WHEN WeekDays & 2 > 0 THEN 'Tue,' ELSE '' END +
       CASE WHEN WeekDays & 4 > 0 THEN 'Wed,' ELSE '' END +
       CASE WHEN WeekDays & 8 > 0 THEN 'Thu,' ELSE '' END +
       CASE WHEN WeekDays & 16 > 0 THEN 'Fri,' ELSE '' END +
       CASE WHEN WeekDays & 32 > 0 THEN 'Sat,' ELSE '' END AS WeekDays
FROM (
    SELECT r.ContractID,
           MIN(d.dt) AS StartDT,
           MAX(d.dt) AS EndDT,
           COUNT(*) AS DayCount,
           SUM(DISTINCT d.dowBit) AS WeekDays
    FROM (SELECT *, COALESCE((SELECT MIN(DayNo) FROM @RangeStart WHERE ContractID = rs.ContractID AND DayNo > rs.DayNo), 999999) AS DayEnd FROM @RangeStart rs) r
    JOIN @Days d ON (d.ContractID = r.ContractID AND d.DayNo BETWEEN r.DayNo AND r.DayEnd-1)
    GROUP BY r.ContractID, r.DayNo
    ) d
ORDER BY ContractID, StartDT

@VladimirBaranov ฉันได้เพิ่มกลยุทธ์ใหม่ซึ่งน่าจะเร็วกว่านี้มาก แจ้งให้เราทราบว่ามันให้คะแนนกับข้อมูลจริงของคุณอย่างไร!
Ziggy Crueltyfree Zeitgeister

2
@ZiggyCrueltyfreeZeitgeister ฉันตรวจสอบโซลูชันล่าสุดของคุณและเพิ่มลงในรายการคำตอบทั้งหมดในคำถาม มันให้ผลลัพธ์ที่ถูกต้องและมีจำนวนช่วงเวลาเดียวกันกับ CTE แบบเรียกซ้ำและความเร็วใกล้เคียงกันมาก อย่างที่ฉันพูดความเร็วไม่สำคัญตราบใดที่มันสมเหตุสมผล 1 วินาทีหรือ 10 วินาทีไม่สำคัญสำหรับฉัน
Vladimir Baranov

คำตอบอื่น ๆ ก็ยอดเยี่ยมและมีประโยชน์เช่นกันและฉันหวังว่าฉันจะมอบรางวัลให้มากกว่าหนึ่งคำตอบ ฉันเลือกคำตอบนี้เพราะในขณะที่ฉันเริ่มรับรางวัลฉันไม่ได้คิดถึง CTE ซ้ำและคำตอบนี้เป็นคำตอบแรกที่แนะนำและมีวิธีแก้ปัญหาการทำงาน CTE แบบพูดซ้ำอย่างเคร่งครัดนั้นไม่ใช่โซลูชันแบบตั้งค่า แต่ให้ผลลัพธ์ที่ดีที่สุดและรวดเร็วพอสมควร คำตอบโดย @GeoffPatterson เป็นสิ่งที่ดี แต่ให้ผลลัพธ์ที่ดีที่สุดที่น้อยลงและตรงไปตรงมาพูดวิธีที่ซับซ้อนเกินไป
Vladimir Baranov

5

ไม่ว่าสิ่งที่คุณกำลังมองหา แต่อาจเป็นที่สนใจของคุณ

แบบสอบถามสร้างสัปดาห์ด้วยสตริงที่คั่นด้วยเครื่องหมายจุลภาคสำหรับวันที่ใช้ในแต่ละสัปดาห์ Weekdaysจากนั้นก็พบว่าเกาะของสัปดาห์ติดต่อกันที่ใช้รูปแบบเดียวกันใน

with Weeks as
(
  select T.*,
         row_number() over(partition by T.ContractID, T.WeekDays order by T.WeekNumber) as rn
  from (
       select S1.ContractID,
              min(S1.dt) as StartDT,
              max(S1.dt) as EndDT,
              datediff(day, 0, S1.dt) / 7 as WeekNumber, -- Number of weeks since '1900-01-01 (a monday)'
              count(*) as DayCount,
              stuff((
                    select ','+S2.dowChar
                    from @Src as S2
                    where S2.ContractID = S1.ContractID and
                          S2.dt between min(S1.dt) and max(S1.dt)
                    order by S2.dt
                    for xml path('')
                    ), 1, 1, '') as WeekDays
       from @Src as S1
       group by S1.ContractID, 
                datediff(day, 0, S1.dt) / 7
       ) as T
)
select W.ContractID,
       min(W.StartDT) as StartDT,
       max(W.EndDT) as EndDT,
       count(*) * W.DayCount as DayCount,
       W.WeekDays
from Weeks as W
group by W.ContractID,
         W.WeekDays,
         W.DayCount,
         W.rn - W.WeekNumber
order by W.ContractID,
         min(W.WeekNumber);

ผลลัพธ์:

ContractID  StartDT    EndDT      DayCount    WeekDays
----------- ---------- ---------- ----------- -----------------------------
1           2016-05-02 2016-05-13 10          Mon,Tue,Wed,Thu,Fri
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
3           2016-05-02 2016-05-13 6           Mon,Wed,Fri
3           2016-05-16 2016-05-16 1           Mon
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-13 4           Tue,Wed,Thu,Fri
5           2016-05-16 2016-05-20 5           Mon,Tue,Wed,Thu,Fri
6           2016-05-05 2016-05-06 2           Thu,Fri
6           2016-05-09 2016-05-13 5           Mon,Tue,Wed,Thu,Fri
6           2016-05-16 2016-05-17 2           Mon,Tue
6           2016-06-06 2016-06-17 10          Mon,Tue,Wed,Thu,Fri
7           2016-05-02 2016-05-08 7           Mon,Tue,Wed,Thu,Fri,Sat,Sun
7           2016-05-09 2016-05-13 5           Mon,Tue,Wed,Thu,Fri
8           2016-04-30 2016-05-01 2           Sat,Sun
8           2016-05-02 2016-05-08 7           Mon,Tue,Wed,Thu,Fri,Sat,Sun
8           2016-05-09 2016-05-14 6           Mon,Tue,Wed,Thu,Fri,Sat
9           2016-05-02 2016-05-11 6           Mon,Tue,Wed
9           2016-05-16 2016-05-17 2           Mon,Tue
10          2016-05-05 2016-05-22 12          Thu,Fri,Sat,Sun
11          2016-05-03 2016-05-10 2           Tue
11          2016-05-17 2016-05-19 2           Tue,Thu
11          2016-05-26 2016-06-02 2           Thu

ContractID = 2แสดงให้เห็นถึงความแตกต่างในผลลัพธ์เมื่อเปรียบเทียบกับสิ่งที่คุณต้องการ สัปดาห์แรกและสัปดาห์สุดท้ายจะถือว่าเป็นช่วงเวลาที่แยกกันเนื่องจากWeekDaysมีความแตกต่างกัน


ฉันมีความคิดนี้ แต่ไม่มีโอกาสลอง ขอบคุณที่ให้ข้อความค้นหาที่ใช้งานได้ ฉันชอบวิธีที่ให้ผลลัพธ์ที่มีโครงสร้างมากขึ้น ในการจัดกลุ่มข้อมูลเป็นสัปดาห์ด้านล่างจะลดความยืดหยุ่น (ในตัวอย่างช่องว่างและเกาะที่เรียบง่ายตัวอย่างที่ 7 และ 8 จะยุบลงในช่วงเวลาเดียว) แต่มันเป็นด้านที่สว่างในเวลาเดียวกัน - เราลดความซับซ้อนของ ปัญหา. ดังนั้นปัญหาที่ใหญ่ที่สุดด้วยวิธีนี้คือบางส่วนของสัปดาห์ที่จุดเริ่มต้นและจุดสิ้นสุดของตาราง บางส่วนของสัปดาห์ทำให้เกิดช่วงเวลาพิเศษ ...
Vladimir Baranov

คุณนึกถึงวิธีผนวก / กลุ่ม / รวมบางส่วนของสัปดาห์เหล่านี้ลงในตารางหลักได้หรือไม่? ฉันมีความคิดที่คลุมเครือในขั้นตอนนี้เท่านั้น หากเราพบวิธีที่จะผสานบางส่วนของสัปดาห์ได้อย่างถูกต้องผลลัพธ์ที่ได้จะใกล้เคียงที่สุด
Vladimir Baranov

@VladimirBaranov ไม่แน่ใจว่าจะทำอย่างไร ฉันจะอัปเดตคำตอบหากมีบางสิ่งในใจ
Mikael Eriksson

แนวคิดที่คลุมเครือของฉันคือสิ่งนี้: มีเพียง 7 วันในหนึ่งสัปดาห์ดังนั้นจึงWeekDaysเป็นตัวเลข 7 บิต รวมกันเพียง 128 มีเพียงคู่ที่เป็นไปได้ 128 * 128 = 16384 สร้างตารางชั่วคราวด้วยคู่ที่เป็นไปได้ทั้งหมดจากนั้นหาอัลกอริธึมตามชุดที่จะทำเครื่องหมายว่าคู่ใดสามารถรวมกันได้รูปแบบของหนึ่งสัปดาห์นั้นถูก "ปิด" โดยรูปแบบของสัปดาห์ถัดไป เข้าร่วมผลการค้นหารายสัปดาห์ปัจจุบันด้วยตนเอง (เนื่องจากไม่มีLAGในปี 2008) และใช้ตารางอุณหภูมินั้นเพื่อตัดสินใจว่าคู่ใดจะรวม ... ไม่แน่ใจว่าแนวคิดนี้มีข้อดีหรือไม่
Vladimir Baranov

5

ฉันลงเอยด้วยวิธีการที่ให้ทางออกที่ดีที่สุดในกรณีนี้และฉันคิดว่าโดยทั่วไปแล้วจะทำได้ดี อย่างไรก็ตามการแก้ปัญหาค่อนข้างยาวดังนั้นมันน่าสนใจที่จะดูว่าคนอื่นมีวิธีการที่แตกต่างและกระชับกว่าหรือไม่

นี่คือสคริปต์ที่มีวิธีการแก้ปัญหาเต็ม

และนี่คือโครงร่างของอัลกอริทึม:

  • หมุนชุดข้อมูลเพื่อให้มีแถวเดียวแทนแต่ละสัปดาห์
  • คำนวณเกาะต่างๆในแต่ละสัปดาห์ ContractId
  • รวมสัปดาห์ที่อยู่ติดกันใด ๆ ที่ตกอยู่ในสภาพเดียวกันContractIdและมีเหมือนกันWeekDays
  • สำหรับสัปดาห์เดียว (ยังไม่รวม) ซึ่งการจัดกลุ่มก่อนหน้านี้อยู่บนเกาะเดียวกันและWeekDaysสัปดาห์เดียวตรงกับชุดย่อยนำของWeekDaysกลุ่มก่อนหน้านี้รวมเข้ากับการจัดกลุ่มก่อนหน้านั้น
  • สำหรับสัปดาห์เดียว (ยังไม่รวม) ซึ่งการจัดกลุ่มถัดไปอยู่บนเกาะเดียวกันและWeekDaysสัปดาห์เดียวตรงกับชุดย่อยต่อท้ายWeekDaysของการจัดกลุ่มถัดไปรวมเข้ากับการจัดกลุ่มถัดไป
  • สำหรับสองสัปดาห์ที่อยู่ติดกันบนเกาะเดียวกันที่ไม่มีการรวมเข้าด้วยกันให้รวมเข้าด้วยกันหากเป็นทั้งสองสัปดาห์บางส่วนที่สามารถนำมารวมกันได้ (เช่น "จันทร์จันทร์อังคารพุธพุธพฤหัสบดี" และ "พุธพุธพฤหัสบดีเสาร์)" )
  • สำหรับสัปดาห์เดียวที่เหลืออยู่ (ยังไม่ได้รวม) หากเป็นไปได้แบ่งสัปดาห์เป็นสองส่วนและรวมทั้งสองส่วนส่วนแรกเข้ากลุ่มก่อนหน้านี้บนเกาะเดียวกันและส่วนที่สองเป็นการรวมกลุ่มต่อไปนี้บนเกาะเดียวกัน

ขอบคุณสำหรับความยาวที่ยอดเยี่ยมเช่นนี้ในการแก้ปัญหาการทำงาน เป็นเรื่องเล็กน้อยที่ต้องพูดตามตรง ฉันสงสัยว่ามันจะไม่ง่ายที่จะรวมบางส่วนของสัปดาห์ แต่ฉันไม่สามารถคาดหวังว่ามันจะซับซ้อนเกินไป ฉันยังมีความหวังว่ามันจะทำได้ง่ายขึ้น แต่ฉันไม่มีความคิดที่ชัดเจน
Vladimir Baranov

ตรวจสอบอย่างรวดเร็วยืนยันว่าจะสร้างผลลัพธ์ที่คาดหวังสำหรับข้อมูลตัวอย่างซึ่งดีมาก แต่ฉันสังเกตเห็นว่าตารางบางอย่างไม่ได้รับการจัดการอย่างเหมาะสม ตัวอย่างที่ง่ายที่สุด: (1214,12,'2016-05-06', 'Fri', 6), (1225,12,'2016-05-09', 'Mon', 2),. อาจแสดงเป็นช่วงเวลาเดียว แต่โซลูชันของคุณสร้างสองช่วง ฉันยอมรับตัวอย่างนี้ไม่ได้อยู่ในข้อมูลตัวอย่างและมันไม่สำคัญ ฉันจะพยายามใช้งานโซลูชันของคุณกับข้อมูลจริง
Vladimir Baranov

ฉันขอขอบคุณคำตอบของคุณ ตอนที่ฉันเริ่มรับรางวัลฉันไม่ได้คิดถึง CTE และ Ziggy Crueltyfree Zeitgeister เป็นคนแรกที่แนะนำและนำเสนอวิธีแก้ปัญหาการทำงาน การพูดอย่างซ้ำซาก CTE แบบเรียกซ้ำไม่ใช่โซลูชันแบบตั้งค่า แต่ให้ผลลัพธ์ที่ดีที่สุดมีความซับซ้อนพอสมควรและรวดเร็วพอสมควร คำตอบของคุณถูกตั้งค่า แต่กลับซับซ้อนเกินไปจนไม่สามารถใช้งานได้จริง ฉันหวังว่าฉันสามารถแบ่งเงินรางวัล แต่น่าเสียดายที่มันไม่ได้รับอนุญาต
Vladimir Baranov

@VladimirBaranov ไม่มีปัญหาเงินรางวัลคือ 100% ของคุณที่จะใช้ตามที่คุณต้องการ เหตุผลที่ฉันชอบคำถามที่ให้ความโปรดปรานเป็นเพราะคนที่ถามคำถามนั้นมักจะมีส่วนร่วมมากกว่าคำถามทั่วไป อย่าสนใจเกี่ยวกับประเด็นต่าง ๆ มากเกินไป ฉันเห็นด้วยอย่างยิ่งว่าวิธีนี้ไม่ใช่โซลูชันที่ฉันจะใช้ในรหัสการผลิตของฉัน เป็นการสำรวจความคิดที่เป็นไปได้ แต่จบลงด้วยความซับซ้อน
Geoff Patterson

3

ฉันไม่สามารถเข้าใจตรรกะที่อยู่เบื้องหลังการจัดกลุ่มสัปดาห์ด้วยช่องว่างหรือสัปดาห์กับวันหยุดสุดสัปดาห์ (เช่นเมื่อมีสองสัปดาห์ติดต่อกันกับวันหยุดสุดสัปดาห์

แบบสอบถามต่อไปนี้สร้างผลลัพธ์ที่ต้องการยกเว้นว่าจะจัดกลุ่มเฉพาะวันทำงานติดต่อกันและกลุ่มสัปดาห์อาทิตย์ - เสาร์ (ไม่ใช่จันทร์ - อาทิตย์) ในขณะที่ไม่ตรงตามที่คุณต้องการบางทีนี่อาจเป็นเคล็ดลับบางอย่างสำหรับกลยุทธ์ที่แตกต่าง การจัดกลุ่มของวันที่มาจากที่นี่ ฟังก์ชั่นการเรียงหน้าต่างที่ใช้ควรทำงานกับ SQLServer 2008 แต่ฉันไม่มีเวอร์ชั่นนั้นเพื่อทดสอบว่ามันใช้งานได้จริงหรือไม่

WITH 
  mysrc AS (
    SELECT *, RANK() OVER (PARTITION BY ContractID ORDER BY DT) AS rank
    FROM @Src
    ),
  prepos AS (
    SELECT s.*, pos.ID AS posid
    FROM mysrc s
    LEFT JOIN mysrc pos ON (pos.ContractID = s.ContractID AND pos.rank = s.rank+1 AND (pos.DowInt = s.DowInt+1 OR pos.DowInt = 2 AND s.DowInt=6))
    ),
  grped AS (
    SELECT TOP 100 *, (SELECT COUNT(CASE WHEN posid IS NULL THEN 1 END) FROM prepos WHERE contractid = p.contractid AND rank < p.rank) as grp
    FROM prepos p
    ORDER BY ContractID, DT
    )
SELECT ContractID, min(dt) AS StartDT, max(dt) AS EndDT, count(*) AS DayCount,
       STUFF( (SELECT ', ' + dowchar
               FROM (
                 SELECT TOP 100 dowint, dowchar 
                 FROM grped 
                 WHERE ContractID = g.ContractID AND grp = g.grp 
                 GROUP BY dowint, dowchar 
                 ORDER BY 1
                 ) a 
               FOR XML PATH(''), TYPE).value('.','varchar(max)'), 1, 2, '') AS WeekDays
FROM grped g
GROUP BY ContractID, grp
ORDER BY 1, 2

ผลลัพธ์

+------------+------------+------------+----------+-----------------------------------+
| ContractID | StartDT    | EndDT      | DayCount | WeekDays                          |
+------------+------------+------------+----------+-----------------------------------+
| 1          | 2/05/2016  | 13/05/2016 | 10       | Mon, Tue, Wed, Thu, Fri           |
| 2          | 5/05/2016  | 17/05/2016 | 9        | Mon, Tue, Wed, Thu, Fri           |
| 3          | 2/05/2016  | 2/05/2016  | 1        | Mon                               |
| 3          | 4/05/2016  | 4/05/2016  | 1        | Wed                               |
| 3          | 6/05/2016  | 9/05/2016  | 2        | Mon, Fri                          |
| 3          | 11/05/2016 | 11/05/2016 | 1        | Wed                               |
| 3          | 13/05/2016 | 16/05/2016 | 2        | Mon, Fri                          |
| 4          | 2/05/2016  | 6/05/2016  | 5        | Mon, Tue, Wed, Thu, Fri           |
| 4          | 10/05/2016 | 13/05/2016 | 4        | Tue, Wed, Thu, Fri                |
| 5          | 2/05/2016  | 6/05/2016  | 5        | Mon, Tue, Wed, Thu, Fri           |
| 5          | 10/05/2016 | 20/05/2016 | 9        | Mon, Tue, Wed, Thu, Fri           |
| 6          | 5/05/2016  | 17/05/2016 | 9        | Mon, Tue, Wed, Thu, Fri           |
| 6          | 6/06/2016  | 17/06/2016 | 10       | Mon, Tue, Wed, Thu, Fri           |
| 7          | 2/05/2016  | 7/05/2016  | 6        | Mon, Tue, Wed, Thu, Fri, Sat      |
| 7          | 8/05/2016  | 13/05/2016 | 6        | Sun, Mon, Tue, Wed, Thu, Fri      |
| 8          | 30/04/2016 | 30/04/2016 | 1        | Sat                               |
| 8          | 1/05/2016  | 7/05/2016  | 7        | Sun, Mon, Tue, Wed, Thu, Fri, Sat |
| 8          | 8/05/2016  | 14/05/2016 | 7        | Sun, Mon, Tue, Wed, Thu, Fri, Sat |
| 9          | 2/05/2016  | 4/05/2016  | 3        | Mon, Tue, Wed                     |
| 9          | 9/05/2016  | 10/05/2016 | 2        | Mon, Tue                          |
+------------+------------+------------+----------+-----------------------------------+

การอภิปรายในคำตอบนี้ได้รับการย้ายไปแชท
พอลไวท์พูดว่า GoFundMonica

3

เพื่อความสมบูรณ์นี่เป็นแนวทางสองgaps-and-islandsทางที่ฉันลองด้วยตัวเองก่อนถามคำถามนี้

ในขณะที่ฉันกำลังทดสอบกับข้อมูลจริงฉันพบบางกรณีเมื่อมันให้ผลลัพธ์ที่ไม่ถูกต้องและแก้ไขมัน

นี่คืออัลกอริทึม:

  • สร้างเกาะของวันติดต่อกัน ( CTE_ContractDays, CTE_DailyRN, CTE_DailyIslands) และคำนวณจำนวนสัปดาห์สำหรับแต่ละวันเริ่มต้นและสิ้นสุดของเกาะ ตัวเลขในที่นี้คำนวณโดยสมมติว่าวันจันทร์เป็นวันแรกของสัปดาห์
  • หากกำหนดการมีวันที่ไม่ต่อเนื่องกันภายในสัปดาห์เดียวกัน (เช่นในตัวอย่าง 3) ระยะก่อนหน้านี้จะสร้างแถวหลายแถวสำหรับสัปดาห์เดียวกัน แถวกลุ่มที่จะมีเพียงหนึ่งแถวต่อสัปดาห์ ( CTE_Weeks)
  • สำหรับแต่ละแถวจากระยะก่อนหน้าสร้างรายการที่คั่นด้วยเครื่องหมายจุลภาคของวันในสัปดาห์ ( CTE_FirstResult)
  • ช่องว่างที่สองและหมู่เกาะไปยังกลุ่มต่อเนื่องเป็นสัปดาห์ด้วยWeekDays( CTE_SecondRN, CTE_Schedules)

มันจัดการกรณีที่ดีเมื่อไม่มีการหยุดชะงักในรูปแบบรายสัปดาห์ (1, 7, 8, 10, 12) มันจัดการกรณีที่ดีเมื่อรูปแบบมีวันที่ไม่ต่อเนื่อง (3)

แต่น่าเสียดายที่มันสร้างช่วงเวลาพิเศษสำหรับบางส่วนของสัปดาห์ (2, 3, 5, 6, 9, 11)

WITH
CTE_ContractDays
AS
(
    SELECT
         S.ContractID
        ,MIN(S.dt) OVER (PARTITION BY S.ContractID) AS ContractMinDT
        ,S.dt
        ,ROW_NUMBER() OVER (PARTITION BY S.ContractID ORDER BY S.dt) AS rn1
        ,DATEDIFF(day, '2001-01-01', S.dt) AS DayNumber
        ,S.dowChar
        ,S.dowInt
    FROM
        @Src AS S
)
,CTE_DailyRN
AS
(
    SELECT
        DayNumber - rn1 AS WeekGroupNumber
        ,ROW_NUMBER() OVER (
            PARTITION BY
                ContractID
                ,DayNumber - rn1
            ORDER BY dt) AS rn2
        ,ContractID
        ,ContractMinDT
        ,dt
        ,rn1
        ,DayNumber
        ,dowChar
        ,dowInt
    FROM CTE_ContractDays
)
,CTE_DailyIslands
AS
(
    SELECT
        ContractID
        ,ContractMinDT
        ,MIN(dt) AS MinDT
        ,MAX(dt) AS MaxDT
        ,COUNT(*) AS DayCount
        -- '2001-01-01' is Monday
        ,DATEDIFF(day, '2001-01-01', MIN(dt)) / 7 AS WeekNumberMin
        ,DATEDIFF(day, '2001-01-01', MAX(dt)) / 7 AS WeekNumberMax
    FROM CTE_DailyRN
    GROUP BY
        ContractID
        ,rn1-rn2
        ,ContractMinDT
)
,CTE_Weeks
AS
(
    SELECT
        ContractID
        ,ContractMinDT
        ,MIN(MinDT) AS MinDT
        ,MAX(MaxDT) AS MaxDT
        ,SUM(DayCount) AS DayCount
        ,WeekNumberMin
        ,WeekNumberMax
    FROM CTE_DailyIslands
    GROUP BY
        ContractID
        ,ContractMinDT
        ,WeekNumberMin
        ,WeekNumberMax
)
,CTE_FirstResult
AS
(
    SELECT
        ContractID
        ,ContractMinDT
        ,MinDT
        ,MaxDT
        ,DayCount
        ,CA_Data.XML_Value AS DaysOfWeek
        ,WeekNumberMin AS WeekNumber
        ,ROW_NUMBER() OVER(PARTITION BY ContractID ORDER BY MinDT) AS rn1
    FROM
        CTE_Weeks
        CROSS APPLY
        (
            SELECT CAST(CTE_ContractDays.dowChar AS varchar(8000)) + ',' AS dw
            FROM CTE_ContractDays
            WHERE
                    CTE_ContractDays.ContractID = CTE_Weeks.ContractID
                AND CTE_ContractDays.dt >= CTE_Weeks.MinDT
                AND CTE_ContractDays.dt <= CTE_Weeks.MaxDT
            GROUP BY
                CTE_ContractDays.dowChar
                ,CTE_ContractDays.dowInt
            ORDER BY CTE_ContractDays.dowInt
            FOR XML PATH(''), TYPE
        ) AS CA_XML(XML_Value)
        CROSS APPLY
        (
            SELECT CA_XML.XML_Value.value('.', 'VARCHAR(8000)')
        ) AS CA_Data(XML_Value)
)
,CTE_SecondRN
AS
(
    SELECT 
        ContractID
        ,ContractMinDT
        ,MinDT
        ,MaxDT
        ,DayCount
        ,DaysOfWeek
        ,WeekNumber
        ,rn1
        ,WeekNumber - rn1 AS SecondGroupNumber
        ,ROW_NUMBER() OVER (
            PARTITION BY
                ContractID
                ,DaysOfWeek
                ,DayCount
                ,WeekNumber - rn1
            ORDER BY MinDT) AS rn2
    FROM CTE_FirstResult
)
,CTE_Schedules
AS
(
    SELECT
        ContractID
        ,MIN(MinDT) AS StartDT
        ,MAX(MaxDT) AS EndDT
        ,SUM(DayCount) AS DayCount
        ,DaysOfWeek
    FROM CTE_SecondRN
    GROUP BY
        ContractID
        ,DaysOfWeek
        ,rn1-rn2
)
SELECT
    ContractID
    ,StartDT
    ,EndDT
    ,DayCount
    ,DaysOfWeek AS WeekDays
FROM CTE_Schedules
ORDER BY
    ContractID
    ,StartDT
;

ผลลัพธ์

+------------+------------+------------+----------+------------------------------+
| ContractID |  StartDT   |   EndDT    | DayCount |           WeekDays           |
+------------+------------+------------+----------+------------------------------+
|          1 | 2016-05-02 | 2016-05-13 |       10 | Mon,Tue,Wed,Thu,Fri,         |
|          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,                     |
|          3 | 2016-05-02 | 2016-05-13 |        6 | Mon,Wed,Fri,                 |
|          3 | 2016-05-16 | 2016-05-16 |        1 | Mon,                         |
|          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-13 |        4 | Tue,Wed,Thu,Fri,             |
|          5 | 2016-05-16 | 2016-05-20 |        5 | Mon,Tue,Wed,Thu,Fri,         |
|          6 | 2016-05-05 | 2016-05-06 |        2 | Thu,Fri,                     |
|          6 | 2016-05-09 | 2016-05-13 |        5 | Mon,Tue,Wed,Thu,Fri,         |
|          6 | 2016-05-16 | 2016-05-17 |        2 | Mon,Tue,                     |
|          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-11 |        6 | Mon,Tue,Wed,                 |
|          9 | 2016-05-16 | 2016-05-17 |        2 | Mon,Tue,                     |
|         10 | 2016-05-05 | 2016-05-22 |       12 | Sun,Thu,Fri,Sat,             |
|         11 | 2016-05-03 | 2016-05-10 |        2 | Tue,                         |
|         11 | 2016-05-17 | 2016-05-19 |        2 | Tue,Thu,                     |
|         11 | 2016-05-26 | 2016-06-02 |        2 | 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,         |
+------------+------------+------------+----------+------------------------------+

โซลูชันที่ใช้เคอร์เซอร์

ฉันแปลงรหัส C # ของฉันเป็นอัลกอริทึมแบบเคอร์เซอร์เพื่อดูว่ามันเปรียบเทียบกับโซลูชันอื่น ๆ ในข้อมูลจริงได้อย่างไร มันยืนยันว่ามันช้ากว่าวิธีการตั้งค่าหรือการเรียกซ้ำ แต่ก็สร้างผลลัพธ์ที่ดีที่สุด

CREATE TABLE #Dst_V2 (ContractID bigint, StartDT date, EndDT date, DayCount int, WeekDays varchar(255) COLLATE SQL_Latin1_General_CP1_CI_AS);

SET NOCOUNT ON;

DECLARE @VarOldDateFirst int = @@DATEFIRST;
SET DATEFIRST 7;

DECLARE @iFS int;
DECLARE @VarCursor CURSOR;
SET @VarCursor = CURSOR FAST_FORWARD
FOR
    SELECT
        ContractID
        ,dt
        ,dowChar
        ,dowInt
    FROM #Src AS S
    ;

OPEN @VarCursor;

DECLARE @CurrContractID bigint = 0;
DECLARE @Currdt date;
DECLARE @CurrdowChar char(3);
DECLARE @CurrdowInt int;


DECLARE @VarCreateNewInterval bit = 0;
DECLARE @VarTempDT date;
DECLARE @VarTempdowInt int;

DECLARE @LastContractID bigint = 0;
DECLARE @LastStartDT date;
DECLARE @LastEndDT date;
DECLARE @LastDayCount int = 0;
DECLARE @LastWeekDays varchar(255);
DECLARE @LastMonCount int;
DECLARE @LastTueCount int;
DECLARE @LastWedCount int;
DECLARE @LastThuCount int;
DECLARE @LastFriCount int;
DECLARE @LastSatCount int;
DECLARE @LastSunCount int;


FETCH NEXT FROM @VarCursor INTO @CurrContractID, @Currdt, @CurrdowChar, @CurrdowInt;
SET @iFS = @@FETCH_STATUS;
IF @iFS = 0
BEGIN
    SET @LastContractID = @CurrContractID;
    SET @LastStartDT = @Currdt;
    SET @LastEndDT = @Currdt;
    SET @LastDayCount = 1;
    SET @LastMonCount = 0;
    SET @LastTueCount = 0;
    SET @LastWedCount = 0;
    SET @LastThuCount = 0;
    SET @LastFriCount = 0;
    SET @LastSatCount = 0;
    SET @LastSunCount = 0;
    IF @CurrdowInt = 1 SET @LastSunCount = @LastSunCount + 1;
    IF @CurrdowInt = 2 SET @LastMonCount = @LastMonCount + 1;
    IF @CurrdowInt = 3 SET @LastTueCount = @LastTueCount + 1;
    IF @CurrdowInt = 4 SET @LastWedCount = @LastWedCount + 1;
    IF @CurrdowInt = 5 SET @LastThuCount = @LastThuCount + 1;
    IF @CurrdowInt = 6 SET @LastFriCount = @LastFriCount + 1;
    IF @CurrdowInt = 7 SET @LastSatCount = @LastSatCount + 1;
END;

WHILE @iFS = 0
BEGIN

    SET @VarCreateNewInterval = 0;

    -- Contract changes -> start new interval
    IF @LastContractID <> @CurrContractID
    BEGIN
        SET @VarCreateNewInterval = 1;
    END;

    IF @VarCreateNewInterval = 0
    BEGIN
        -- check days of week
        -- are we still within the first week of the interval?
        IF DATEDIFF(day, @LastStartDT, @Currdt) > 6
        BEGIN
            -- we are beyond the first week, check day of the week
            -- have we seen @CurrdowInt before?
            -- we should start a new interval if this is the new day of the week that didn't exist in the first week
            IF @CurrdowInt = 1 AND @LastSunCount = 0 SET @VarCreateNewInterval = 1;
            IF @CurrdowInt = 2 AND @LastMonCount = 0 SET @VarCreateNewInterval = 1;
            IF @CurrdowInt = 3 AND @LastTueCount = 0 SET @VarCreateNewInterval = 1;
            IF @CurrdowInt = 4 AND @LastWedCount = 0 SET @VarCreateNewInterval = 1;
            IF @CurrdowInt = 5 AND @LastThuCount = 0 SET @VarCreateNewInterval = 1;
            IF @CurrdowInt = 6 AND @LastFriCount = 0 SET @VarCreateNewInterval = 1;
            IF @CurrdowInt = 7 AND @LastSatCount = 0 SET @VarCreateNewInterval = 1;

            IF @VarCreateNewInterval = 0
            BEGIN
                -- check the gap between current day and last day of the interval
                -- if the gap between current day and last day of the interval
                -- contains a day of the week that was included in the interval before,
                -- we should create new interval
                SET @VarTempDT = DATEADD(day, 1, @LastEndDT);
                WHILE @VarTempDT < @Currdt
                BEGIN
                    SET @VarTempdowInt = DATEPART(WEEKDAY, @VarTempDT);

                    IF @VarTempdowInt = 1 AND @LastSunCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;
                    IF @VarTempdowInt = 2 AND @LastMonCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;
                    IF @VarTempdowInt = 3 AND @LastTueCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;
                    IF @VarTempdowInt = 4 AND @LastWedCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;
                    IF @VarTempdowInt = 5 AND @LastThuCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;
                    IF @VarTempdowInt = 6 AND @LastFriCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;
                    IF @VarTempdowInt = 7 AND @LastSatCount > 0 BEGIN SET @VarCreateNewInterval = 1; BREAK; END;

                    SET @VarTempDT = DATEADD(day, 1, @VarTempDT);
                END;
            END;
        END;
        -- else
        -- we are still within the first week, so we can add this day to the interval
    END;

    IF @VarCreateNewInterval = 1
    BEGIN
        -- save the new interval into the final table
        SET @LastWeekDays = '';
        IF @LastSunCount > 0 SET @LastWeekDays = @LastWeekDays + 'Sun,';
        IF @LastMonCount > 0 SET @LastWeekDays = @LastWeekDays + 'Mon,';
        IF @LastTueCount > 0 SET @LastWeekDays = @LastWeekDays + 'Tue,';
        IF @LastWedCount > 0 SET @LastWeekDays = @LastWeekDays + 'Wed,';
        IF @LastThuCount > 0 SET @LastWeekDays = @LastWeekDays + 'Thu,';
        IF @LastFriCount > 0 SET @LastWeekDays = @LastWeekDays + 'Fri,';
        IF @LastSatCount > 0 SET @LastWeekDays = @LastWeekDays + 'Sat,';

        INSERT INTO #Dst_V2 
            (ContractID
            ,StartDT
            ,EndDT
            ,DayCount
            ,WeekDays)
        VALUES
            (@LastContractID
            ,@LastStartDT
            ,@LastEndDT
            ,@LastDayCount
            ,@LastWeekDays);

        -- init the new interval
        SET @LastContractID = @CurrContractID;
        SET @LastStartDT = @Currdt;
        SET @LastEndDT = @Currdt;
        SET @LastDayCount = 1;
        SET @LastMonCount = 0;
        SET @LastTueCount = 0;
        SET @LastWedCount = 0;
        SET @LastThuCount = 0;
        SET @LastFriCount = 0;
        SET @LastSatCount = 0;
        SET @LastSunCount = 0;
        IF @CurrdowInt = 1 SET @LastSunCount = @LastSunCount + 1;
        IF @CurrdowInt = 2 SET @LastMonCount = @LastMonCount + 1;
        IF @CurrdowInt = 3 SET @LastTueCount = @LastTueCount + 1;
        IF @CurrdowInt = 4 SET @LastWedCount = @LastWedCount + 1;
        IF @CurrdowInt = 5 SET @LastThuCount = @LastThuCount + 1;
        IF @CurrdowInt = 6 SET @LastFriCount = @LastFriCount + 1;
        IF @CurrdowInt = 7 SET @LastSatCount = @LastSatCount + 1;

    END ELSE BEGIN

        -- update last interval
        SET @LastEndDT = @Currdt;
        SET @LastDayCount = @LastDayCount + 1;
        IF @CurrdowInt = 1 SET @LastSunCount = @LastSunCount + 1;
        IF @CurrdowInt = 2 SET @LastMonCount = @LastMonCount + 1;
        IF @CurrdowInt = 3 SET @LastTueCount = @LastTueCount + 1;
        IF @CurrdowInt = 4 SET @LastWedCount = @LastWedCount + 1;
        IF @CurrdowInt = 5 SET @LastThuCount = @LastThuCount + 1;
        IF @CurrdowInt = 6 SET @LastFriCount = @LastFriCount + 1;
        IF @CurrdowInt = 7 SET @LastSatCount = @LastSatCount + 1;
    END;


    FETCH NEXT FROM @VarCursor INTO @CurrContractID, @Currdt, @CurrdowChar, @CurrdowInt;
    SET @iFS = @@FETCH_STATUS;
END;

-- save the last interval into the final table
IF @LastDayCount > 0
BEGIN
    SET @LastWeekDays = '';
    IF @LastSunCount > 0 SET @LastWeekDays = @LastWeekDays + 'Sun,';
    IF @LastMonCount > 0 SET @LastWeekDays = @LastWeekDays + 'Mon,';
    IF @LastTueCount > 0 SET @LastWeekDays = @LastWeekDays + 'Tue,';
    IF @LastWedCount > 0 SET @LastWeekDays = @LastWeekDays + 'Wed,';
    IF @LastThuCount > 0 SET @LastWeekDays = @LastWeekDays + 'Thu,';
    IF @LastFriCount > 0 SET @LastWeekDays = @LastWeekDays + 'Fri,';
    IF @LastSatCount > 0 SET @LastWeekDays = @LastWeekDays + 'Sat,';

    INSERT INTO #Dst_V2
        (ContractID
        ,StartDT
        ,EndDT
        ,DayCount
        ,WeekDays)
    VALUES
        (@LastContractID
        ,@LastStartDT
        ,@LastEndDT
        ,@LastDayCount
        ,@LastWeekDays);
END;

CLOSE @VarCursor;
DEALLOCATE @VarCursor;

SET DATEFIRST @VarOldDateFirst;

DROP TABLE #Dst_V2;

2

ฉันแปลกใจเล็กน้อยที่โซลูชันเคอร์เซอร์ของ Vladimirช้ามากดังนั้นฉันจึงพยายามปรับรุ่นให้เหมาะสม ฉันยืนยันว่าการใช้เคอร์เซอร์ช้ามากสำหรับฉันเช่นกัน

อย่างไรก็ตามด้วยค่าใช้จ่ายของการใช้ฟังก์ชันการทำงานที่ไม่มีเอกสารใน SQL Server โดยต่อท้ายตัวแปรขณะประมวลผล rowset ฉันสามารถสร้างตรรกะที่เรียบง่ายรุ่นนี้ซึ่งให้ผลลัพธ์ที่ดีที่สุดและดำเนินการได้เร็วกว่าทั้งเคอร์เซอร์และโซลูชันดั้งเดิมของฉัน . ดังนั้นใช้ความเสี่ยงของคุณเอง แต่ฉันจะนำเสนอวิธีแก้ปัญหาในกรณีที่เป็นที่สนใจ นอกจากนี้ยังเป็นไปได้ที่จะปรับปรุงโซลูชันเพื่อใช้การWHILEวนซ้ำจากหนึ่งไปยังหมายเลขแถวสูงสุดโดยการค้นหาไปยังหมายเลขแถวถัดไปที่การวนซ้ำแต่ละครั้งของการวนซ้ำ สิ่งนี้จะยึดติดกับฟังก์ชั่นการบันทึกที่สมบูรณ์และเชื่อถือได้ แต่จะละเมิดข้อ จำกัด ที่ระบุไว้ (ของเทียม) ของปัญหาที่WHILEลูปไม่ได้รับอนุญาต

โปรดทราบว่าหากใช้ SQL 2014 ได้รับอนุญาตอาจเป็นไปได้ว่า กระบวนการจัดเก็บที่รวบรวมแบบดั้งเดิมที่วนรอบหมายเลขแถวและเข้าถึงหมายเลขแถวแต่ละแถวในตารางเพิ่มประสิทธิภาพหน่วยความจำจะเป็นการดำเนินการตามตรรกะเดียวกันนี้ที่จะทำงานได้เร็วขึ้น

นี่คือทางออกที่สมบูรณ์รวมถึงการขยายข้อมูลทดลองใช้ที่กำหนดไว้ประมาณครึ่งล้านแถว โซลูชันใหม่จะเสร็จสมบูรณ์ในเวลาประมาณ 3 วินาทีและในความเห็นของฉันนั้นมีความกระชับและอ่านได้มากกว่าโซลูชั่นที่ฉันเคยนำเสนอ ฉันจะแยกสามขั้นตอนที่เกี่ยวข้องที่นี่:

ขั้นตอนที่ 1: การประมวลผลล่วงหน้า

อันดับแรกเราเพิ่มหมายเลขแถวในชุดข้อมูลตามลำดับเราจะประมวลผลข้อมูล ในขณะที่ทำเช่นนั้นเรายังแปลง dowInt แต่ละอันให้เป็นพลังงาน 2 เพื่อให้เราสามารถใช้บิตแมปเพื่อแสดงว่าวันใดได้รับการสังเกตในการจัดกลุ่มใด ๆ :

IF OBJECT_ID('tempdb..#srcWithRn') IS NOT NULL
    DROP TABLE #srcWithRn
GO
SELECT rn = IDENTITY(INT, 1, 1), ContractId, dt, dowInt,
    POWER(2, dowInt) AS dowPower, dowChar
INTO #srcWithRn
FROM #src
ORDER BY ContractId, dt
GO
ALTER TABLE #srcWithRn
ADD PRIMARY KEY (rn)
GO

ขั้นตอนที่ 2: วนรอบวันทำสัญญาเพื่อระบุกลุ่มใหม่

เราวนรอบข้อมูลถัดไปตามลำดับหมายเลขแถว เราคำนวณเฉพาะรายการหมายเลขแถวที่เป็นขอบเขตของการจัดกลุ่มใหม่แล้วส่งออกหมายเลขแถวเหล่านั้นลงในตาราง:

DECLARE @ContractId INT, @RnList VARCHAR(MAX), @NewGrouping BIT = 0, @DowBitmap INT = 0, @startDt DATE
SELECT TOP 1 @ContractId = ContractId, @startDt = dt, @RnList = ',' + CONVERT(VARCHAR(MAX), rn), @DowBitmap = DowPower
FROM #srcWithRn
WHERE rn = 1

SELECT 
    -- New grouping if new contract, or if we're observing a new day that we did
    -- not observe within the first 7 days of the grouping
    @NewGrouping = CASE
        WHEN ContractId <> @ContractId THEN 1
        WHEN DATEDIFF(DAY, @startDt, dt) > 6
            AND @DowBitmap & dowPower <> dowPower THEN 1
        ELSE 0
        END,
    @ContractId = ContractId,
    -- If this is a newly observed day in an existing grouping, add it to the bitmap
    @DowBitmap = CASE WHEN @NewGrouping = 0 THEN @DowBitmap | DowPower ELSE DowPower END,
    -- If this is a new grouping, reset the start date of the grouping
    @startDt = CASE WHEN @NewGrouping = 0 THEN @startDt ELSE dt END,
    -- If this is a new grouping, add this rn to the list of row numbers that delineate the boundary of a new grouping
    @RnList = CASE WHEN @NewGrouping = 0 THEN @RnList ELSE @RnList + ',' + CONVERT(VARCHAR(MAX), rn) END 
FROM #srcWithRn
WHERE rn >= 2
ORDER BY rn
OPTION (MAXDOP 1)

-- Split the list of grouping boundaries into a table
IF OBJECT_ID('tempdb..#newGroupingRns') IS NOT NULL
    DROP TABLE #newGroupingRns
SELECT splitListId AS rn
INTO #newGroupingRns
FROM dbo.f_delimitedIntListSplitter(SUBSTRING(@RnList, 2, 1000000000), DEFAULT)
GO
ALTER TABLE #newGroupingRns
ADD PRIMARY KEY (rn)
GO

ขั้นตอนที่ 3: การคำนวณผลลัพธ์สุดท้ายตามจำนวนแถวของขอบเขตการจัดกลุ่มแต่ละกลุ่ม

จากนั้นเราคำนวณการจัดกลุ่มสุดท้ายโดยใช้ขอบเขตที่ระบุในลูปด้านบนเพื่อรวมวันที่ทั้งหมดที่อยู่ในการจัดกลุ่มแต่ละครั้ง:

IF OBJECT_ID('tempdb..#finalGroupings') IS NOT NULL
    DROP TABLE #finalGroupings
GO
SELECT MIN(s.ContractId) AS ContractId,
    MIN(dt) AS StartDT,
    MAX(dt) AS EndDT,
    COUNT(*) AS DayCount,
    CASE WHEN MAX(CASE WHEN dowChar = 'Sun' THEN 1 ELSE 0 END) = 1 THEN 'Sun,' ELSE '' END + 
    CASE WHEN MAX(CASE WHEN dowChar = 'Mon' THEN 1 ELSE 0 END) = 1 THEN 'Mon,' ELSE '' END + 
    CASE WHEN MAX(CASE WHEN dowChar = 'Tue' THEN 1 ELSE 0 END) = 1 THEN 'Tue,' ELSE '' END + 
    CASE WHEN MAX(CASE WHEN dowChar = 'Wed' THEN 1 ELSE 0 END) = 1 THEN 'Wed,' ELSE '' END + 
    CASE WHEN MAX(CASE WHEN dowChar = 'Thu' THEN 1 ELSE 0 END) = 1 THEN 'Thu,' ELSE '' END + 
    CASE WHEN MAX(CASE WHEN dowChar = 'Fri' THEN 1 ELSE 0 END) = 1 THEN 'Fri,' ELSE '' END + 
    CASE WHEN MAX(CASE WHEN dowChar = 'Sat' THEN 1 ELSE 0 END) = 1 THEN 'Sat,' ELSE '' END AS WeekDays
INTO #finalGroupings
FROM #srcWithRn s
CROSS APPLY (
    -- For any row, its grouping is the largest boundary row number that occurs at or before this row
    SELECT TOP 1 rn AS groupingRn
    FROM #newGroupingRns grp
    WHERE grp.rn <= s.rn
    ORDER BY grp.rn DESC
) g
GROUP BY g.groupingRn
ORDER BY g.groupingRn
GO

ขอขอบคุณ. ฉันถามว่าจะไม่ใช้เคอร์เซอร์หรือWHILEลูปเพราะฉันรู้วิธีแก้ปัญหาด้วยเคอร์เซอร์แล้วและฉันต้องการค้นหาวิธีแก้ปัญหาแบบตั้งค่า นอกจากนี้ฉันสงสัยว่าเคอร์เซอร์จะช้า (โดยเฉพาะเมื่อมีลูปซ้อนอยู่ในนั้น) คำตอบนี้น่าสนใจมากในแง่ของการเรียนรู้เทคนิคใหม่และฉันซาบซึ้งในความพยายามของคุณ
Vladimir Baranov

1

การสนทนาจะเป็นไปตามรหัส

declare @Helper table(
    rn tinyint,
    dowInt tinyint,
    dowChar char(3));
insert @Helper
values  ( 1,1,'Sun'),
        ( 2,2,'Mon'),
        ( 3,3,'Tue'),
        ( 4,4,'Wed'),
        ( 5,5,'Thu'),
        ( 6,6,'Fri'),
        ( 7,7,'Sat'),
        ( 8,1,'Sun'),
        ( 9,2,'Mon'),
        (10,3,'Tue'),
        (11,4,'Wed'),
        (12,5,'Thu'),
        (13,6,'Fri'),
        (14,7,'Sat');



with MissingDays as
(
    select
        h1.rn as rn1,
        h1.dowChar as StartDay,
        h2.rn as rn2,
        h2.dowInt as FollowingDayInt,
        h2.dowChar as FollowingDayChar
    from @Helper as h1
    inner join @Helper as h2
        on h2.rn > h1.rn
    where h1.rn < 8
    and h2.rn < h1.rn + 8
)
,Numbered as
(
    select
        a.*,
        ROW_NUMBER() over (partition by a.ContractID order by a.dt) as rn
    from #Src as a
)
,Incremented as
(
    select
        b.*,
        convert(varchar(max), b.dowChar)+',' as WeekDays,
        b.dt as IntervalStart
    from Numbered as b
    where b.rn = 1

    union all

    select
        c.*,
        case
            when
                (DATEDIFF(day, d.IntervalStart, c.dt) > 6)      -- interval goes beyond 7 days
            and (
                    (d.WeekDays not like '%'+c.dowChar+'%')     -- the new week day has not been seen before
                or 
                    (DATEDIFF(day, d.dt, c.dt) > 7)
                or 
                    (
                        (DATEDIFF(day, d.dt, c.dt) > 1)
                        and
                        (
                        exists( select
                                    e.FollowingDayChar
                                from MissingDays as e
                                where e.StartDay = d.dowChar
                                and rn2 < (select f.rn2 from MissingDays as f
                                            where f.StartDay = d.dowChar
                                            and f.FollowingDayInt = c.dowInt)
                                and d.WeekDays like '%'+e.FollowingDayChar+'%'
                            )
                        )
                    )
                )
            then convert(varchar(max),c.dowChar)+','
            else
                case
                    when d.WeekDays like '%'+c.dowChar+'%'
                    then d.WeekDays
                    else d.WeekDays+convert(varchar(max),c.dowChar)+','
                end
        end,
        case
            when
                (DATEDIFF(day, d.IntervalStart, c.dt) > 6)      -- interval goes beyond 7 days
            and (
                    (d.WeekDays not like '%'+c.dowChar+'%')     -- the new week day has not been seen before
                or
                    (DATEDIFF(day, d.dt, c.dt) > 7)             -- there is a one week gap
                or 
                    (
                        (DATEDIFF(day, d.dt, c.dt) > 1)         -- there is a gap..
                        and
                        (
                        exists( select                          -- .. and the omitted days are in the preceeding interval
                                    e.FollowingDayChar
                                from MissingDays as e
                                where e.StartDay = d.dowChar
                                and rn2 < (select f.rn2 from MissingDays as f
                                            where f.StartDay = d.dowChar
                                            and f.FollowingDayInt = c.dowInt)
                                and d.WeekDays like '%'+e.FollowingDayChar+'%'
                            )
                        )
                    )
                )
            then c.dt
            else d.IntervalStart
        end
    from Numbered as c
    inner join Incremented as d
    on d.ContractID = c.ContractID
    and d.rn = c.rn - 1
)
select
    g.ContractID,
    g.IntervalStart as StartDT,
    MAX(g.dt) as EndDT,
    COUNT(*) as DayCount,
    MAX(g.WeekDays) as WeekDays
from Incremented as g
group by
    g.ContractID,
    g.IntervalStart
order by
    ContractID,
    StartDT;

@Helper คือการรับมือกับกฎนี้:

หากช่องว่างระหว่างวันปัจจุบันและวันสุดท้ายของช่วงเวลามีวันของสัปดาห์ที่รวมอยู่ในช่วงก่อนหน้านี้เราควรสร้างช่วงเวลาใหม่

ช่วยให้ฉันสามารถระบุชื่อวันตามลำดับหมายเลขวันระหว่างสองวันที่กำหนด สิ่งนี้ใช้เมื่อตัดสินใจว่าควรเริ่มช่วงเวลาใหม่หรือไม่ ฉันเติมด้วยค่าสองสัปดาห์เพื่อให้ง่ายต่อการเขียนโค้ดในวันหยุดสุดสัปดาห์

มีวิธีที่สะอาดกว่าในการใช้สิ่งนี้ ตาราง "วันที่" เต็มจะเป็นหนึ่ง อาจมีวิธีที่ชาญฉลาดด้วยหมายเลขวันและเลขคณิตแบบโมดูโลเช่นกัน

CTE MissingDaysคือการสร้างรายชื่อวันระหว่างสองวันที่กำหนด มันถูกจัดการด้วยวิธี clunky นี้เนื่องจาก CTE แบบเรียกซ้ำ (ต่อไปนี้) ไม่อนุญาตการรวม, TOP () หรือตัวดำเนินการอื่น นี่เป็นสิ่งที่ไม่เหมาะสม แต่ใช้ได้ผล

CTE Numberedคือการบังคับใช้ลำดับที่ไม่มีช่องว่างที่รู้จักบนข้อมูล มันหลีกเลี่ยงการเปรียบเทียบจำนวนมากในภายหลัง

CTE Incrementedคือที่ที่การกระทำเกิดขึ้น โดยพื้นฐานแล้วฉันใช้ CTE แบบเรียกซ้ำเพื่อก้าวผ่านข้อมูลและบังคับใช้กฎ หมายเลขแถวที่สร้างในNumbered(ด้านบน) ถูกใช้เพื่อขับเคลื่อนการประมวลผลแบบเรียกซ้ำ

CTE แบบเรียกซ้ำได้รับวันแรกของ ContractID แต่ละรายการและกำหนดค่าเริ่มต้นที่จะใช้ในการตัดสินใจว่าต้องการช่วงเวลาใหม่หรือไม่

การตัดสินใจว่าช่วงเวลาใหม่ควรเริ่มต้นต้องมีวันที่เริ่มต้นของช่วงเวลาปัจจุบันรายการวันและความยาวของช่องว่างใด ๆ ในวันที่ปฏิทิน สิ่งเหล่านี้อาจถูกรีเซ็ตหรือยกยอดไปข้างหน้าขึ้นอยู่กับการตัดสินใจ ดังนั้นส่วนที่เรียกซ้ำเป็น verbose และทำซ้ำเล็กน้อยเนื่องจากเราต้องตัดสินใจว่าจะเริ่มช่วงเวลาใหม่สำหรับค่าคอลัมน์มากกว่าหนึ่งค่าหรือไม่

ตรรกะการตัดสินใจสำหรับคอลัมน์WeekDaysและIntervalStartควรมีตรรกะการตัดสินใจเหมือนกัน - มันสามารถตัดและวางระหว่างพวกเขา หากตรรกะสำหรับการเริ่มต้นช่วงเวลาใหม่คือการเปลี่ยนแปลงนี่คือรหัสที่จะเปลี่ยนแปลง เป็นการดีที่มันจะเป็นนามธรรมดังนั้น; การทำเช่นนี้ใน CTE แบบเรียกซ้ำอาจเป็นการท้าทาย

EXISTS()ข้อเป็นแม่น้ำที่จะไม่สามารถที่จะใช้ฟังก์ชั่นรวมใน CTE ทั่วถึง ทั้งหมดจะเห็นว่าวันที่ตกอยู่ในช่องว่างอยู่ในช่วงเวลาปัจจุบันแล้ว

ไม่มีอะไรมหัศจรรย์เกี่ยวกับการทำรังของตรรกะของประโยค หากมีความชัดเจนในโครงสร้างอื่นหรือใช้ CASE ที่ซ้อนกันบอกว่าไม่มีเหตุผลที่จะคงไว้เช่นนี้

สุดท้ายSELECTคือการให้ผลลัพธ์ในรูปแบบที่ต้องการ

การมี PK on Src.IDจะไม่มีประโยชน์สำหรับวิธีนี้ (ContractID,dt)ฉันคิดว่าดัชนีแบบคลัสเตอร์น่าจะดี

มีขอบขรุขระเล็กน้อย วันจะไม่ถูกส่งกลับในลำดับ dow แต่ในลำดับปฏิทินจะปรากฏในข้อมูลต้นฉบับ ทุกอย่างที่เกี่ยวข้องกับ @Helper นั้นค่อนข้าง klunky และสามารถปรับให้เรียบได้ LIKEผมชอบความคิดของการใช้หนึ่งบิตต่อวันและการใช้ฟังก์ชั่นไบนารีแทน การแยก CTEs แนะแนวบางส่วนออกเป็นตารางอุณหภูมิด้วยดัชนีที่เหมาะสมจะช่วยได้อย่างแน่นอน

หนึ่งในความท้าทายในเรื่องนี้คือ "สัปดาห์" ไม่สอดคล้องกับปฏิทินมาตรฐาน แต่ถูกขับเคลื่อนโดยข้อมูลและรีเซ็ตเมื่อมีการพิจารณาว่าช่วงเวลาใหม่ควรเริ่มต้นขึ้น "สัปดาห์" หรืออย่างน้อยช่วงเวลาอาจมาจากหนึ่งวันเพื่อครอบคลุมชุดข้อมูลทั้งหมด


นี่คือค่าใช้จ่ายโดยประมาณกับข้อมูลตัวอย่างของ Geoff (ขอบคุณสำหรับสิ่งนั้น!) หลังจากการเปลี่ยนแปลงต่างๆ:

                                             estimated cost

My submission as is w/ CTEs, Geoff's data:      791682
Geoff's data, cluster key on (ContractID, dt):   21156.2
Real table for MissingDays:                      21156.2
Numbered as table UCI=(ContractID, rn):             16.6115    26s elapsed.
                  UCI=(rn, ContractID):             41.9845    26s elapsed.
MissingDays as refactored to simple lookup          16.6477    22s elapsed.
Weekdays as varchar(30)                             13.4013    30s elapsed.

จำนวนแถวโดยประมาณและตามจริงนั้นแตกต่างกันไป

แผนมี spoo ตารางน่าจะเป็นผลมาจาก CTE แบบเรียกซ้ำ การกระทำส่วนใหญ่อยู่ในโต๊ะทำงานซึ่งปิดตัวลง:

Table 'Worktable'.   Scan count       2, logical reads 4 196 269, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'MissingDays'. Scan count 464 116, logical reads   928 232, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Numbered'.    Scan count 484 122, logical reads 1 475 467, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

ฉันคิดว่า!


ขอขอบคุณ. มันให้ผลลัพธ์ที่ถูกต้องและเหมาะสมกับข้อมูลตัวอย่าง ฉันจะตรวจสอบกับข้อมูลจริงตอนนี้ สังเกตด้าน: MAX(g.IntervalStart)ดูเหมือนว่าแปลกเพราะ อยู่ในg.IntervalStart GROUP BYฉันคาดว่าจะให้ข้อผิดพลาดทางไวยากรณ์ แต่ก็ใช้งานได้ มันควรจะเป็นเพียงแค่g.IntervalStart as StartDTในSELECT? หรือg.IntervalStartไม่ควรอยู่ในGROUP BY?
Vladimir Baranov

ฉันพยายามเรียกใช้แบบสอบถามกับข้อมูลจริงและฉันต้องหยุดหลังจาก 10 นาที อาจเป็นไปได้ว่าหาก CTE MissingDaysและNumberedถูกแทนที่ด้วยตาราง temp ที่มีดัชนีที่เหมาะสมก็อาจมีประสิทธิภาพที่ดี คุณจะแนะนำดัชนีใด ฉันสามารถลองได้พรุ่งนี้เช้า
Vladimir Baranov

ฉันคิดว่าการแทนที่Numberedด้วยตารางชั่วคราวและดัชนีกลุ่มบน(ContractID, rn)จะคุ้มค่าไป หากไม่มีชุดข้อมูลขนาดใหญ่เพื่อสร้างแผนการที่สอดคล้องกันมันเป็นการคาดเดาที่ยุ่งยาก การMissingDatesทำดัชนีด้วยตัวเอง(StartDay, FollowingDayInt)ก็ดีเช่นกัน
Michael Green

ขอบคุณ ฉันไม่สามารถลองตอนนี้ แต่พรุ่งนี้เช้าฉันจะ
Vladimir Baranov

ฉันลองใช้ชุดข้อมูลแถวครึ่งล้าน (ชุดข้อมูลที่มีอยู่ซึ่งทำซ้ำ 4,000 ครั้งด้วย ContractIds ที่ต่างกัน) มันใช้งานมาประมาณ 15 นาทีและใช้พื้นที่ tempdb ถึง 30GB แล้ว ดังนั้นฉันคิดว่าการเพิ่มประสิทธิภาพบางอย่างอาจจำเป็น นี่คือข้อมูลการทดสอบเพิ่มเติมในกรณีที่คุณพบว่ามีประโยชน์
Geoff Patterson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.