Apakah pernah boleh menggunakan daftar dalam basis data relasional?


94

Saya telah mencoba merancang database untuk pergi dengan konsep proyek dan bertemu dengan apa yang tampaknya seperti masalah yang diperdebatkan. Saya telah membaca beberapa artikel dan beberapa jawaban Stack Overflow yang menyatakan tidak pernah (atau hampir tidak pernah) menyimpan daftar ID atau sejenisnya di suatu bidang - semua data harus bersifat relasional, dll.

Namun, masalah yang saya hadapi adalah saya mencoba membuat pemberi tugas. Orang akan membuat tugas, menetapkannya ke beberapa orang, dan itu akan disimpan ke database.

Tentu saja, jika saya menyimpan tugas-tugas ini secara individual di "Person", saya harus memiliki lusinan kolom "TaskID" dan mengelola mikronya karena mungkin ada 0 hingga 100 tugas yang ditugaskan untuk satu orang, katakanlah.

Kemudian lagi, jika saya menyimpan tugas dalam tabel "Tugas", saya harus memiliki lusinan kolom "PersonID" dan mengelola mikro - masalah yang sama seperti sebelumnya.

Untuk masalah seperti ini, apakah boleh menyimpan daftar ID dalam satu bentuk atau yang lain atau apakah saya hanya tidak memikirkan cara lain ini dapat dicapai tanpa melanggar prinsip?


22
Saya menyadari tagged "database relasional" ini jadi saya hanya akan meninggalkan sebagai komentar bukan jawaban, tetapi dalam jenis lain dari database itu tidak masuk akal untuk menyimpan daftar. Cassandra datang ke pikiran karena tidak ada yang bergabung.
Kapten Man

12
Kerja bagus dalam meneliti dan kemudian bertanya di sini! Memang, 'rekomendasi' untuk tidak pernah melanggar bentuk normal ke-1 sangat baik bagi Anda, karena Anda benar-benar harus datang dengan pendekatan relasional yang lain, yaitu hubungan "banyak-ke-banyak", di mana ada pola standar dalam database relasional yang harus digunakan.
JimmyB

6
"Apakah itu pernah baik-baik saja" ya .... apa pun yang mengikuti, jawabannya adalah ya. Selama Anda punya alasan yang valid. Selalu ada kasus penggunaan yang memaksa Anda untuk melanggar praktik terbaik karena masuk akal untuk melakukannya. (Namun, dalam kasus Anda, Anda tentu tidak seharusnya)
xyious

3
Saat ini saya menggunakan array ( bukan string yang dibatasi - a VARCHAR ARRAY) untuk menyimpan daftar tag. Itu mungkin bukan bagaimana mereka akhirnya akan disimpan kemudian, tetapi daftar bisa sangat berguna selama tahap prototyping, ketika Anda tidak memiliki hal lain untuk ditunjukkan dan tidak ingin membangun seluruh skema database sebelum Anda bisa lakukan hal lain.
Nic Hartley

3
@Ben " (meskipun mereka tidak akan diindeks) " - di Postgres, beberapa pertanyaan terhadap kolom JSON (dan mungkin XML, meskipun saya belum diperiksa) dapat diindeks.
Nic Hartley

Jawaban:


249

Kata kunci dan konsep kunci yang perlu Anda selidiki adalah normalisasi basis data .

Apa yang akan Anda lakukan, daripada menambahkan info tentang tugas ke orang atau tabel tugas, adalah Anda menambahkan tabel baru dengan info tugas itu, dengan hubungan yang relevan.

Contoh, Anda memiliki tabel berikut:

Orang:

+ −−−− + −−−−−−−−−−− +
| ID | Nama |
+ ==== + =========== +
| 1 | Alfred |
| 2 | Jebediah |
| 3 | Yakub |
| 4 | Yehezkiel |
+ −−−− + −−−−−−−−−−− +

Tugas:

+ −−−− + −−−−−−−−−−−−−−−−−−−− +
| ID | Nama |
+ ==== + ================== +
| 1 | Pakan Ayam |
| 2 | Bajak |
| 3 | Sapi Perah |
| 4 | Angkat gudang |
+ −−−− + −−−−−−−−−−−−−−−−−−−− +

