Apakah menggunakan beberapa kunci asing dipisahkan dengan koma salah, dan jika demikian, mengapa?

31

Ada dua tabel: Dealdan DealCategories. Satu kesepakatan dapat memiliki banyak kategori kesepakatan.

Jadi cara yang tepat adalah membuat tabel DealCategoriesdengan struktur berikut:

DealCategoryId (PK)
DealId (FK)
DealCategoryId (FK)

Namun, tim outsourcing kami menyimpan beberapa kategori dalam Dealtabel dengan cara ini:

DealId (PK)
DealCategory -- In here they store multiple deal ids separated by commas like this: 18,25,32.

Saya merasa apa yang mereka lakukan salah, tetapi saya tidak tahu bagaimana menjelaskan dengan jelas mengapa ini tidak benar.

Bagaimana saya harus menjelaskan kepada mereka bahwa ini salah? Atau mungkin akulah yang salah dan ini bisa diterima?

Sarawut Positwinyu
sumber
20
Kamu benar. Apakah menyimpan daftar yang dipisahkan koma di kolom database benar-benar buruk? . Jawaban singkat: Ya, seburuk itu.
ypercubeᵀᴹ
7
tembak tim outsourcing segera sebelum mereka melakukan kerusakan lagi ... (-_-)
Rafa

Jawaban:

49

Ya itu ide yang buruk.

Alih-alih pergi:

SELECT Deal.Name, DealCategory.Name
FROM Deal
  INNER JOIN
     DealCategories ON Deal.DealID = DealCategories.DealID
  INNER JOIN
     DealCategory ON DealCategories.DealCategoryID = DealCategory.DealCategoryID
WHERE Deal.DealID = 1234

Anda sekarang harus pergi:

SELECT Deal.ID, Deal.Name, DealCategories
FROM Deal
WHERE Deal.DealID = 1234

Maka Anda perlu melakukan hal-hal dalam kode aplikasi Anda untuk membagi daftar koma itu menjadi angka-angka individual, lalu query database secara terpisah:

SELECT DealCategory.Name
FROM DealCategory
WHERE DealCategory.DealCategoryID IN (<<that list from before>>)

Antipattern desain ini berasal dari kesalahpahaman lengkap pemodelan relasional (Anda tidak perlu takut tabel. Tabel adalah teman Anda. Gunakan mereka), atau keyakinan salah arah yang aneh, lebih cepat mengambil daftar yang dipisahkan koma dan membaginya dalam kode aplikasi daripada menambahkan tabel tautan (tidak pernah ada). Opsi ketiga adalah bahwa mereka tidak cukup percaya diri / cukup kompeten dengan SQL untuk dapat mengatur kunci asing, tetapi jika itu masalahnya mereka seharusnya tidak ada hubungannya dengan desain model relasional.

SQL Antipatterns (Karwin, 2010) mencurahkan seluruh bab untuk antipattern ini (yang ia sebut 'Jaywalking'), halaman 15-23. Juga, penulis telah memposting pertanyaan serupa di SO . Poin-poin penting yang dia catat (sebagaimana diterapkan pada contoh ini) adalah:

  • Permintaan untuk semua transaksi dalam kategori tertentu agak rumit (cara termudah untuk menyelesaikan masalah itu adalah ekspresi reguler, tetapi ekspresi reguler adalah masalah di dalam dan dari dirinya sendiri).
  • Anda tidak dapat menerapkan integritas referensial tanpa hubungan kunci asing. Jika Anda menghapus DealCategory nr. # 26, Anda kemudian, dalam kode aplikasi Anda, harus melalui setiap transaksi mencari referensi ke kategori # 26 dan menghapusnya. Ini adalah sesuatu yang harus ditangani pada lapisan data, dan harus menanganinya di aplikasi Anda adalah hal yang sangat buruk .
  • Permintaan agregat ( COUNT, SUMdll), sekali lagi, bervariasi dari 'rumit' hingga 'hampir tidak mungkin'. Tanyakan pengembang Anda bagaimana mereka membuat Anda daftar semua kategori dengan jumlah jumlah penawaran dalam kategori itu. Dengan desain yang tepat, itulah empat baris SQL.
  • Pembaruan menjadi jauh lebih sulit (yaitu Anda memiliki kesepakatan yang ada di lima kategori, tetapi Anda ingin menghapus dua dan menambahkan tiga yang lain). Itu tiga baris SQL dengan desain yang tepat.
  • Akhirnya Anda akan mengalami VARCHARbatasan panjang daftar. Meskipun jika Anda memiliki daftar yang dipisahkan koma, lebih dari 4000 karakter, kemungkinan parsing monster itu akan menjadi lambat sekali.
  • Menarik daftar dari database, membaginya, dan kemudian kembali ke database untuk query lain secara intrinsik lebih lambat dari satu query.

TLDR: Ini adalah desain yang cacat secara fundamental, tidak akan menskala dengan baik, ini memperkenalkan kompleksitas tambahan bahkan untuk pertanyaan paling sederhana, dan saat itu juga, itu akan memperlambat aplikasi Anda.

