Saya mencoba memahami bagaimana cara menyimpan informasi yang dipesan dengan benar dalam database relasional.
Sebuah contoh:
Say I have a Playlist, terdiri dari Songs. Di dalam Database Relasional saya, saya memiliki tabel Playlists
, berisi beberapa metadata (nama, pencipta, dll). Saya juga memiliki tabel yang disebut Songs
, berisi playlist_id
, dan info spesifik lagu (nama, artis, durasi, dll).
Secara default, ketika Lagu baru ditambahkan ke Daftar Putar, ditambahkan ke bagian akhir. Saat memesan pada Song-ID (ascending), pesanan akan menjadi urutan penambahan. Tetapi bagaimana jika pengguna harus dapat memesan ulang lagu di playlist?
Saya datang dengan beberapa ide, masing-masing dengan kelebihan dan kekurangan mereka:
- Kolom disebut
order
, yang merupakan bilangan bulat . Saat lagu dipindahkan, urutan semua lagu antara posisi lama dan baru diubah, untuk mencerminkan perubahan. Kelemahan dari ini adalah bahwa banyak pertanyaan perlu dilakukan setiap kali lagu dipindahkan, dan algoritma pemindahannya tidak sepele seperti dengan opsi lainnya. - Kolom disebut
order
, yang merupakan desimal (NUMERIC
). Ketika sebuah lagu dipindahkan, ia diberi nilai floating point antara dua angka yang berdekatan. Kekurangan: Bidang desimal membutuhkan lebih banyak ruang, dan dimungkinkan untuk kehabisan presisi, kecuali kehati-hatian dilakukan untuk mendistribusikan kembali kisaran setelah setiap beberapa perubahan. - Cara lain adalah dengan memiliki
previous
dannext
bidang yang mereferensikan Lagu lain. (atau NULL untuk lagu pertama, terakhir lagu terakhir dalam daftar putar sekarang; Pada dasarnya Anda membuat daftar tertaut ). Kekurangan: Pertanyaan seperti 'temukan Lagu Ke-X dalam daftar' bukan lagi waktu konstan, melainkan waktu linear.
Manakah dari prosedur ini yang paling sering digunakan dalam praktek? Manakah dari prosedur ini yang tercepat dari basis data menengah ke besar? Apakah ada cara lain untuk mengarsipkan ini?
EDIT: Demi kesederhanaan, dalam contoh Lagu hanya milik satu Playlist (hubungan banyak-ke-satu). Tentu saja, orang juga bisa menggunakan Tabel Junction sehingga daftar lagu-adalah hubungan banyak-ke-banyak (dan menerapkan salah satu strategi di atas pada tabel itu).
update songorder set order = order - 1 where order >= 12 & order <= 42; update songorder set order = 42 where id = 123;
- itu dua pembaruan - bukan tiga puluh. Tiga jika Anda ingin menempatkan batasan unik pada pesanan.Queries like 'find the Xth Song in the list' are no longer constant-time
ini juga berlaku untuk opsi 2.Jawaban:
Database dioptimalkan untuk hal-hal tertentu. Memperbarui banyak baris dengan cepat adalah salah satunya. Ini menjadi benar terutama ketika Anda membiarkan database melakukan tugasnya.
Mempertimbangkan:
Dan Anda ingin pindah
Beat It
ke akhir, Anda akan memiliki dua pertanyaan:Dan itu saja. Ini berskala sangat baik dengan jumlah yang sangat besar. Coba letakkan beberapa ribu lagu dalam daftar putar hipotetis di database Anda dan lihat berapa lama waktu yang dibutuhkan untuk memindahkan lagu dari satu lokasi ke lokasi lain. Karena ini memiliki bentuk yang sangat standar:
Anda memiliki dua pernyataan siap yang dapat Anda gunakan kembali dengan sangat efisien.
Ini memberikan beberapa keuntungan signifikan - urutan tabel adalah sesuatu yang dapat Anda alasankan. Lagu ketiga memiliki
order
3, selalu. Satu-satunya cara untuk menjamin ini adalah dengan menggunakan bilangan bulat berturut-turut sebagai pesanan. Menggunakan daftar yang ditautkan pseudo atau angka desimal atau bilangan bulat dengan celah tidak akan membuat Anda menjamin properti ini; dalam kasus ini satu-satunya cara untuk mendapatkan lagu ke-n adalah menyortir seluruh tabel dan mendapatkan catatan ke-n.Dan sungguh, ini jauh lebih mudah daripada yang Anda pikirkan. Mudah untuk mengetahui apa yang ingin Anda lakukan, untuk menghasilkan dua pernyataan pembaruan dan bagi orang lain untuk melihat dua pernyataan pembaruan tersebut dan menyadari apa yang sedang dilakukan.
sumber
order
karenaorder by
merupakan kata kunci?order
nilai saat menambahkan lagu baru ke daftar putar. Katakan itu lagu ke-9, apakah ada cara yang lebih baik untuk memasukkan 9 ke dalamorder
daripada melakukanCOUNT
sebelum menambahkan catatan?Pertama-tama, tidak jelas dari deskripsi Anda tentang apa yang telah Anda lakukan, tetapi Anda membutuhkan
PlaylistSongs
tabel yang berisi aPlaylistId
dan aSongId
, yang menggambarkan lagu mana yang termasuk daftar putar mana.Dalam tabel ini Anda harus menambahkan informasi pemesanan.
Mekanisme favorit saya adalah dengan bilangan real. Saya menerapkannya baru-baru ini, dan itu berfungsi seperti pesona. Saat Anda ingin memindahkan lagu ke posisi tertentu, Anda menghitung
Ordering
nilai baru sebagai rata-rataOrdering
nilai lagu sebelumnya dan lagu berikutnya. Jika Anda menggunakan bilangan real 64-bit, Anda akan kehabisan presisi pada waktu yang sama ketika neraka akan membeku, tetapi jika Anda benar-benar menulis perangkat lunak Anda untuk anak cucu, maka pertimbangkan untuk menetapkan ulang bilangan bulat bulat yang bagusOrdering
nilai untuk semua lagu di masing-masing daftar putar sesekali.Sebagai bonus tambahan, berikut adalah kode yang saya tulis yang mengimplementasikan ini. Tentu saja Anda tidak dapat menggunakannya sebagaimana adanya, dan itu akan terlalu sulit bagi saya sekarang untuk membersihkannya untuk Anda, jadi saya hanya mempostingnya agar Anda mendapatkan ide darinya.
Kelasnya adalah
ParameterTemplate
(apa pun, jangan tanya!) Metode ini mendapatkan daftar templat parameter yang dimiliki templat ini dari induknyaActivityTemplate
. (Terserah, jangan tanya!) Kode berisi beberapa penjaga terhadap kehabisan presisi. Pembagi digunakan untuk pengujian: unit test menggunakan pembagi besar sehingga kehabisan presisi dengan cepat, dan dengan demikian memicu kode pelindung presisi. Metode kedua adalah publik dan "hanya untuk penggunaan internal; jangan meminta" sehingga kode pengujian dapat memohonnya. (Itu tidak bisa paket-pribadi karena kode pengujian saya tidak dalam paket yang sama dengan kode yang diuji.) Bidang yang mengontrol pemesanan disebutOrdering
, diakses melaluigetOrdering()
dansetOrdering()
. Anda tidak melihat SQL apa pun karena saya menggunakan Object-Relational Mapping via Hibernate.sumber
Apa yang berhasil bagi saya, untuk daftar kecil di urutan 100 item adalah untuk mengambil pendekatan hybrid:
Jadi Anda berakhir dengan urutan bilangan bulat tanpa celah, disimpan dalam kolom desimal. Rasanya cukup bersih. Tetapi mungkin tidak meningkatkan dengan sangat baik setelah Anda memiliki ratusan ribu baris yang perlu Anda perbarui, sekaligus. Tetapi jika Anda melakukannya, mengapa Anda menggunakan jenis yang ditentukan pengguna di tempat pertama? (Catatan: jika Anda memiliki tabel besar dengan jutaan pengguna tetapi setiap pengguna hanya memiliki beberapa ratus item untuk disortir, Anda dapat menggunakan pendekatan di atas dengan baik karena Anda akan menggunakan klausa mana untuk membatasi perubahan hanya pada satu pengguna )
sumber