Anda kemudian akan membuat tabel ketiga dengan Penugasan. Tabel ini akan memodelkan hubungan antara orang-orang dan tugas-tugas:

+ −−−− + −−−−−−−−−−− + −−−−−−−−− +
| ID | PersonId | TaskId |
+ ==== + =========== + ========= +
| 1 | 1 | 3 |
| 2 | 3 | 2 |
| 3 | 2 | 1 |
| 4 | 1 | 4 |
+ −−−− + −−−−−−−−−−− + −−−−−−−−− +

Kami kemudian akan memiliki batasan Kunci Asing, sehingga database akan menegakkan bahwa PersonId dan TaskIds harus menjadi ID yang valid untuk item asing tersebut. Untuk baris pertama, kita bisa melihat PersonId is 1, jadi Alfred , ditugaskan TaskId 3, Memerah susu sapi .

Apa yang seharusnya dapat Anda lihat di sini adalah bahwa Anda dapat memiliki sedikit atau banyak tugas per tugas atau per orang yang Anda inginkan. Dalam contoh ini, Yehezkiel tidak diberi tugas apa pun, dan Alfred ditugaskan 2. Jika Anda memiliki satu tugas dengan 100 orang, melakukan SELECT PersonId from Assignments WHERE TaskId=<whatever>;akan menghasilkan 100 baris, dengan berbagai Orang yang berbeda ditugaskan. Anda dapat WHEREdi PersonId untuk menemukan semua tugas yang diberikan kepada orang itu.

Jika Anda ingin mengembalikan kueri yang mengganti ID dengan Nama dan tugas, maka Anda bisa mempelajari cara GABUNG tabel.


86
Kata kunci yang ingin Anda cari untuk mempelajari lebih lanjut adalah "hubungan banyak-ke-banyak "
BlueRaja - Danny Pflughoeft

34
Untuk menguraikan sedikit tentang komentar Thierrys: Anda mungkin berpikir bahwa Anda tidak perlu menormalkan karena saya hanya perlu X dan sangat mudah untuk menyimpan daftar ID , tetapi untuk sistem apa pun yang mungkin diperpanjang kemudian Anda akan menyesal tidak menormalkannya. sebelumnya. Selalu menormalkan ; satu-satunya pertanyaan adalah apa bentuk normal
Jan Doggen

8
Setuju dengan @Jan - bertentangan dengan penilaian saya yang lebih baik, saya mengizinkan tim saya untuk mengambil jalan pintas desain beberapa waktu lalu, menyimpan JSON untuk sesuatu yang "tidak perlu diperpanjang". Itu berlangsung seperti FML enam bulan. Upgrade kami kemudian bertengkar buruk di tangannya untuk memigrasi JSON ke skema yang seharusnya kita mulai. Seharusnya aku tahu yang lebih baik.
Lightness Races in Orbit

13
@Dupuplikator: itu hanya representasi dari kolom utama, bilangan bulat utama auto-increment integer. Hal yang cukup khas.
whatsisname

8
@whatsisname Pada tabel Orang atau Tugas, saya setuju dengan Anda. Di atas meja jembatan di mana satu-satunya tujuan adalah untuk mewakili hubungan banyak-ke-banyak antara dua tabel lain yang sudah memiliki kunci pengganti? Saya tidak akan menambahkan satu tanpa alasan yang bagus. Itu hanya overhead karena tidak akan pernah digunakan dalam pertanyaan atau hubungan.
jpmc26

35

Anda mengajukan dua pertanyaan di sini.

Pertama, Anda bertanya apakah boleh menyimpan daftar yang diserialisasi dalam sebuah kolom. Ya, tidak apa-apa. Jika proyek Anda membutuhkannya. Contohnya mungkin bahan produk untuk halaman katalog, di mana Anda tidak ingin mencoba melacak setiap bahan secara individual.

