Saya perlu mengkonversi data antara dua sistem.
Sistem pertama menyimpan jadwal sebagai daftar tanggal yang jelas. Setiap tanggal yang termasuk dalam jadwal adalah satu baris. Mungkin ada berbagai kesenjangan dalam urutan tanggal (akhir pekan, hari libur nasional, dan jeda yang lebih lama, beberapa hari dalam seminggu dapat dikecualikan dari jadwal). Tidak ada celah sama sekali, bahkan akhir pekan bisa dimasukkan. Jadwalnya bisa sampai 2 tahun. Biasanya panjangnya beberapa minggu.
Berikut adalah contoh sederhana dari jadwal yang mencakup dua minggu tidak termasuk akhir pekan (ada contoh yang lebih rumit dalam skrip di bawah):
+----+------------+------------+---------+--------+
| 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
unik, tetapi tidak harus berurutan (itu adalah kunci primer). Tanggal unik dalam setiap Kontrak (ada indeks unik aktif (ContractID, dt)
).
Sistem kedua menyimpan jadwal sebagai interval dengan daftar hari kerja yang merupakan bagian dari jadwal. Setiap interval ditentukan oleh tanggal mulai dan berakhirnya (inklusif) dan daftar hari kerja yang termasuk dalam jadwal. Dalam format ini, Anda dapat mendefinisikan pola mingguan berulang secara efisien, seperti Senin-Rabu, tetapi akan merepotkan ketika sebuah pola terganggu, misalnya oleh hari libur umum.
Berikut adalah contoh sederhana di atas akan terlihat seperti:
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 1 | 2016-05-02 | 2016-05-13 | 10 | Mon,Tue,Wed,Thu,Fri, |
+------------+------------+------------+----------+----------------------+
[StartDT;EndDT]
interval yang termasuk dalam Kontrak yang sama tidak boleh tumpang tindih.
Saya perlu mengubah data dari sistem pertama ke format yang digunakan oleh sistem kedua. Saat ini saya sedang menyelesaikan ini di sisi klien di C # untuk Kontrak yang diberikan tunggal, tapi saya ingin melakukannya dalam T-SQL di sisi server untuk pemrosesan massal dan ekspor / impor antar server. Kemungkinan besar, itu bisa dilakukan menggunakan CLR UDF, tetapi pada tahap ini saya tidak dapat menggunakan SQLCLR.
Tantangannya di sini adalah membuat daftar interval sesingkat dan seramah mungkin.
Misalnya, jadwal ini:
+-----+------------+------------+---------+--------+
| 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 |
+-----+------------+------------+---------+--------+
harus menjadi ini:
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 2 | 2016-05-05 | 2016-05-17 | 9 | Mon,Tue,Wed,Thu,Fri, |
+------------+------------+------------+----------+----------------------+
,bukan ini:
+------------+------------+------------+----------+----------------------+
| 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, |
+------------+------------+------------+----------+----------------------+
Saya mencoba menerapkan gaps-and-islands
pendekatan untuk masalah ini. Saya mencoba melakukannya dalam dua langkah. Pada pass pertama saya menemukan pulau-pulau yang terdiri dari hari-hari sederhana berturut-turut, yaitu ujung pulau adalah celah dalam urutan hari, baik itu akhir pekan, hari libur nasional atau yang lainnya. Untuk setiap pulau yang ditemukan seperti itu saya membuat daftar yang berbeda dengan koma WeekDays
. Pada pass kedua saya grup menemukan pulau lebih jauh dengan melihat celah dalam urutan angka minggu atau perubahan dalam WeekDays
.
Dengan pendekatan ini setiap minggu parsial berakhir sebagai interval tambahan seperti yang ditunjukkan di atas, karena meskipun angka minggu berturut-turut, WeekDays
perubahan. Selain itu, mungkin ada kesenjangan reguler dalam seminggu (lihat ContractID=3
dalam data sampel, yang hanya memiliki data Mon,Wed,Fri,
) dan pendekatan ini akan menghasilkan interval terpisah untuk setiap hari dalam jadwal tersebut. Sisi baiknya, ini menghasilkan satu interval jika jadwal tidak memiliki celah sama sekali (lihat ContractID=7
dalam data sampel yang termasuk akhir pekan) dan dalam hal itu tidak masalah jika awal atau akhir minggu parsial.
Silakan lihat contoh lain dalam skrip di bawah ini untuk mendapatkan ide yang lebih baik tentang apa yang saya kejar. Anda dapat melihat bahwa akhir pekan cukup sering dikecualikan, tetapi hari-hari lain dalam seminggu juga bisa dikecualikan. Dalam contoh 3 saja Mon
, Wed
dan Fri
merupakan bagian dari jadwal. Selain itu, akhir pekan dapat dimasukkan, seperti dalam contoh 7. Solusinya harus memperlakukan semua hari dalam seminggu secara merata. Setiap hari dalam seminggu dapat dimasukkan atau dikecualikan dari jadwal.
Untuk memverifikasi bahwa daftar interval yang dihasilkan menggambarkan jadwal yang diberikan dengan benar, Anda dapat menggunakan kode semu berikut:
- loop melalui semua interval
- untuk setiap putaran interval melalui semua tanggal kalender antara tanggal Mulai dan Akhir (termasuk).
- untuk setiap tanggal periksa apakah hari dalam seminggu terdaftar di
WeekDays
. Jika ya, maka tanggal ini termasuk dalam jadwal.
Semoga, ini menjelaskan dalam kasus apa interval baru harus dibuat. Dalam contoh 4 dan 5 satu Senin ( 2016-05-09
) dihapus dari tengah jadwal dan jadwal tersebut tidak dapat diwakili oleh satu interval. Dalam contoh 6 ada kesenjangan yang panjang dalam jadwal, sehingga diperlukan dua interval.
Interval mewakili pola mingguan dalam jadwal dan ketika suatu pola terganggu / diubah, interval baru harus ditambahkan. Dalam contoh 11 tiga minggu pertama memiliki pola Tue
, maka pola ini berubah menjadi Thu
. Sebagai hasilnya, kita membutuhkan dua interval untuk menggambarkan jadwal tersebut.
Saya menggunakan SQL Server 2008 saat ini, jadi solusinya harus berfungsi dalam versi ini. Jika solusi untuk SQL Server 2008 dapat disederhanakan / ditingkatkan menggunakan fitur dari versi yang lebih baru, itu bonus, tolong tunjukkan juga.
Saya memiliki Calendar
tabel (daftar tanggal) dan Numbers
tabel (daftar angka integer mulai dari 1), jadi tidak masalah untuk menggunakannya, jika perlu. Ini juga OK untuk membuat tabel sementara dan memiliki beberapa permintaan yang memproses data dalam beberapa tahap. Jumlah tahapan dalam suatu algoritma harus diperbaiki, kursor dan WHILE
loop eksplisit tidak OK.
Skrip untuk data sampel dan hasil yang diharapkan
-- @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;
Perbandingan jawaban
Tabel sebenarnya @Src
memiliki 403,555
baris dengan yang 15,857
berbeda ContractIDs
. Semua jawaban menghasilkan hasil yang benar (setidaknya untuk data saya) dan semuanya cukup cepat, tetapi berbeda dalam optimalitas. Semakin sedikit interval yang dihasilkan, semakin baik. Saya memasukkan waktu lari hanya untuk rasa ingin tahu. Fokus utama adalah hasil yang benar dan optimal, bukan kecepatan (kecuali jika terlalu lama - saya menghentikan permintaan non-rekursif oleh Ziggy Crueltyfree Zeitgeister setelah 10 menit).
+--------------------------------------------------------+-----------+---------+
| 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 | | |
+--------------------------------------------------------+-----------+---------+
sumber
(11,'2016-05-03', '2016-05-17', 3, 'Tue,'), (11,'2016-05-19', '2016-06-02', 3, 'Thu,');
di @Dst harus satu baris denganTue, Thu,
?@Dst
). Dua minggu pertama dari jadwal hanya memilikiTue
, jadi Anda tidak dapat memilikiWeekDays=Tue,Thu,
untuk minggu ini. Dua minggu terakhir dari jadwal hanya memilikiThu
, sehingga Anda tidak dapat lagiWeekDays=Tue,Thu,
untuk minggu ini. Solusi yang kurang optimal untuk itu adalah tiga baris: hanyaTue
untuk dua minggu pertama, kemudianTue,Thu,
untuk minggu ketiga yang memiliki keduanyaTue
danThu
, kemudian hanyaThu
untuk dua minggu terakhir.ContractID
perubahan, jika interval melampaui 7 hari dan hari minggu baru belum terlihat sebelumnya, jika ada celah dalam daftar hari yang dijadwalkan.Jawaban:
Yang ini menggunakan CTE rekursif. Hasilnya identik dengan contoh dalam pertanyaan . Itu adalah mimpi buruk untuk datang dengan ... Kode termasuk komentar untuk memudahkan melalui logika yang berbelit-belit.
Strategi lain
Yang ini harus secara signifikan lebih cepat daripada yang sebelumnya karena tidak bergantung pada CTE rekursif terbatas lambat di SQL Server 2008, meskipun menerapkan lebih atau kurang strategi yang sama.
Ada satu
WHILE
pengulangan (saya tidak bisa menemukan cara untuk menghindarinya), tetapi berlaku untuk pengurangan jumlah iterasi (jumlah urutan tertinggi (minus satu) pada kontrak yang diberikan).Ini adalah strategi yang sederhana, dan dapat digunakan untuk urutan yang lebih pendek atau lebih lama dari satu minggu (menggantikan setiap kemunculan konstanta 7 untuk nomor lainnya, dan
dowBit
dihitung dari MODULUS xDayNo
daripadaDATEPART(wk)
) dan hingga 32.sumber
Tidak persis apa yang Anda cari tetapi mungkin bisa menarik bagi Anda.
Permintaan menciptakan minggu dengan string yang dipisahkan koma untuk hari yang digunakan dalam setiap minggu. Kemudian menemukan pulau-pulau minggu berturut-turut yang menggunakan pola yang sama di
Weekdays
.Hasil:
ContractID = 2
menunjukkan apa perbedaan dalam hasil dibandingkan dengan apa yang Anda inginkan. Minggu pertama dan terakhir akan diperlakukan sebagai periode terpisah karenaWeekDays
berbeda.sumber
WeekDays
juga angka 7-bit. Hanya 128 kombinasi. Hanya ada 128 * 128 = 16384 pasangan yang memungkinkan. Bangun tabel temp dengan semua pasangan yang memungkinkan, kemudian cari algoritme berbasis set yang akan menandai pasangan mana yang dapat digabungkan: pola satu minggu "ditutupi" oleh pola minggu berikutnya. Bergabung sendiri dengan hasil mingguan saat ini (karena tidak adaLAG
pada tahun 2008) dan gunakan tabel temp itu untuk memutuskan pasangan mana yang akan digabung ... Tidak yakin apakah ide ini memiliki kelebihan.Saya berakhir dengan pendekatan yang menghasilkan solusi optimal dalam kasus ini dan saya pikir akan baik-baik saja secara umum. Namun, solusinya cukup panjang, jadi akan menarik untuk melihat apakah orang lain memiliki pendekatan berbeda yang lebih ringkas.
Berikut ini skrip yang berisi solusi lengkap .
Dan di sini adalah garis besar algoritma:
ContractId
ContractId
dan samaWeekDays
WeekDays
dalam satu minggu cocok dengan subset terkemuka dariWeekDays
pengelompokan sebelumnya, bergabung ke pengelompokan sebelumnyaWeekDays
dalam satu minggu cocok dengan subset trailing dariWeekDays
pengelompokan berikutnya, bergabung ke pengelompokan berikutnyasumber
(1214,12,'2016-05-06', 'Fri', 6), (1225,12,'2016-05-09', 'Mon', 2),
. Itu bisa direpresentasikan sebagai satu interval, tetapi solusi Anda menghasilkan dua. Saya akui, contoh ini tidak ada dalam data sampel dan tidak kritis. Saya akan mencoba menjalankan solusi Anda pada data nyata.Saya tidak dapat memahami logika di balik pengelompokan minggu dengan kesenjangan, atau minggu dengan akhir pekan (misalnya ketika ada dua minggu berturut-turut dengan akhir pekan, minggu mana akhir pekan pergi ke?).
Kueri berikut menghasilkan output yang diinginkan kecuali hanya mengelompokkan hari kerja berturut-turut, dan mengelompokkan minggu Sun-Sat (daripada Senin-Minggu). Meskipun tidak persis apa yang Anda inginkan, mungkin ini dapat memberikan beberapa petunjuk untuk strategi yang berbeda. Pengelompokan hari datang dari sini . Fungsi-fungsi windowing yang digunakan harus bekerja dengan SQLServer 2008, tapi saya tidak punya versi untuk menguji apakah itu benar-benar.
Hasil
sumber
Demi kelengkapan, ini adalah dua pass
gaps-and-islands
yang saya coba sendiri sebelum mengajukan pertanyaan ini.Ketika saya mengujinya pada data nyata saya menemukan beberapa kasus ketika itu menghasilkan hasil yang salah dan memperbaikinya.
Berikut algoritanya:
CTE_ContractDays
,CTE_DailyRN
,CTE_DailyIslands
) dan menghitung jumlah minggu untuk setiap tanggal mulai dan berakhir dari sebuah pulau. Di sini angka minggu dihitung dengan asumsi bahwa Senin adalah hari pertama dalam seminggu.CTE_Weeks
).CTE_FirstResult
).WeekDays
(CTE_SecondRN
,CTE_Schedules
).Ini menangani kasus dengan baik ketika tidak ada gangguan dalam pola mingguan (1, 7, 8, 10, 12). Ini menangani kasus dengan baik ketika pola memiliki hari non-berurutan (3).
Tapi, sayangnya, ini menghasilkan interval ekstra untuk minggu parsial (2, 3, 5, 6, 9, 11).
Hasil
Solusi berbasis kursor
Saya mengubah kode C # saya menjadi algoritma berbasis kursor, hanya untuk melihat bagaimana perbandingannya dengan solusi lain pada data nyata. Ini menegaskan bahwa itu jauh lebih lambat daripada pendekatan berbasis set atau rekursif lainnya, tetapi menghasilkan hasil yang optimal.
sumber
Saya sedikit terkejut bahwa solusi kursor Vladimir sangat lambat, jadi saya juga mencoba untuk mengoptimalkan versi itu. Saya mengkonfirmasi bahwa menggunakan kursor juga sangat lambat bagi saya.
Namun, dengan biaya menggunakan fungsi tidak berdokumen dalam SQL Server dengan menambahkan variabel saat memproses rowset, saya dapat membuat versi sederhana dari logika ini yang menghasilkan hasil yang optimal dan mengeksekusi lebih cepat daripada kursor dan solusi asli saya . Jadi gunakan dengan risiko Anda sendiri, tetapi saya akan menyajikan solusinya jika itu menarik. Juga dimungkinkan untuk memperbarui solusi untuk menggunakan
WHILE
loop dari satu ke nomor baris maksimum, mencari ke nomor baris berikutnya pada setiap iterasi loop. Ini akan menempel pada fungsionalitas yang sepenuhnya terdokumentasi dan dapat diandalkan, tetapi akan melanggar kendala (agak buatan) yang dinyatakan dari masalah ituWHILE
loop tidak diizinkan.Perhatikan bahwa jika menggunakan SQL 2014 diizinkan, kemungkinan bahwa a prosedur tersimpan yang disusun secara asli yang melompati nomor baris dan mengakses setiap nomor baris dalam tabel yang dioptimalkan memori akan menjadi implementasi dari logika yang sama ini yang akan berjalan lebih cepat.
Inilah solusi lengkapnya , termasuk memperluas data uji coba yang ditetapkan menjadi sekitar setengah juta baris. Solusi baru selesai dalam waktu sekitar 3 detik dan menurut saya jauh lebih ringkas dan mudah dibaca daripada solusi sebelumnya yang saya tawarkan. Saya akan memecah tiga langkah yang terlibat di sini:
Langkah 1: pra-pemrosesan
Kami pertama-tama menambahkan nomor baris ke kumpulan data, dalam urutan kami akan memproses data. Sambil melakukan hal itu, kami juga mengubah setiap dowInt menjadi kekuatan 2 sehingga kami dapat menggunakan bitmap untuk mewakili hari-hari yang telah diamati dalam pengelompokan apa pun yang diberikan:
Langkah 2: Looping melalui hari-hari kontrak untuk mengidentifikasi pengelompokan baru
Kami selanjutnya mengulangi data, berdasarkan nomor baris. Kami hanya menghitung daftar nomor baris yang membentuk batas pengelompokan baru, lalu menampilkan nomor-nomor baris itu ke dalam tabel:
Langkah 3: Menghitung hasil akhir berdasarkan nomor baris setiap batas pengelompokan
Kami kemudian menghitung pengelompokan terakhir dengan menggunakan batas-batas yang diidentifikasi dalam loop di atas untuk menggabungkan semua tanggal yang termasuk dalam setiap pengelompokan:
sumber
WHILE
loop, karena saya sudah tahu bagaimana menyelesaikannya dengan kursor dan saya ingin menemukan solusi berbasis set. Selain itu, saya menduga kursor akan lambat (terutama dengan loop bersarang di dalamnya). Jawaban ini sangat menarik dalam hal mempelajari trik baru dan saya menghargai upaya Anda.Diskusi akan mengikuti kode.
@Helper
adalah untuk mengatasi aturan ini:Ini memungkinkan saya untuk membuat daftar nama hari, dalam urutan nomor hari, antara dua hari yang diberikan. Ini digunakan ketika memutuskan apakah interval baru harus dimulai. Saya mengisinya dengan nilai dua minggu untuk membuat pembungkus akhir pekan lebih mudah dikodekan.
Ada cara yang lebih bersih untuk mengimplementasikan ini. Tabel "tanggal" penuh akan menjadi satu. Mungkin ada cara pintar dengan nomor hari dan modulo aritmatika juga.
CTE
MissingDays
adalah untuk menghasilkan daftar nama hari antara dua hari yang diberikan. Ini ditangani dengan cara yang kikuk karena CTE rekursif (berikut) tidak memungkinkan agregat, TOP (), atau operator lain. Ini tidak berlaku, tetapi berhasil.CTE
Numbered
adalah untuk menegakkan urutan yang diketahui bebas celah pada data. Ini menghindari banyak perbandingan nanti.CTE
Incremented
adalah tempat terjadinya tindakan. Intinya saya menggunakan CTE rekursif untuk melangkah melalui data dan menegakkan aturan. Nomor baris yang dihasilkanNumbered
( atas) digunakan untuk mendorong pemrosesan rekursif.Benih CTE rekursif hanya mendapatkan tanggal pertama untuk setiap ContractID dan menginisialisasi nilai yang akan digunakan untuk memutuskan apakah interval baru diperlukan.
Memutuskan apakah interval baru harus dimulai memerlukan tanggal mulai interval saat ini, daftar hari dan panjang setiap kesenjangan dalam tanggal kalender. Ini dapat diatur ulang atau diteruskan, tergantung pada keputusan. Oleh karena itu bagian rekursif adalah verbose dan sedikit pengulangan, karena kita harus memutuskan apakah akan memulai interval baru untuk lebih dari satu nilai kolom.
Logika keputusan untuk kolom
WeekDays
danIntervalStart
harus memiliki logika keputusan yang sama - dapat dipotong dan disisipkan di antara mereka. Jika logika untuk memulai interval baru adalah untuk mengubah ini adalah kode untuk diubah. Idealnya itu akan diabstraksi, oleh karena itu; melakukan ini dalam CTE rekursif mungkin sulit.The
EXISTS()
klausa adalah pembuangan tidak mampu untuk menggunakan fungsi agregat dalam CTE rekursif. Yang perlu dilakukan adalah melihat apakah hari-hari yang berada dalam celah sudah dalam interval saat ini.Tidak ada yang ajaib tentang bersarangnya klausa logika. Jika lebih jelas dalam konformasi lain, atau menggunakan KASUS bersarang, katakanlah, tidak ada alasan untuk tetap seperti ini.
Yang terakhir
SELECT
adalah memberikan output dalam format yang diinginkan.Mengaktifkan PK
Src.ID
tidak berguna untuk metode ini. Indeks berkerumun aktif(ContractID,dt)
akan menyenangkan, saya pikir.Ada beberapa sisi yang kasar. Hari-hari tidak dikembalikan dalam urutan dow, tetapi dalam urutan kalender mereka muncul di sumber data. Semuanya harus dilakukan dengan @Helper klunky dan bisa dihaluskan. Saya suka ide menggunakan satu bit per hari dan menggunakan fungsi biner, bukan
LIKE
. Memisahkan beberapa CTE tambahan ke dalam tabel temp dengan indeks yang tepat tidak diragukan lagi akan membantu.Salah satu tantangan dengan ini adalah bahwa "minggu" tidak sejajar dengan kalender standar, tetapi didorong oleh data, dan me-reset ketika ditentukan bahwa interval baru harus dimulai. "Seminggu", atau setidaknya satu interval, bisa dari satu hari panjang hingga mencakup seluruh dataset.
Demi kepentingan, inilah perkiraan biaya terhadap data sampel Geoff (terima kasih untuk itu!) Setelah berbagai perubahan:
Perkiraan dan jumlah aktual dari baris sangat berbeda.
Rencana tersebut memiliki spoo meja, kemungkinan akibat CTE rekursif. Sebagian besar tindakan adalah di meja kerja yang datang dari itu:
Hanya cara rekursif diterapkan, saya kira!
sumber
MAX(g.IntervalStart)
sepertinya aneh, karenag.IntervalStart
ada diGROUP BY
. Saya berharap ini memberikan kesalahan sintaks, tetapi berhasil. Haruskah itug.IntervalStart as StartDT
masukSELECT
? Ataug.IntervalStart
tidak seharusnya diGROUP BY
?MissingDays
danNumbered
diganti dengan tabel temp dengan indeks yang tepat, itu bisa memiliki kinerja yang layak. Indeks apa yang akan Anda rekomendasikan? Saya bisa mencobanya besok pagi.Numbered
dengan tabel temp & indeks berkerumun(ContractID, rn)
akan layak untuk dicoba. Tanpa dataset besar untuk menghasilkan rencana yang sesuai sulit untuk ditebak. FisikMissingDates
dengan indeks(StartDay, FollowingDayInt)
juga bagus.