Verileri iki sistem arasında dönüştürmem gerekiyor.
İlk sistem, programları basit bir tarih listesi olarak saklar. Programa dahil edilen her tarih bir satırdır. Tarihler dizisinde çeşitli boşluklar olabilir (hafta sonları, resmi tatiller ve daha uzun aralar, haftanın bazı günleri programdan çıkarılabilir). Hiç boşluk olmayabilir, hafta sonları bile dahil edilebilir. Program 2 yıla kadar olabilir. Genellikle birkaç hafta sürer.
Hafta sonları hariç iki hafta süren basit bir program örneği (aşağıdaki senaryoda daha karmaşık örnekler var):
+----+------------+------------+---------+--------+
| 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
benzersizdir, ancak mutlaka sıralı değildir (birincil anahtardır). Tarihler her Sözleşme içinde benzersizdir (tarihinde benzersiz bir indeks vardır (ContractID, dt)
).
İkinci sistem, programları, programın parçası olan hafta günleri listesiyle aralıklarla saklar. Her aralık, başlangıç ve bitiş tarihleri (dahil) ve programa dahil edilen hafta günlerinin bir listesi ile tanımlanır. Bu formatta, Mon-Wed gibi tekrarlayan haftalık kalıpları etkili bir şekilde tanımlayabilirsiniz, ancak bir kalıp bozulduğunda, örneğin resmi tatil için bir acı haline gelir.
Yukarıdaki basit örnek şöyle görünecektir:
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 1 | 2016-05-02 | 2016-05-13 | 10 | Mon,Tue,Wed,Thu,Fri, |
+------------+------------+------------+----------+----------------------+
[StartDT;EndDT]
Aynı Sözleşmeye ait aralıklar çakışmamalıdır.
İlk sistemden ikinci sistem tarafından kullanılan biçime veri dönüştürmek gerekiyor. Şu anda verilen tek sözleşme için C # istemci tarafında bu çözüyorum, ancak toplu işlem ve sunucular arasında ihracat / ithalat için sunucu tarafında T-SQL yapmak istiyorum. Büyük olasılıkla, CLR UDF kullanılarak yapılabilir, ancak bu aşamada SQLCLR'yi kullanamıyorum.
Buradaki zorluk, aralıkların listesini mümkün olduğunca kısa ve insan dostu hale getirmektir.
Örneğin, bu program:
+-----+------------+------------+---------+--------+
| 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 |
+-----+------------+------------+---------+--------+
bu olmalı:
+------------+------------+------------+----------+----------------------+
| ContractID | StartDT | EndDT | DayCount | WeekDays |
+------------+------------+------------+----------+----------------------+
| 2 | 2016-05-05 | 2016-05-17 | 9 | Mon,Tue,Wed,Thu,Fri, |
+------------+------------+------------+----------+----------------------+
,bu değil:
+------------+------------+------------+----------+----------------------+
| 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
Bu soruna bir yaklaşım uygulamaya çalıştım . Bunu iki geçişte yapmaya çalıştım. İlk geçişte basit ardışık gün adaları buluyorum, yani adanın sonu, hafta sonu, resmi tatil veya başka bir şey olsun, gün dizisindeki herhangi bir boşluk. Bu tür bulunan her ada için virgülle ayrılmış ayrı bir liste hazırlarım WeekDays
. İkinci geçişte I grubu, hafta sayıları sıralamasındaki boşluğa veya WeekDays
.
Bu yaklaşımla, her kısmi hafta yukarıda gösterildiği gibi ekstra bir aralık olarak sona erer, çünkü hafta sayıları ardışık olsa da, WeekDays
değişiklik. Ayrıca, bir hafta içinde düzenli boşluklar olabilir ( ContractID=3
sadece verileri olan örnek verilere bakın Mon,Wed,Fri,
) ve bu yaklaşım, bu programda her gün için ayrı aralıklar oluşturacaktır. Parlak tarafta, programın hiç boşluğu yoksa ( ContractID=7
hafta sonlarını içeren örnek verilerde bakın ) bir aralık oluşturur ve bu durumda başlangıç veya bitiş haftasının kısmi olması önemli değildir.
Neyin peşinde olduğum hakkında daha iyi bir fikir edinmek için lütfen aşağıdaki koddaki diğer örneklere bakın. Hafta sonlarının genellikle hariç tutulduğunu, ancak haftanın diğer günlerinin de hariç tutulabileceğini görebilirsiniz. Örnek 3 Sadece olarak Mon
, Wed
ve Fri
zamanlama bir parçasıdır. Ayrıca, hafta sonları, örnek 7'deki gibi dahil edilebilir. Çözüm, haftanın tüm günlerine eşit muamele etmelidir. Haftanın herhangi bir günü programa dahil edilebilir veya programın dışında bırakılabilir.
Oluşturulan aralık listesinin verilen zamanlamayı doğru bir şekilde açıkladığını doğrulamak için aşağıdaki sahte kodu kullanabilirsiniz:
- tüm aralıklarla döngü
- Başlangıç ve Bitiş tarihleri (dahil) arasındaki tüm takvim tarihleri arasında her aralık döngüsü için.
- her tarih için haftanın gününün
WeekDays
. Evet ise, bu tarih programa dahil edilir.
Umarım, bu hangi durumlarda yeni bir aralık yaratılması gerektiğini netleştirir. 4. ve 5. örneklerde, bir Pazartesi ( 2016-05-09
) zamanlamanın ortasından kaldırılır ve bu zamanlama tek bir aralıkla temsil edilemez. Örnek 6'da çizelgede uzun bir boşluk vardır, bu nedenle iki aralık gereklidir.
Aralıklar, programdaki haftalık kalıpları temsil eder ve bir kalıp bozulduğunda / değiştirildiğinde yeni aralık eklenmelidir. Örnek 11'de ilk üç hafta bir örüntüye sahiptir Tue
, daha sonra bu örüntü olarak değişir Thu
. Sonuç olarak, bu programı tanımlamak için iki aralığa ihtiyacımız var.
Şu anda SQL Server 2008 kullanıyorum, bu yüzden çözüm bu sürümde çalışmalıdır. SQL Server 2008 için bir çözüm daha sonraki sürümlerden özellikler kullanılarak basitleştirilebilir / iyileştirilebilirse, bu bir bonus, lütfen bunu da gösterin.
Bir Calendar
tablo (tarih listesi) ve Numbers
tablo (1'den başlayan tam sayıların listesi) var, bu yüzden gerekirse bunları kullanmak için sorun yok. Geçici tablolar oluşturmak ve verileri birkaç aşamada işleyen birkaç sorguya sahip olmak da iyidir. Algoritmadaki aşama sayısı sabitlenmelidir, imleçler ve açık WHILE
döngüler doğru değildir.
Örnek veriler ve beklenen sonuçlar için komut dosyası
-- @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;
Cevapların karşılaştırılması
Tabloyu @Src
sahip 403,555
olan satırlar 15,857
tat ContractIDs
. Tüm cevaplar doğru sonuç verir (en azından verilerim için) ve hepsi oldukça hızlıdır, ancak optimallik bakımından farklılık gösterir. Ne kadar az aralık üretilirse o kadar iyidir. Ben sadece merak için çalışma süreleri dahil. Ana odak doğru değil, doğru sonuç, hız değil (çok uzun sürmediği sürece - Ziggy Crueltyfree Zeitgeister tarafından özyinelemeyen sorguyu 10 dakika sonra durdurdum).
+--------------------------------------------------------+-----------+---------+
| Answer | Intervals | Seconds |
+--------------------------------------------------------+-----------+---------+
| Ziggy Crueltyfree Zeitgeister | 25751 | 7.88 |
| While loop | | |
| | | |
| Ziggy Crueltyfree Zeitgeister | 25751 | 8.27 |
| Recursive | | |
| | | |
| Michael Green | 25751 | 22.63 |
| Recursive | | |
| | | |
| Geoff Patterson | 26670 | 4.79 |
| Weekly gaps-and-islands with merging of partial weeks | | |
| | | |
| Vladimir Baranov | 34560 | 4.03 |
| Daily, then weekly gaps-and-islands | | |
| | | |
| Mikael Eriksson | 35840 | 0.65 |
| Weekly gaps-and-islands | | |
+--------------------------------------------------------+-----------+---------+
| Vladimir Baranov | 25751 | 121.51 |
| Cursor | | |
+--------------------------------------------------------+-----------+---------+
@Dst
) sahip olmalıdır. Programın ilk iki haftasında sadece bu haftalar Tue
olamaz WeekDays=Tue,Thu,
. Programın son iki haftası sadece Thu
bu yüzden WeekDays=Tue,Thu,
bu haftalar için tekrar sahip olamazsınız . Bunun için en uygun olmayan çözüm üç sıra olacaktır: sadece Tue
ilk iki hafta için, daha sonra Tue,Thu,
her ikisine de sahip olan üçüncü hafta Tue
ve Thu
daha sonra sadece Thu
son iki hafta için.
ContractID
değişiklik olursa, aralıklıysa planlanan günler listesinde bir boşluk varsa, 7 günden fazladır ve yeni hafta günü daha önce görülmemiştir.
(11,'2016-05-03', '2016-05-17', 3, 'Tue,'), (11,'2016-05-19', '2016-06-02', 3, 'Thu,');
@Dst ile bir satır olmamalı mıTue, Thu,
?