Simon Righarts
sumber
1
Simon, seseorang melakukan pertanyaan yang sama ( dba.stackexchange.com/questions/17824/… ), tetapi saya tidak memiliki alasan yang jelas mengapa FK dan PK yang sama berada di meja yang sama, yang mengerem 3FN.
jcho360
2
Saya tidak sepenuhnya yakin apakah mereka ingin memiliki hubungan banyak-ke-banyak antara Penawaran dan Kategori, atau semacam hirarki Kategori. Either way, itu adalah sampingan ke titik utama, bahwa menjadi bidang yang dibatasi koma bukan tabel tautan adalah ide yang buruk.
Simon Righarts
4

Namun, tim outsourcing kami menyimpan beberapa kategori dalam tabel Transaksi dengan cara ini:

DealId (PK) DealCategory - Di sini mereka menyimpan beberapa id kesepakatan yang dipisahkan oleh koma seperti ini: 18,25,32.

Itu sebenarnya desain yang bagus jika Anda hanya perlu menanyakan kategori untuk kesepakatan yang diberikan.

Tapi itu mengerikan jika Anda ingin mengetahui semua penawaran dalam kategori tertentu.

Dan itu juga membuatnya sangat sulit dan rawan kesalahan untuk melakukan hal lain - seperti pembaruan, hitungan, bergabung, dll.

Denormalization memiliki tempatnya, tetapi Anda harus mengingatnya melakukan optimasi untuk satu jenis kueri dengan mengorbankan semua lainnya yang mungkin Anda buat terhadap data yang sama. Jika Anda tahu Anda akan selalu bertanya dalam satu pola, maka mungkin memberi Anda keuntungan untuk menggunakan desain denormalized. Tetapi jika ada kemungkinan Anda bisa membutuhkan lebih banyak fleksibilitas dalam jenis pertanyaan, tetap dengan desain normal.

Seperti bentuk pengoptimalan lainnya, Anda perlu mengetahui kueri apa yang akan dijalankan sebelum Anda dapat memutuskan apakah denasionalisasi dibenarkan.

Bill Karwin
sumber
1
Apakah Anda benar-benar berpikir string dengan ID anak yang dipisahkan koma bermanfaat? Maksudku, aplikasi harus membaca dulu, lalu mengurai ID dan meminta semua anak, suka select * from DealCategories where DealId in (1,2,3,4,...). Anda memiliki lebih banyak pengalaman, mengenai desain basis data, daripada saya, jadi mungkin Anda memiliki alasan yang bagus dalam beberapa kasus untuk "penyetelan ekstrem" dalam kasus yang sangat spesifik. Satu-satunya ide saya untuk membenarkan ini adalah selectbeban yang sangat tinggi pada Deal / DealCategory. Bagi saya ini terlihat seperti beberapa tim outsourcing tanpa pengetahuan desain DB, selain membuat tabel, menciptakannya.
Erik Hart
1
@ ErikHart, ini adalah denormalisasi, dan ini bisa membantu, tetapi poin saya adalah bahwa itu sepenuhnya tergantung pada pertanyaan yang Anda perlu jalankan. Anda benar bahwa denasionalisasi membuat semua kueri berkinerja lebih buruk, kecuali satu kueri yang dioptimalkan untuknya. Jika Anda hanya perlu menjalankan kueri yang satu itu, dan Anda tidak peduli dengan kueri lainnya, itu adalah kemenangan. Tetapi ini adalah kasus yang jarang, karena biasanya kami ingin fleksibilitas untuk meminta data dalam berbagai cara.
Bill Karwin
1
@ErikHart, jika tim outsourcing diberi spesifikasi proyek yang hanya menyertakan satu permintaan terhadap data ini, mereka dapat merancang pengoptimalan untuk permintaan khusus itu saja. Dengan kata lain, "Anda memintanya, Anda mendapatkannya." Tetapi penyedia outsourcing tidak memiliki alasan untuk merencanakan penggunaan data di masa mendatang - mereka menerapkan aplikasi pada surat yang tertulis dalam spesifikasi.
Bill Karwin
1

Nilai berganda dalam suatu kolom bertentangan dengan formulir normal pertama.

Ini juga sama sekali tidak mendapatkan kecepatan, karena tabel harus ditautkan dalam database. Anda harus membaca dan mengurai string terlebih dahulu, lalu pilih semua kategori untuk "Kesepakatan".

Implementasi yang benar akan menjadi tabel persimpangan seperti "DealDealCategories", dengan DealId dan DealCategoryId.

Implementasi hierarki yang buruk?

Juga, FK di DealCategories ke DealCategory lain tampak seperti implementasi hierarki / pohon DealCategories yang buruk. Bekerja dengan pohon melalui relasi Parent ID (disebut adjacency list) sungguh menyebalkan!

Periksa Nested Set (bagus untuk dibaca, tetapi sulit dimodifikasi) dan Tabel Penutupan (kinerja keseluruhan terbaik, tetapi kemungkinan penggunaan memori tinggi - mungkin tidak terlalu banyak untuk DealCategories Anda) saat menerapkan hierarki!

Erik Hart
sumber