Sayangnya pertanyaan kedua Anda menggambarkan skenario di mana Anda harus memilih pendekatan yang lebih relasional. Anda akan membutuhkan 3 tabel. Satu untuk orang-orang, satu untuk tugas-tugas, dan satu yang mempertahankan daftar tugas yang ditugaskan kepada orang-orang yang mana. Yang terakhir akan menjadi vertikal, satu baris per orang / kombinasi tugas, dengan kolom untuk kunci utama, id tugas, dan id orang.


9
Contoh bahan yang Anda referensi benar di permukaan; tapi itu akan menjadi teks biasa dalam kasus itu. Ini bukan daftar dalam arti pemrograman (kecuali jika Anda bermaksud bahwa string adalah daftar karakter yang jelas tidak Anda miliki). OP menggambarkan data mereka sebagai "daftar ID" (atau bahkan hanya "daftar [..]") menyiratkan bahwa mereka pada suatu titik menangani data ini sebagai objek individu.
Flater

10
@Flater: Tapi ini daftar. Anda harus dapat memformat ulang sebagai (berbagai) daftar HTML, daftar penurunan harga, daftar JSON, dll. Untuk memastikan item ditampilkan dengan benar di (beragam) halaman web, dokumen teks biasa, ponsel aplikasi ... dan Anda tidak dapat melakukannya dengan teks biasa.
Kevin

12
@Kevin Jika itu adalah tujuan Anda, maka itu jauh lebih mudah dan mudah dicapai dengan menyimpan bahan-bahan dalam sebuah tabel! Belum lagi jika, nanti, orang akan ... oh, saya tidak tahu, katakanlah, berharap untuk pengganti yang direkomendasikan , atau sesuatu yang konyol seperti mencari semua resep tanpa kacang, atau gluten, atau protein hewani ...
Dan Bron

10
@DanBron: YAGNI. Saat ini kami hanya menggunakan daftar karena itu membuat logika UI lebih mudah. Jika kita perlu atau akan memerlukan perilaku seperti daftar di lapisan logika bisnis, maka itu harus dinormalisasi menjadi tabel yang terpisah. Tabel dan gabungan tidak harus mahal, tetapi mereka tidak gratis, dan mereka membawa pertanyaan tentang urutan unsur ("Apakah kita peduli dengan urutan bahan?") Dan normalisasi lebih lanjut ("Apakah Anda akan mengubah '3 butir telur' menjadi ('telur', 3)? Bagaimana dengan 'Garam, secukupnya', apakah itu ('garam', NULL)? ").
Kevin

7
@Kevin: YAGNI sangat salah di sini. Anda sendiri berpendapat perlunya mengubah daftar dengan banyak cara (HTML, markdown, JSON) dan dengan demikian berpendapat bahwa Anda memerlukan elemen individual dari daftar . Kecuali jika penyimpanan data dan aplikasi "penanganan daftar" adalah dua aplikasi yang dikembangkan secara independen (dan perlu dicatat bahwa pisahkan lapisan aplikasi! = Aplikasi terpisah), struktur basis data harus selalu dibuat untuk menyimpan data dalam format yang membuatnya tersedia. - sambil menghindari logika parsing / konversi tambahan.
Flater

22

Apa yang Anda gambarkan dikenal sebagai hubungan "banyak ke banyak", dalam kasus Anda antara Persondan Task. Ini biasanya diimplementasikan menggunakan tabel ketiga, kadang-kadang disebut tabel "tautan" atau "referensi silang". Sebagai contoh:

create table person (
    person_id integer primary key,
    ...
);

create table task (
    task_id integer primary key,
    ...
);

create table person_task_xref (
    person_id integer not null,
    task_id integer not null,
    primary key (person_id, task_id),
    foreign key (person_id) references person (person_id),
    foreign key (task_id) references task (task_id)
);

2
Anda mungkin juga ingin menambahkan indeks dengan task_idterlebih dahulu, jika Anda mungkin melakukan kueri yang difilter berdasarkan tugas.
jpmc26

1
Juga dikenal sebagai meja jembatan. Juga, berharap saya bisa memberi Anda plus tambahan untuk tidak memiliki kolom identitas, meskipun saya akan merekomendasikan indeks pada setiap kolom.
jmoreno

