Pikiran pertama saya adalah
select
<best solution>
from
<all possible combinations>
Bagian "solusi terbaik" didefinisikan dalam pertanyaan - perbedaan terkecil antara truk paling banyak dimuat dan paling sedikit dimuat. Bit lain - semua kombinasi - membuat saya berhenti untuk berpikir.
Pertimbangkan situasi di mana kami memiliki tiga pesanan A, B dan C dan tiga truk. Kemungkinannya adalah
Truck 1 Truck 2 Truck 3
------- ------- -------
A B C
A C B
B A C
B C A
C A B
C B A
AB C -
AB - C
C AB -
- AB C
C - AB
- C AB
AC B -
AC - B
B AC -
- AC B
B - AC
- B AC
BC A -
BC - A
A BC -
- BC A
A - BC
- A BC
ABC - -
- ABC -
- - ABC
Table A: all permutations.
Banyak dari ini simetris. Enam baris pertama, misalnya, hanya berbeda di mana truk setiap pesanan ditempatkan. Karena truk dapat difungsikan, pengaturan ini akan menghasilkan hasil yang sama. Saya akan mengabaikan ini untuk saat ini.
Ada pertanyaan yang diketahui untuk menghasilkan permutasi dan kombinasi. Namun, ini akan menghasilkan pengaturan dalam satu ember. Untuk masalah ini saya perlu pengaturan lintas beberapa ember.
Melihat output dari kueri "semua kombinasi" standar
;with Numbers as
(
select n = 1
union
select 2
union
select 3
)
select
a.n,
b.n,
c.n
from Numbers as a
cross join Numbers as b
cross join Numbers as c
order by 1, 2, 3;
n n n
--- --- ---
1 1 1
1 1 2
1 1 3
1 2 1
<snip>
3 2 3
3 3 1
3 3 2
3 3 3
Table B: cross join of three values.
Saya mencatat hasil membentuk pola yang sama seperti Tabel A. Dengan membuat lompatan kongnitif dengan mempertimbangkan setiap kolom sebagai Pesanan 1 , nilai - nilai untuk mengatakan truk mana yang akan memegang Pesanan itu, dan baris untuk menjadi pengaturan Pesanan dalam truk. Kueri kemudian menjadi
select
Arrangement = ROW_NUMBER() over(order by (select null)),
First_order_goes_in = a.TruckNumber,
Second_order_goes_in = b.TruckNumber,
Third_order_goes_in = c.TruckNumber
from Trucks a -- aka Numbers in Table B
cross join Trucks b
cross join Trucks c
Arrangement First_order_goes_in Second_order_goes_in Third_order_goes_in
----------- ------------------- -------------------- -------------------
1 1 1 1
2 1 1 2
3 1 1 3
4 1 2 1
<snip>
Query C: Orders in trucks.
Membentang ini untuk mencakup empat belas Pesanan dalam data contoh, dan menyederhanakan nama-nama yang kita dapatkan ini:
;with Trucks as
(
select *
from (values (1), (2), (3)) as T(TruckNumber)
)
select
arrangement = ROW_NUMBER() over(order by (select null)),
First = a.TruckNumber,
Second = b.TruckNumber,
Third = c.TruckNumber,
Fourth = d.TruckNumber,
Fifth = e.TruckNumber,
Sixth = f.TruckNumber,
Seventh = g.TruckNumber,
Eigth = h.TruckNumber,
Ninth = i.TruckNumber,
Tenth = j.TruckNumber,
Eleventh = k.TruckNumber,
Twelth = l.TruckNumber,
Thirteenth = m.TruckNumber,
Fourteenth = n.TruckNumber
into #Arrangements
from Trucks a
cross join Trucks b
cross join Trucks c
cross join Trucks d
cross join Trucks e
cross join Trucks f
cross join Trucks g
cross join Trucks h
cross join Trucks i
cross join Trucks j
cross join Trucks k
cross join Trucks l
cross join Trucks m
cross join Trucks n;
Query D: Orders spread over trucks.
Saya memilih untuk menyimpan hasil antara dalam tabel sementara untuk kenyamanan.
Langkah-langkah selanjutnya akan jauh lebih mudah jika data tersebut terlebih dahulu UNPIVOTED.
select
Arrangement,
TruckNumber,
ItemNumber = case NewColumn
when 'First' then 1
when 'Second' then 2
when 'Third' then 3
when 'Fourth' then 4
when 'Fifth' then 5
when 'Sixth' then 6
when 'Seventh' then 7
when 'Eigth' then 8
when 'Ninth' then 9
when 'Tenth' then 10
when 'Eleventh' then 11
when 'Twelth' then 12
when 'Thirteenth' then 13
when 'Fourteenth' then 14
else -1
end
into #FilledTrucks
from #Arrangements
unpivot
(
TruckNumber
for NewColumn IN
(
First,
Second,
Third,
Fourth,
Fifth,
Sixth,
Seventh,
Eigth,
Ninth,
Tenth,
Eleventh,
Twelth,
Thirteenth,
Fourteenth
)
) as q;
Query E: Filled trucks, unpivoted.
Bobot dapat diperkenalkan dengan bergabung ke tabel Pesanan.
select
ft.arrangement,
ft.TruckNumber,
TruckWeight = sum(i.Size)
into #TruckWeights
from #FilledTrucks as ft
inner join #Order as i
on i.OrderId = ft.ItemNumber
group by
ft.arrangement,
ft.TruckNumber;
Query F: truck weights
Pertanyaannya sekarang dapat dijawab dengan menemukan pengaturan yang memiliki perbedaan terkecil antara truk yang paling banyak memuat dan yang paling sedikit memuat
select
Arrangement,
LightestTruck = MIN(TruckWeight),
HeaviestTruck = MAX(TruckWeight),
Delta = MAX(TruckWeight) - MIN(TruckWeight)
from #TruckWeights
group by
arrangement
order by
4 ASC;
Query G: most balanced arrangements
Diskusi
Ada banyak masalah dengan ini. Pertama adalah algoritma brute-force. Jumlah baris dalam tabel kerja eksponensial dalam jumlah truk dan pesanan. Jumlah baris dalam #Arrangements adalah (jumlah truk) ^ (jumlah pesanan). Ini tidak akan skala dengan baik.
Kedua adalah bahwa kueri SQL memiliki jumlah Pesanan yang tertanam di dalamnya. Satu-satunya cara untuk mengatasi ini adalah dengan menggunakan SQL dinamis, yang memiliki masalah sendiri. Jika jumlah pesanan dalam ribuan mungkin ada saatnya ketika SQL yang dihasilkan menjadi terlalu panjang.
Ketiga adalah redundansi dalam pengaturan. Ini menggembungkan tabel perantara meningkatkan runtime sangat.
Keempat, banyak baris di #Arrangements membiarkan satu atau lebih truk kosong. Ini tidak mungkin konfigurasi optimal. Akan mudah untuk menyaring baris-baris ini pada saat penciptaan. Saya memilih untuk tidak melakukannya agar kode lebih sederhana dan fokus.
Di sisi atas ini menangani bobot negatif, jika perusahaan Anda pernah mulai mengirim balon helium diisi!
Pikiran
Jika ada cara untuk mengisi #FilledTrucks langsung dari daftar truk dan Pesanan, saya pikir yang terburuk dari masalah ini adalah dapat dikelola. Sayangnya imaginasi saya tersandung pada rintangan itu. Harapan saya adalah beberapa kontributor di masa depan mungkin dapat memasok apa yang luput dari saya.
1 Anda mengatakan semua item untuk pesanan harus di truk yang sama. Ini berarti atom penugasan adalah Orde, bukan OrderDetail. Saya telah menghasilkan ini dari data pengujian Anda sebagai berikut:
select
OrderId,
Size = sum(OrderDetailSize)
into #Order
from #OrderDetail
group by OrderId;
Namun, tidak ada bedanya, apakah kita memberi label pada item yang dimaksud 'Pesanan' atau 'PesananDetail', solusinya tetap sama.