Cara menyimpan daftar di kolom tabel database


115

Jadi, sesuai jawaban Mehrdad untuk pertanyaan terkait , saya mengerti bahwa kolom tabel database yang "tepat" tidak menyimpan daftar. Sebaliknya, Anda harus membuat tabel lain yang secara efektif menampung elemen dari daftar tersebut dan kemudian menautkannya secara langsung atau melalui tabel persimpangan. Namun, jenis daftar yang ingin saya buat akan terdiri dari item unik (tidak seperti buah pertanyaan yang ditautkancontoh). Selanjutnya, item dalam daftar saya diurutkan secara eksplisit - yang berarti jika saya menyimpan elemen di tabel lain, saya harus mengurutkannya setiap kali saya mengaksesnya. Akhirnya, daftar ini pada dasarnya bersifat atomik karena kapan pun saya ingin mengakses daftar, saya ingin mengakses seluruh daftar daripada hanya sebagian - jadi tampaknya konyol untuk mengeluarkan kueri database untuk mengumpulkan potongan Daftar.

Solusi AKX (ditautkan di atas) adalah menyusun daftar dan menyimpannya dalam kolom biner. Tetapi ini juga tampaknya tidak nyaman karena itu berarti saya harus khawatir tentang serialisasi dan deserialisasi.

Apakah ada solusi yang lebih baik? Jika ada yang tidak ada solusi yang lebih baik, lalu mengapa? Tampaknya masalah ini harus muncul dari waktu ke waktu.

... hanya sedikit info lagi untuk memberi tahu Anda dari mana saya berasal. Begitu saya baru mulai memahami SQL dan database secara umum, saya beralih ke LINQ ke SQL, dan sekarang saya sedikit manja karena saya berharap dapat menangani model objek pemrograman saya tanpa harus memikirkan bagaimana objek dipertanyakan atau disimpan dalam database.

Terima kasih semuanya!

John

PEMBARUAN: Jadi dalam kebingungan pertama jawaban yang saya dapatkan, saya melihat "Anda bisa pergi ke rute CSV / XML ... tapi JANGAN!". Jadi sekarang saya sedang mencari penjelasan mengapa. Arahkan saya ke beberapa referensi yang bagus.

Juga, untuk memberi Anda gambaran yang lebih baik tentang apa yang saya lakukan: Dalam database saya, saya memiliki tabel Fungsi yang akan memiliki daftar pasangan (x, y). (Tabel juga akan memiliki informasi lain yang bukan merupakan konsekuensi untuk diskusi kita.) Saya tidak akan pernah perlu melihat bagian dari daftar pasangan (x, y). Sebaliknya, saya akan mengambil semuanya dan memplotnya di layar. Saya akan mengizinkan pengguna untuk menyeret node di sekitar untuk mengubah nilai sesekali atau menambahkan lebih banyak nilai ke plot.

Jawaban:


183

Tidak, tidak ada cara yang "lebih baik" untuk menyimpan urutan item dalam satu kolom. Database relasional dirancang khusus untuk menyimpan satu nilai per kombinasi baris / kolom. Untuk menyimpan lebih dari satu nilai, Anda harus membuat daftar Anda menjadi satu nilai untuk penyimpanan, kemudian deserialisasinya setelah pengambilan. Tidak ada cara lain untuk melakukan apa yang Anda bicarakan (karena yang Anda bicarakan adalah ide buruk yang seharusnya, secara umum, tidak pernah dilakukan ).

Saya mengerti bahwa Anda pikir itu konyol untuk membuat tabel lain untuk menyimpan daftar itu, tetapi inilah yang sebenarnya dilakukan oleh database relasional. Anda sedang berjuang keras dan melanggar salah satu prinsip paling dasar dari desain database relasional tanpa alasan yang jelas. Karena Anda menyatakan bahwa Anda baru belajar SQL, saya sangat menyarankan Anda untuk menghindari ide ini dan tetap berpegang pada praktik yang direkomendasikan kepada Anda oleh pengembang SQL yang lebih berpengalaman.

Prinsip yang Anda langgar disebut bentuk normal pertama , yang merupakan langkah pertama dalam normalisasi database.