13

... tidak pernah (atau hampir tidak pernah) menyimpan daftar ID atau sejenisnya di suatu bidang

Satu-satunya waktu Anda mungkin menyimpan lebih dari satu item data dalam satu bidang adalah ketika bidang itu hanya pernah digunakan sebagai entitas tunggal dan tidak pernah dianggap sebagai terdiri dari elemen-elemen yang lebih kecil. Contohnya mungkin gambar, disimpan dalam bidang BLOB. Itu terdiri dari banyak dan banyak elemen yang lebih kecil (byte) tetapi ini yang tidak berarti apa - apa untuk basis data dan hanya dapat digunakan bersama-sama (dan terlihat cantik bagi Pengguna Akhir).

Karena "daftar", menurut definisi, terdiri dari unsur-unsur yang lebih kecil (item), ini tidak terjadi di sini dan Anda harus menormalkan data.

... jika saya menyimpan tugas-tugas ini secara individual di "Person", saya harus memiliki lusinan kolom "TaskID" dummy ...

Tidak. Anda akan memiliki beberapa baris dalam Tabel Persimpangan (alias Entitas Lemah) antara Orang dan Tugas. Database sangat bagus dalam bekerja dengan banyak baris; mereka sebenarnya sangat sampah bekerja dengan banyak kolom [berulang].

Contoh jelas bagus diberikan oleh whatsisname.


4
Ketika menciptakan sistem kehidupan nyata "tidak pernah berkata tidak pernah" adalah aturan yang sangat baik untuk dijalani.
l0b0

1
Dalam banyak kasus, biaya per-elemen untuk mempertahankan atau mengambil daftar dalam bentuk yang dinormalisasi mungkin jauh melebihi biaya untuk menjaga barang-barang sebagai gumpalan, karena setiap item dari daftar harus memegang identitas item master yang dengannya dikaitkan dan lokasinya dalam daftar di samping data aktual. Bahkan dalam kasus-kasus di mana kode mungkin mendapat manfaat dari dapat memperbarui beberapa elemen daftar tanpa memperbarui seluruh daftar, mungkin lebih murah untuk menyimpan semuanya sebagai gumpalan dan menulis ulang semuanya ketika seseorang harus menulis ulang apa pun.
supercat

4

Mungkin sah di bidang pra-perhitungan tertentu.

Jika beberapa pertanyaan Anda mahal dan Anda memutuskan untuk pergi dengan bidang yang dihitung sebelumnya diperbarui secara otomatis menggunakan pemicu basis data, maka mungkin sah untuk menyimpan daftar di dalam kolom.

Misalnya, di UI Anda ingin menampilkan daftar ini menggunakan tampilan kisi, tempat setiap baris dapat membuka detail lengkap (dengan daftar lengkap) setelah mengklik dua kali:

REGISTERED USER LIST
+------------------+----------------------------------------------------+
|Name              |Top 3 most visited tags                             |
+==================+====================================================+
|Peter             |Design, Fitness, Gifts                              |
+------------------+----------------------------------------------------+
|Lucy              |Fashion, Gifts, Lifestyle                           |
+------------------+----------------------------------------------------+

Anda menjaga kolom kedua diperbarui oleh pemicu ketika klien mengunjungi artikel baru atau oleh tugas yang dijadwalkan.

Anda dapat membuat bidang seperti itu tersedia bahkan untuk pencarian (seperti teks biasa).

Untuk kasus seperti itu, menyimpan daftar adalah sah. Anda hanya perlu mempertimbangkan kemungkinan panjang bidang maksimum.


Juga, jika Anda menggunakan Microsoft Access, bidang multinilai yang ditawarkan adalah kasus penggunaan khusus lainnya. Mereka menangani daftar Anda di bidang secara otomatis.

Tetapi Anda selalu dapat kembali ke bentuk normalisasi standar yang ditunjukkan dalam jawaban lain.


