Saya akan menjelaskan masalah dalam hal memuat sejumlah truk dengan pesanan, serata mungkin.
Input:
@TruckCount - the number of empty trucks to fill
Satu set:
OrderId,
OrderDetailId,
OrderDetailSize,
TruckId (initially null)
Orders
terdiri dari satu atau lebih OrderDetails
.
Tantangannya di sini adalah untuk menetapkan a TruckId
untuk setiap catatan.
Satu pesanan tidak dapat dibagi di truk.
Truk harus sama rata * dimuat sebanyak mungkin, diukur dengan sum(OrderDetailSize)
.
* Merata: Delta terkecil yang dapat dicapai antara truk dengan muatan paling sedikit dan truk dengan muatan terbanyak. Dengan definisi ini, 1,2,3 lebih merata daripada 1,1,4. Jika itu membantu, anggaplah Anda adalah algoritma statistik, buat bahkan histogram ketinggian.
Tidak ada pertimbangan untuk muatan truk maksimum. Ini adalah truk elastis ajaib. Namun jumlah truk sudah diperbaiki.
Jelas ada solusi yang berulang - round robin mengalokasikan pesanan.
Tetapi dapatkah hal itu dilakukan sebagai logika berbasis set?
Minat utama saya adalah untuk SQL Server 2014 atau lebih baru. Tetapi menetapkan solusi berbasis untuk platform lain juga bisa menarik.
Ini terasa seperti wilayah Itzik Ben-Gan :)
Aplikasi dunia nyata saya mendistribusikan beban kerja pemrosesan ke sejumlah ember agar sesuai dengan jumlah CPU logis. Karenanya setiap ember tidak memiliki ukuran maksimum. Statistik pembaruan, khususnya. Saya hanya berpikir lebih menyenangkan untuk mengabstraksi masalah menjadi truk sebagai cara membingkai tantangan.
CREATE TABLE #OrderDetail (
OrderId int NOT NULL,
OrderDetailId int NOT NULL PRIMARY KEY,
OrderDetailSize tinyint NOT NULL,
TruckId tinyint NULL)
-- Sample Data
INSERT #OrderDetail (OrderId, OrderDetailId, OrderDetailSize)
VALUES
(1 ,100 ,75 ),
(2 ,101 ,5 ),
(2 ,102 ,5 ),
(2 ,103 ,5 ),
(2 ,104 ,5 ),
(2 ,105 ,5 ),
(3 ,106 ,100),
(4 ,107 ,1 ),
(5 ,108 ,11 ),
(6 ,109 ,21 ),
(7 ,110 ,49 ),
(8 ,111 ,25 ),
(8 ,112 ,25 ),
(9 ,113 ,40 ),
(10 ,114 ,49 ),
(11 ,115 ,10 ),
(11 ,116 ,10 ),
(12 ,117 ,15 ),
(13 ,118 ,18 ),
(14 ,119 ,26 )
--> YOUR SOLUTION HERE
-- After assigning Trucks, Measure delta between most and least loaded trucks.
-- Zero is perfect score, however the challenge is a set based solution that will scale, and produce good results, rather
-- than iterative solution that will produce perfect results by exploring every possibility.
SELECT max(TruckOrderDetailSize) - MIN(TruckOrderDetailSize) AS TruckMinMaxDelta
FROM
(SELECT SUM(OrderDetailSize) AS TruckOrderDetailSize FROM #OrderDetail GROUP BY TruckId) AS Truck
DROP TABLE #OrderDetail
sumber
Jawaban:
Pikiran pertama saya adalah
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
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
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
Membentang ini untuk mencakup empat belas Pesanan dalam data contoh, dan menyederhanakan nama-nama yang kita dapatkan ini:
Saya memilih untuk menyimpan hasil antara dalam tabel sementara untuk kenyamanan.
Langkah-langkah selanjutnya akan jauh lebih mudah jika data tersebut terlebih dahulu UNPIVOTED.
Bobot dapat diperkenalkan dengan bergabung ke tabel Pesanan.
Pertanyaannya sekarang dapat dijawab dengan menemukan pengaturan yang memiliki perbedaan terkecil antara truk yang paling banyak memuat dan yang paling sedikit memuat
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:
Namun, tidak ada bedanya, apakah kita memberi label pada item yang dimaksud 'Pesanan' atau 'PesananDetail', solusinya tetap sama.
sumber
Melihat kebutuhan dunia nyata Anda (yang saya asumsikan adalah upaya untuk menyeimbangkan beban kerja Anda di satu set CPU) ...
Apakah ada alasan mengapa Anda perlu menetapkan proses ke bucket / cpus tertentu? [Mencoba untuk memahami Anda nyata kebutuhan ]
Sebagai contoh Anda 'pembaruan statistik', bagaimana Anda tahu berapa lama operasi tertentu akan memakan waktu? Bagaimana jika operasi tertentu mengalami keterlambatan yang tak terduga (misalnya, fragmentasi tabel / indeks yang lebih dari yang direncanakan / berlebihan, pengguna yang sudah berjalan lama dapat memblokir operasi 'pembaruan statistik')?
Untuk tujuan penyeimbangan beban, saya biasanya membuat daftar tugas (misalnya, daftar tabel agar statistik diperbarui) dan menempatkan daftar kata dalam tabel (sementara / awal).
Struktur tabel dapat dimodifikasi sesuai kebutuhan Anda, misalnya:
Selanjutnya saya memulai sejumlah X proses bersamaan untuk melakukan operasi 'pembaruan statistik' yang sebenarnya, dengan setiap proses melakukan hal berikut:
tasks
meja (memastikan tidak ada tugas yang diambil oleh lebih dari satu proses; harus berupa kunci yang berumur pendek)start = NULL
('pertama' akan ditentukan oleh Anda, mis. dipesan olehpriority
?)start = getdate(), thread = <process_number>
id
dantarget/command
nilai - nilaitarget
(secara alternatif, jalankancommand
) dan ketika selesai ...tasks
denganend = getdate() where id = <id>
Dengan desain di atas, saya sekarang memiliki operasi yang seimbang (sebagian besar) secara dinamis.
CATATAN:
tasks
tasks
tabel harus memberikan manfaat lain, misalnya, sejarah waktu proses yang dapat Anda arsipkan untuk referensi di masa mendatang, riwayat waktu proses yang dapat digunakan untuk memodifikasi prioritas, memberikan status operasi saat ini, dll.tasks
mungkin tampak sedikit berlebihan, perlu diingat bahwa kami harus merencanakan kemungkinan masalah 2 (atau lebih) proses yang berusaha untuk mendapatkan tugas baru pada waktu yang sama , jadi kami perlu menjamin tugas ditugaskan hanya untuk satu proses (dan ya, Anda dapat memperoleh hasil yang sama dengan pernyataan 'pembaruan / pilih' kombo - tergantung pada kemampuan bahasa SQL RDBMS Anda); langkah mendapatkan 'tugas' baru harus cepat, yaitu, 'kunci eksklusif' harus berumur pendek dan dalam kenyataannya, proses akan memukultasks
secara acak sehingga akan sedikit menghalangi lagianSecara pribadi, saya menemukan
tasks
proses yang didorong tabel ini sedikit lebih mudah untuk diimplementasikan dan dipertahankan ... sebagai lawan dari proses (biasanya) yang lebih kompleks dalam mencoba untuk menetapkan tugas / pemetaan proses ... ymmv.Tentunya untuk contoh percaya Anda, Anda tidak dapat meminta truk Anda kembali ke distribusi / gudang untuk pesanan berikutnya, jadi Anda perlu menetapkan pesanan terlebih dahulu ke berbagai truk (perlu diingat bahwa UPS / Fedex / dll juga harus menetapkan berdasarkan rute pengiriman untuk mengurangi waktu pengiriman dan penggunaan gas).
Namun, dalam contoh dunia nyata Anda ('pembaruan statistik') tidak ada alasan mengapa tugas / proses tugas tidak dapat dilakukan secara dinamis sehingga memastikan peluang yang lebih baik untuk menyeimbangkan beban kerja (lintas CPU dan dalam hal mengurangi waktu berjalan keseluruhan) .
CATATAN: Saya secara rutin melihat (IT) orang-orang yang mencoba melakukan pra-penugasan tugas-tugas mereka (sebagai bentuk load balancing) sebelum benar-benar menjalankan tugas-tugas tersebut, dan dalam setiap kasus ia akhirnya harus terus-menerus mengubah proses pra-penugasan untuk mengambil mempertimbangkan berbagai masalah tugas yang terus-menerus (mis., tingkat fragmentasi dalam tabel / indeks, aktivitas pengguna bersamaan, dll).
sumber
buat dan isi tabel angka sesuai keinginan. Ini hanya satu kali pembuatan.
Meja truk dibuat
Saya telah membuat satu
OrderSummary
TabelSilakan periksa nilai Delta saya dan beri tahu saya jika itu salah
Anda dapat memeriksa hasil CTE1, semuanya memungkinkan
Permutation and Combination of order along with their size
.Jika pendekatan saya benar sampai di sini, maka saya butuh bantuan seseorang.
saring dan Bagi hasil
CTE1
menjadi 3 bagian (Truck count
) sedemikian rupa sehinggaOrderid
unik di antara setiap kelompok dan setiap bagian TruckOrderSize
dekat dengan Delta.sumber