Pada risiko terlalu menyederhanakan hal-hal, normalisasi database adalah proses mendefinisikan database Anda berdasarkan apa data yang , sehingga Anda dapat menulis masuk akal, query konsisten terhadap hal itu dan mampu mempertahankannya dengan mudah. Normalisasi dirancang untuk membatasi inkonsistensi logis dan kerusakan dalam data Anda, dan ada banyak tingkatan untuk itu. Artikel Wikipedia tentang normalisasi database sebenarnya cukup bagus.

Pada dasarnya, aturan pertama (atau bentuk) normalisasi menyatakan bahwa tabel Anda harus mewakili suatu relasi. Artinya:

  • Anda harus dapat membedakan satu baris dari baris lainnya (dengan kata lain, tabel Anda harus memiliki sesuatu yang dapat berfungsi sebagai kunci utama. Ini juga berarti bahwa tidak ada baris yang harus diduplikasi.
  • Setiap pengurutan data harus ditentukan oleh data, bukan oleh pengurutan fisik baris (SQL didasarkan pada gagasan kumpulan, yang berarti bahwa satu - satunya pengurutan yang harus Anda andalkan adalah pengurutan yang Anda tentukan secara eksplisit dalam kueri Anda)
  • Setiap persimpangan baris / kolom harus berisi satu dan hanya satu nilai

Poin terakhir jelas merupakan poin yang menonjol di sini. SQL dirancang untuk menyimpan set Anda, bukan untuk memberi Anda "keranjang" bagi Anda untuk menyimpan set sendiri. Ya, itu mungkin dilakukan. Tidak, dunia tidak akan berakhir. Namun, Anda telah melumpuhkan diri Anda sendiri dalam memahami SQL dan praktik terbaik yang menyertainya dengan langsung beralih menggunakan ORM. LINQ ke SQL luar biasa, seperti halnya kalkulator grafik. Dalam nada yang sama, bagaimanapun, mereka tidak boleh digunakan sebagai pengganti untuk mengetahui bagaimana proses yang mereka terapkan sebenarnya bekerja.

Daftar Anda mungkin sepenuhnya "lengkap" sekarang, dan itu mungkin tidak berubah untuk proyek ini. Tetapi Anda akan, bagaimanapun, terbiasa melakukan hal serupa di proyek lain, dan pada akhirnya Anda (kemungkinan besar dengan cepat) akan mengalami skenario di mana Anda sekarang menyesuaikan daftar-dalam-kolom-cepat-n-mudah Anda. pendekatan yang sama sekali tidak pantas. Tidak banyak pekerjaan tambahan dalam membuat tabel yang benar untuk apa yang Anda coba simpan, dan Anda tidak akan dicemooh oleh pengembang SQL lain ketika mereka melihat desain database Anda. Selain itu, LINQ ke SQL akan melihat relasi Anda dan memberi Anda antarmuka berorientasi objek yang tepat ke daftar Anda secara otomatis . Mengapa Anda melepaskan kenyamanan yang ditawarkan oleh ORM kepada Anda sehingga Anda dapat melakukan peretasan database yang tidak standar dan keliru?


17
Jadi Anda sangat yakin bahwa menyimpan daftar di kolom adalah ide yang buruk, tetapi Anda gagal menyebutkan alasannya. Karena saya baru memulai dengan SQL, sedikit "mengapa" akan sangat membantu. Misalnya, Anda mengatakan bahwa saya "berjuang keras dan melanggar salah satu prinsip paling dasar dari desain database relasional tanpa alasan yang baik" ... jadi apa prinsipnya? Mengapa alasan saya mengutip "tidak baik"? (Khususnya, sifat diurutkan dan atom dari daftar saya)
JnBrymn

6
Pada dasarnya, ini bergantung pada pengalaman bertahun-tahun yang diringkas menjadi praktik terbaik. Prinsip dasar yang dimaksud dikenal sebagai Bentuk Normal ke-1 .
Toby

1
Terima kasih Adam. Sangat informatif. Poin bagus dengan pertanyaan terakhir Anda.
JnBrymn

8
“[…] Dan Anda tidak akan dicemooh oleh pengembang SQL lain ketika mereka melihat desain database Anda.” Ada alasan yang sangat bagus untuk menghormati Bentuk Normal Pertama (dan jawaban Anda menyebutkannya), tetapi tekanan teman / "begitulah cara dilakukan di sekitar sini" tidak termasuk di antaranya.
Lynn

5
Kami sudah menyimpan banyak daftar di kolom database setiap hari. Mereka disebut "char" dan "varchar". Tentu saja di Postgres, mereka juga disebut teks. Apa yang sebenarnya dikatakan 1NF adalah bahwa Anda seharusnya tidak ingin membagi informasi dalam bidang apa pun menjadi bidang yang lebih kecil, dan jika Anda melakukannya, Anda tidak melakukan kesalahan. Jadi Anda tidak menyimpan nama, Anda menyimpan nama pribadi, nama tengah, dan nama keluarga (tergantung pada pelokalan), dan menjahitnya menjadi satu. Jika tidak, kami tidak akan menyimpan string teks sama sekali. Di sisi lain, yang dia inginkan hanyalah seutas tali. Dan ada cara untuk melakukannya.
Haakon Løtveit

15

Anda bisa melupakan semua SQL dan pergi dengan pendekatan "NoSQL". RavenDB , MongoDB , dan CouchDB muncul sebagai solusi yang mungkin. Dengan pendekatan NoSQL, Anda tidak menggunakan model relasional..Anda bahkan tidak dibatasi oleh skema.


11

Apa yang saya lihat banyak orang lakukan adalah ini (ini mungkin bukan pendekatan terbaik, perbaiki saya jika saya salah):

Tabel yang saya gunakan dalam contoh diberikan di bawah ini (tabel termasuk nama panggilan yang Anda berikan kepada pacar tertentu Anda. Setiap pacar memiliki id unik):

nicknames(id,seq_no,names)

Misalkan, Anda ingin menyimpan banyak nama panggilan di bawah sebuah id. Inilah mengapa kami menyertakan seq_nobidang.

Sekarang, isi nilai-nilai ini ke tabel Anda:

(1,1,'sweetheart'), (1,2,'pumpkin'), (2,1,'cutie'), (2,2,'cherry pie')

Jika Anda ingin menemukan semua nama yang telah Anda berikan kepada pacar Anda id 1 maka Anda dapat menggunakan:

select names from nicknames where id = 1;

5

Jawaban sederhana: Jika, dan hanya jika, Anda yakin bahwa daftar tersebut akan selalu digunakan sebagai daftar, maka gabungkan daftar itu bersama-sama di pihak Anda dengan karakter (seperti '\ 0') yang tidak akan digunakan di teks, dan simpan itu. Kemudian saat Anda mengambilnya, Anda dapat membaginya dengan '\ 0'. Tentu saja ada cara lain untuk melakukan hal ini, tetapi itu tergantung pada vendor database spesifik Anda.

Sebagai contoh, Anda dapat menyimpan JSON dalam database Postgres. Jika daftar Anda adalah teks, dan Anda hanya ingin daftar itu tanpa kerumitan lebih lanjut, itu kompromi yang masuk akal.

Orang lain telah mengajukan saran untuk membuat serialisasi, tetapi saya tidak benar-benar berpikir bahwa serialisasi adalah ide yang baik: Bagian dari hal yang rapi tentang database adalah bahwa beberapa program yang ditulis dalam bahasa yang berbeda dapat saling berkomunikasi. Dan program yang diserialkan menggunakan format Java tidak akan bekerja dengan baik jika program Lisp ingin memuatnya.

Jika Anda menginginkan cara yang baik untuk melakukan hal semacam ini biasanya ada tipe array-atau-serupa yang tersedia. Postgres misalnya, menawarkan array sebagai tipe, dan memungkinkan Anda menyimpan array teks, jika itu yang Anda inginkan , dan ada trik serupa untuk MySql dan MS SQL menggunakan JSON, dan DB2 IBM menawarkan tipe array juga (dalam memiliki dokumentasi yang bermanfaat ). Ini tidak akan umum jika tidak diperlukan.

Apa yang Anda kehilangan dengan menempuh jalan itu adalah gagasan tentang daftar sebagai sekumpulan hal secara berurutan. Setidaknya secara nominal, database memperlakukan bidang sebagai nilai tunggal. Tetapi jika hanya itu yang Anda inginkan, maka Anda harus melakukannya. Ini adalah penilaian nilai yang harus Anda buat sendiri.


3

Selain apa yang orang lain katakan, saya sarankan Anda menganalisis pendekatan Anda dalam jangka yang lebih panjang daripada sekarang. Saat ini kasus item itu unik. Saat ini kasus yang menggunakan item akan membutuhkan daftar baru. Hampir disyaratkan bahwa daftar saat ini pendek. Meskipun saya tidak memiliki domain spesifik, tidaklah sulit untuk berpikir bahwa persyaratan tersebut dapat berubah. Jika Anda membuat daftar serial Anda, Anda memanggang dalam ketidakfleksibelan yang tidak diperlukan dalam desain yang lebih dinormalisasi. Btw, itu tidak berarti hubungan Banyak: Banyak penuh. Anda bisa saja memiliki tabel anak tunggal dengan kunci asing untuk induk dan kolom karakter untuk item tersebut.

Jika Anda masih ingin melakukan serialisasi daftar ini, Anda mungkin mempertimbangkan untuk menyimpan daftar dalam XML. Beberapa database seperti SQL Server bahkan memiliki tipe data XML. Satu-satunya alasan saya menyarankan XML adalah karena hampir menurut definisi, daftar ini harus pendek. Jika daftarnya panjang, maka membuat serialisasi secara umum adalah pendekatan yang buruk. Jika Anda mengikuti rute CSV, Anda harus memperhitungkan nilai yang berisi pemisah yang berarti Anda harus menggunakan pengenal yang dikutip. Menganggap bahwa daftarnya pendek, mungkin tidak akan membuat banyak perbedaan apakah Anda menggunakan CSV atau XML.


1 untuk mengantisipasi perubahan di masa mendatang - selalu rancang model data Anda agar dapat diperluas.
coolgeek

2

Saya hanya akan menyimpannya sebagai CSV, jika nilainya sederhana maka itu harus menjadi semua yang Anda butuhkan (XML sangat bertele-tele dan serialisasi ke / dari itu mungkin akan berlebihan tetapi itu akan menjadi pilihan juga).

Berikut adalah jawaban yang bagus tentang cara mengeluarkan CSV dengan LINQ.


Saya berpikir tentang itu. Itu masih berarti bahwa saya harus membuat serial dan deserialisasi ... tapi saya rasa itu bisa dilakukan. Saya berharap ada cara yang dimaafkan untuk melakukan apa yang saya inginkan, tetapi saya rasa tidak ada.
JnBrymn

capnproto.org adalah cara untuk tidak perlu membuat serial dan deserialisasi, sama cepatnya (dibandingkan dengan csv atau xml) jika capnproto tidak didukung dalam bahasa pilihan Anda msgpack.org/index.html
VoronoiPotato

2

Jika Anda perlu membuat kueri pada daftar, simpan dalam tabel.

Jika Anda selalu menginginkan daftar tersebut, Anda dapat menyimpannya sebagai daftar yang dipisahkan dalam kolom. Bahkan dalam kasus ini, kecuali Anda memiliki alasan yang SANGAT spesifik untuk tidak melakukannya, simpanlah dalam tabel pencarian.


1

Hanya satu opsi yang tidak disebutkan dalam jawaban. Anda dapat menormalisasi desain DB Anda. Jadi, Anda membutuhkan dua tabel. Satu tabel berisi daftar yang benar, satu item per baris, tabel lain berisi seluruh daftar dalam satu kolom (dipisahkan koma, misalnya).

Ini dia desain DB 'tradisional':

List(ListID, ListName) 
Item(ItemID,ItemName) 
List_Item(ListID, ItemID, SortOrder)

Ini dia tabel yang dinormalisasi:

Lists(ListID, ListContent)

Idenya di sini - Anda memelihara tabel Daftar menggunakan pemicu atau kode aplikasi. Setiap kali Anda memodifikasi konten List_Item, baris yang sesuai dalam Daftar akan diperbarui secara otomatis. Jika Anda kebanyakan membaca daftar, itu bisa berfungsi dengan baik. Pro - Anda dapat membaca daftar dalam satu pernyataan. Kontra - pembaruan membutuhkan lebih banyak waktu dan upaya.


0

Jika Anda benar-benar ingin menyimpannya dalam kolom dan membuatnya dapat dikueri, banyak database yang mendukung XML sekarang. Jika tidak membuat kueri, Anda dapat menyimpannya sebagai nilai yang dipisahkan koma dan menguraikannya dengan fungsi saat Anda membutuhkannya untuk dipisahkan. Saya setuju dengan orang lain meskipun jika Anda ingin menggunakan database relasional, sebagian besar normalisasi adalah pemisahan data seperti itu. Saya tidak mengatakan bahwa semua data cocok dengan database relasional. Anda selalu dapat melihat jenis database lain jika banyak data Anda tidak sesuai dengan model.


0

Saya rasa dalam kasus tertentu, Anda dapat membuat "daftar" FAKE item di database, misalnya, barang dagangan memiliki beberapa gambar untuk menunjukkan detailnya, Anda dapat menggabungkan semua ID gambar yang dipisahkan dengan koma dan menyimpan string menjadi DB, maka Anda hanya perlu mengurai string saat Anda membutuhkannya. Saya sedang mengerjakan situs web sekarang dan saya berencana untuk menggunakan cara ini.


0

Saya sangat enggan untuk memilih jalan yang akhirnya saya putuskan untuk diambil karena banyak jawaban. Sementara mereka menambahkan lebih banyak pemahaman tentang apa itu SQL dan prinsip-prinsipnya, saya memutuskan untuk menjadi penjahat. Saya juga ragu untuk memposting temuan saya karena bagi beberapa orang lebih penting untuk melampiaskan rasa frustrasi kepada seseorang yang melanggar aturan daripada memahami bahwa hanya ada sedikit kebenaran universal.

Saya telah mengujinya secara ekstensif dan, dalam kasus khusus saya, itu jauh lebih efisien daripada menggunakan tipe array (dengan murah hati ditawarkan oleh PostgreSQL) atau menanyakan tabel lain.

Inilah jawaban saya: Saya telah berhasil menerapkan daftar ke dalam satu bidang di PostgreSQL, dengan menggunakan panjang tetap setiap item dari daftar. Katakanlah setiap item adalah warna sebagai nilai hex ARGB, itu berarti 8 karakter. Jadi, Anda dapat membuat larik berisi maksimal 10 item dengan mengalikan panjang setiap item:

ALTER product ADD color varchar(80)

Jika panjang item daftar Anda berbeda, Anda selalu dapat mengisi padding dengan \ 0

NB: Jelas ini belum tentu pendekatan terbaik untuk nomor hex karena daftar bilangan bulat akan mengkonsumsi lebih sedikit penyimpanan tetapi ini hanya untuk tujuan menggambarkan gagasan array ini dengan memanfaatkan panjang tetap yang dialokasikan untuk setiap item.

Alasan mengapa: 1 / Sangat nyaman: ambil item i di substring i * n, (i +1) * n. 2 / Tidak ada overhead kueri tabel silang. 3 / Lebih efisien dan hemat biaya di sisi server. Daftar ini seperti gumpalan mini yang harus dipisahkan oleh klien.

Sementara saya menghormati orang-orang yang mengikuti aturan, banyak penjelasan yang sangat teoritis dan sering gagal untuk mengakui bahwa, dalam beberapa kasus tertentu, terutama ketika bertujuan untuk mendapatkan biaya yang optimal dengan solusi latensi rendah, beberapa perubahan kecil lebih dari diterima.

"Tuhan melarang bahwa itu melanggar beberapa prinsip suci suci SQL": Mengadopsi pendekatan yang lebih berpikiran terbuka dan pragmatis sebelum melafalkan aturan selalu menjadi jalan yang harus ditempuh. Jika tidak, Anda mungkin akan berakhir seperti seorang fanatik yang jujur ​​membacakan Tiga Hukum Robotika sebelum dilenyapkan oleh Skynet

Saya tidak berpura-pura bahwa solusi ini adalah terobosan, juga tidak ideal dalam hal keterbacaan dan fleksibilitas database, tetapi pasti dapat memberi Anda keunggulan dalam hal latensi.


Tetapi ini adalah kasus yang sangat spesifik: sejumlah item dengan panjang tetap. Bahkan kemudian, itu membuat pencarian sederhana seperti "semua produk yang memiliki setidaknya warna x" lebih sulit daripada SQL standar.
Gert Arnold

Seperti yang saya nyatakan berkali-kali, saya tidak menggunakannya untuk warna, bidang tempat saya menggunakannya tidak boleh diindeks atau digunakan sebagai kondisi, namun ini adalah yang kritis
Antonin GAVREL

Saya tahu, saya mencoba menunjukkan bahwa ini sangat spesifik. Jika ada persyaratan tambahan kecil yang menyelinap di dalamnya dengan cepat menjadi lebih canggung daripada solusi standar. Sebagian besar orang yang tergoda untuk menyimpan daftar dalam satu bidang db mungkin lebih baik tidak melakukannya.
Gert Arnold

0

Banyak database SQL memungkinkan tabel berisi subtabel sebagai komponen. Metode biasa adalah mengizinkan domain salah satu kolom menjadi tabel. Ini sebagai tambahan untuk menggunakan beberapa konvensi seperti CSV untuk menyandikan substruktur dengan cara yang tidak diketahui DBMS.

Ketika Ed Codd mengembangkan model relasional pada 1969-1970, dia secara spesifik mendefinisikan bentuk normal yang akan melarang jenis tabel bersarang ini. Bentuk normal kemudian disebut Bentuk Normal Pertama. Dia kemudian melanjutkan untuk menunjukkan bahwa untuk setiap database, ada database dalam bentuk normal pertama yang mengungkapkan informasi yang sama.

Mengapa repot-repot dengan ini? Nah, database dalam bentuk normal pertama mengizinkan akses kunci ke semua data. Jika Anda memberikan nama tabel, nilai kunci ke dalam tabel itu, dan nama kolom, database akan berisi paling banyak satu sel yang berisi satu item data.

Jika Anda memperbolehkan sel untuk memuat daftar atau tabel atau koleksi lainnya, sekarang Anda tidak dapat memberikan akses kunci ke sub item, tanpa mengerjakan ulang ide kunci sepenuhnya.

Akses kunci ke semua data sangat penting untuk model relasional. Tanpa konsep ini, model tidak relasional. Mengenai mengapa model relasional adalah ide yang bagus, dan apa yang mungkin menjadi batasan dari ide yang bagus itu, Anda harus melihat pengalaman terakumulasi selama 50 tahun dengan model relasional.


-1

Anda dapat menyimpannya sebagai teks yang terlihat seperti daftar dan membuat fungsi yang dapat mengembalikan datanya sebagai daftar yang sebenarnya. contoh:

database:

 _____________________
|  word  | letters    |
|   me   | '[m, e]'   |
|  you   |'[y, o, u]' |  note that the letters column is of type 'TEXT'
|  for   |'[f, o, r]' |
|___in___|_'[i, n]'___|

Dan fungsi penyusun daftar (ditulis dengan python, tetapi seharusnya dapat dengan mudah diterjemahkan ke sebagian besar bahasa pemrograman lain). TEXT mewakili teks yang dimuat dari tabel sql. mengembalikan daftar string dari daftar yang berisi string. jika Anda ingin mengembalikan int sebagai ganti string, buat mode sama dengan 'int'. Begitu juga dengan 'string', 'bool', atau 'float'.

def string_to_list(string, mode):
    items = []
    item = ""
    itemExpected = True
    for char in string[1:]:
        if itemExpected and char not in [']', ',', '[']:
            item += char
        elif char in [',', '[', ']']:
            itemExpected = True
            items.append(item)
            item = ""
    newItems = []
    if mode == "int":
        for i in items:
            newItems.append(int(i))

    elif mode == "float":
        for i in items:
            newItems.append(float(i))

    elif mode == "boolean":
        for i in items:
            if i in ["true", "True"]:
                newItems.append(True)
            elif i in ["false", "False"]:
                newItems.append(False)
            else:
                newItems.append(None)
    elif mode == "string":
        return items
    else:
        raise Exception("the 'mode'/second parameter of string_to_list() must be one of: 'int', 'string', 'bool', or 'float'")
    return newItems

Juga di sini adalah fungsi daftar-ke-string jika Anda membutuhkannya.

def list_to_string(lst):
    string = "["
    for i in lst:
        string += str(i) + ","
    if string[-1] == ',':
        string = string[:-1] + "]"
    else:
        string += "]"
    return string
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.