Rangkuman: Bentuk normal dari basis data adalah model teoritis yang diperlukan untuk memahami aspek-aspek penting dari pemodelan data. Tetapi tentu saja normalisasi tidak memperhitungkan kinerja akun atau biaya lain untuk mengambil data. Ini di luar ruang lingkup model teoritis itu. Tetapi menyimpan daftar atau duplikat pra-dihitung (dan dikendalikan) sering diperlukan oleh implementasi praktis.

Sehubungan dengan hal di atas, dalam implementasi praktis, akankah kita lebih memilih kueri mengandalkan bentuk normal sempurna dan menjalankan 20 detik atau kueri yang setara mengandalkan nilai pra-perhitungan yang memakan waktu 0,08 detik? Tidak ada yang suka produk perangkat lunak mereka dituduh lamban.


1
Itu bisa sah bahkan tanpa hal-hal yang sudah diperhitungkan. Saya sudah melakukannya beberapa kali di mana data disimpan dengan benar tetapi untuk alasan kinerja itu berguna untuk memasukkan beberapa hasil yang di-cache dalam catatan utama.
Loren Pechtel

@LorenPechtel - Ya, terima kasih, dalam penggunaan istilah pra-kalkulasi saya juga menyertakan kasus nilai cache yang disimpan di tempat yang diperlukan. Dalam sistem dengan dependensi yang kompleks, mereka adalah cara untuk menjaga kinerja normal. Dan jika diprogram dengan pengetahuan yang memadai, nilai-nilai ini dapat diandalkan dan selalu sinkron. Saya hanya tidak ingin menambahkan kasus caching ke dalam jawaban untuk menjaga jawaban tetap sederhana dan aman. Lagipula itu diputuskan. :)
miroxlav

@LorenPechtel Sebenarnya, itu masih menjadi alasan buruk ... data cache harus disimpan di toko cache perantara, dan sementara cache masih valid, permintaan itu tidak boleh mengenai DB utama.
Tezra

1
@ Tezra Tidak, saya mengatakan bahwa kadang-kadang sepotong data dari tabel sekunder diperlukan cukup sering untuk masuk akal untuk meletakkan salinan dalam catatan utama. (Contoh yang telah saya lakukan - tabel karyawan menyertakan waktu terakhir masuk dan terakhir keluar. Mereka hanya digunakan untuk tujuan tampilan, setiap perhitungan aktual berasal dari tabel dengan catatan clock-in / clock-out.)
Loren Pechtel

0

Diberikan dua tabel; kami akan memanggil mereka Person dan Task, masing-masing dengan ID itu sendiri (PersonID, TaskID) ... ide dasarnya adalah membuat tabel ketiga untuk mengikat mereka bersama. Kami akan memanggil tabel ini PersonToTask. Minimal itu harus memiliki ID itu sendiri, serta dua lainnya. Jadi ketika datang untuk menugaskan seseorang untuk suatu tugas; Anda tidak perlu lagi MEMPERBARUI tabel Orang, Anda hanya perlu MEMASUKKAN baris baru ke dalam PersonToTaskTable. Dan pemeliharaan menjadi lebih mudah- perlu menghapus tugas hanya menjadi HAPUS berdasarkan TaskID, tidak ada lagi memperbarui tabel Person dan itu terkait parsing

CREATE TABLE dbo.PersonToTask (
    pttID INT IDENTITY(1,1) NOT NULL,
    PersonID INT NULL,
    TaskID   INT NULL
)

CREATE PROCEDURE dbo.Task_Assigned (@PersonID INT, @TaskID INT)
AS
BEGIN
    INSERT PersonToTask (PersonID, TaskID)
    VALUES (@PersonID, @TaskID)
END

CREATE PROCEDURE dbo.Task_Deleted (@TaskID INT)
AS
BEGIN
    DELETE PersonToTask  WHERE TaskID = @TaskID
    DELETE Task          WHERE TaskID = @TaskID
END

Bagaimana dengan laporan sederhana atau siapa yang ditugaskan untuk suatu tugas?

CREATE PROCEDURE dbo.Task_CurrentAssigned (@TaskID INT)
AS
BEGIN
    SELECT PersonName
    FROM   dbo.Person
    WHERE  PersonID IN (SELECT PersonID FROM dbo.PersonToTask WHERE TaskID = @TaskID)
END

Anda tentu saja bisa melakukan lebih banyak lagi; TimeReport dapat dilakukan jika Anda menambahkan bidang DateTime untuk TaskAssigned dan TaskCompleted. Semua terserah padamu


0

Ini dapat berfungsi jika mengatakan Anda memiliki kunci primer yang dapat dibaca manusia dan menginginkan daftar tugas # tanpa harus berurusan dengan sifat vertikal dari struktur tabel. yaitu lebih mudah untuk membaca tabel pertama.

------------------------  
Employee Name | Task 
Jack          |  1,2,5
Jill          |  4,6,7
------------------------

------------------------  
Employee Name | Task 
Jack          |  1
Jack          |  2
Jack          |  5
Jill          |  4
Jill          |  6
Jill          |  7
------------------------

Pertanyaannya kemudian adalah: apakah daftar tugas harus disimpan atau dihasilkan berdasarkan permintaan, yang sebagian besar akan tergantung pada persyaratan seperti: seberapa sering daftar tersebut diperlukan, seberapa akurat berapa banyak baris data yang ada, bagaimana data akan digunakan, dll. .. setelah itu menganalisis pertukaran untuk pengalaman pengguna dan persyaratan pertemuan harus dilakukan.

Misalnya membandingkan waktu yang diperlukan untuk mengingat 2 baris vs menjalankan kueri yang akan menghasilkan 2 baris. Jika perlu waktu lama dan pengguna tidak perlu daftar terbaru (* mengharapkan kurang dari 1 perubahan per hari) maka dapat disimpan.

Atau jika pengguna membutuhkan catatan historis tugas yang diberikan kepada mereka, masuk akal juga jika daftar itu disimpan. Jadi itu sangat tergantung pada apa yang Anda lakukan, tidak pernah mengatakan tidak pernah.


Seperti yang Anda katakan, itu semua tergantung pada bagaimana data akan diambil. Jika Anda / hanya / pernah menanyakan tabel ini dengan Nama Pengguna, maka bidang "daftar" sudah cukup memadai. Namun, bagaimana Anda bisa meminta tabel seperti itu untuk mencari tahu siapa yang mengerjakan Tugas # 1234567 dan tetap membuatnya berkinerja? Hampir setiap jenis fungsi string "temukan-X-di mana saja di lapangan" akan menyebabkan kueri tersebut ke / Pindai Tabel /, memperlambat hal-hal menjadi perayapan. Dengan normalisasi, data yang diindeks dengan benar, itu tidak terjadi.
Phill W.

0

Anda mengambil apa yang seharusnya menjadi meja lain, mengubahnya 90 derajat dan memilihnya ke meja lain.

Ini seperti memiliki tabel pesanan di mana Anda memiliki itemProdcode1, itemQuantity1, itemPrice1 ... itemProdcode37, itemQuantity37, itemPrice37. Selain canggung untuk menangani secara pemrograman, Anda dapat menjamin bahwa besok seseorang ingin memesan 38 barang.

Saya hanya akan melakukannya dengan cara Anda jika 'daftar' tidak benar-benar daftar, yaitu di mana ia berdiri secara keseluruhan dan setiap item baris individu tidak merujuk pada entitas yang jelas dan independen. Dalam hal itu hanya memasukkan semuanya dalam beberapa tipe data yang cukup besar.

Jadi pesanan adalah daftar, Bill Of Material adalah daftar (atau daftar daftar, yang bahkan akan lebih dari mimpi buruk untuk menerapkan "ke samping"). Tetapi catatan / komentar dan puisi tidak.


0

Jika "tidak ok" maka cukup buruk bahwa setiap situs Wordpress pernah memiliki daftar di wp_usermeta dengan wp_capabilities dalam satu baris, daftar dismissed_wp_pointers dalam satu baris, dan yang lainnya ...

Bahkan dalam kasus seperti ini mungkin lebih baik untuk kecepatan karena Anda hampir selalu menginginkan daftarnya . Tetapi Wordpress tidak dikenal sebagai contoh sempurna dari praktik terbaik.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.