Desain Basis Data untuk Penandaan


171

Bagaimana Anda merancang basis data untuk mendukung fitur penandaan berikut:

  • item dapat memiliki sejumlah besar tag
  • mencari semua item yang ditandai dengan serangkaian tag yang diberikan harus cepat (item harus memiliki SEMUA tag, jadi ini adalah pencarian-AND, bukan pencarian-OR)
  • membuat / menulis item mungkin lebih lambat untuk mengaktifkan pencarian cepat / membaca

Idealnya, pencarian semua item yang ditandai dengan (setidaknya) satu set tag yang diberikan harus dilakukan menggunakan pernyataan SQL tunggal. Karena jumlah tag yang dicari serta jumlah tag pada item apa pun tidak diketahui dan mungkin tinggi, menggunakan GABUNGAN tidak praktis.

Ada ide?


Terima kasih atas semua jawaban sejauh ini.

Namun, jika saya tidak salah, jawaban yang diberikan menunjukkan bagaimana melakukan pencarian ATAU pada tag. (Pilih semua item yang memiliki satu atau lebih dari n tag). Saya mencari AND-search yang efisien. (Pilih semua item yang memiliki SEMUA n tag - dan mungkin lebih.)

Jawaban:


22

Tentang ANDing: Sepertinya Anda mencari operasi "divisi relasional". Artikel ini membahas pembagian relasional dalam cara yang ringkas dan mudah dipahami.

Tentang kinerja: Pendekatan berbasis bitmap secara intuitif sepertinya cocok dengan situasi. Namun, saya tidak yakin itu ide yang baik untuk menerapkan pengindeksan bitmap "secara manual", seperti yang digiguru sarankan: Kedengarannya seperti situasi yang rumit setiap kali tag baru ditambahkan (?) Tetapi beberapa DBMS (termasuk Oracle) menawarkan indeks bitmap yang entah bagaimana mungkin berguna, karena sistem pengindeksan bawaan menghilangkan kompleksitas potensial pemeliharaan indeks; selain itu, DBMS yang menawarkan indeks bitmap harus dapat mempertimbangkannya secara tepat ketika melakukan rencana kueri.


4
Saya harus mengatakan bahwa jawabannya agak pendek, karena menggunakan jenis bidang bit dari database membatasi Anda ke jumlah bit tertentu. Ini tidak berarti setiap item dibatasi pada sejumlah tag tertentu, tetapi hanya ada beberapa tag unik di seluruh sistem (biasanya hingga 32 atau 64).
Mark Renouf

1
Dengan asumsi implementasi 3nf (Pertanyaan, Tag, Question_has_Tag), dan indeks bitmap pada Tag_id di Question_has_Tag, indeks bitmap harus dibangun kembali setiap kali sebuah pertanyaan memiliki tag yang ditambahkan atau dihapus. Sebuah query seperti select * from question q inner join question_has_tag qt where tag_id in (select tag_id from tags where (what we want) minus select tag_id from tags where (what we don't)harus baik-baik saja dan diskalakan dengan asumsi indeks b-tree yang tepat ada di tabel tengah
Adam Musch

Tautan "Artikel ini" sudah mati. Saya ingin membaca bahwa :(
mpen

3
Mark: Yang ini terlihat bagus: simple-talk.com/sql/t-sql-programming/… Ini mungkin versi yang diterbitkan ulang dari yang saya sebutkan.
Troels Arvin

URL artikel tidak berlaku lagi
Sebastien H.

77

Inilah artikel yang bagus tentang pemberian tag pada skema Database:

http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/

bersama dengan tes kinerja:

http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/

Perhatikan bahwa kesimpulan di sana sangat spesifik untuk MySQL, yang (setidaknya pada 2005 saat ditulis) memiliki karakteristik pengindeksan teks lengkap yang sangat buruk.


1
Saya juga ingin memiliki wawasan teknis yang lebih rinci tentang bagaimana Anda menerapkan sistem penandaan dengan SO? Saya pikir pada podcast Anda mengatakan Anda menyimpan semua tag di kolom dengan setiap pertanyaan dan kemudian membuat serial / de-serialisasi mereka dengan cepat? Saya ingin tahu lebih banyak tentangnya dan mungkin melihat beberapa cuplikan kode. Saya telah mencari-cari dan menemukan rincian, apakah ada tautan di mana Anda telah melakukan ini sebelum saya mengajukan pertanyaan pada META?
Marston A.

5
Pertanyaan tentang Meta ini memiliki beberapa info tentang skema SO: meta.stackexchange.com/questions/1863/so-database-schema
Barrett

Tautan asli sudah mati, tapi saya rasa saya menemukan lokasi baru mereka. Anda mungkin ingin memverifikasi bahwa ini adalah artikel yang Anda maksud.
Brad Larson

12
Meskipun ditulis oleh @Jeff, ini pada dasarnya masih merupakan satu-satunya jawaban tautan.
curiousdannii

13

Saya tidak melihat masalah dengan solusi langsung: Tabel untuk item, tabel untuk tag, tabel silang untuk "pemberian tag"

Indeks pada tabel silang harus cukup optimal. Memilih item yang sesuai adalah

SELECT * FROM items WHERE id IN  
    (SELECT DISTINCT item_id FROM item_tag WHERE  
    tag_id = tag1 OR tag_id = tag2 OR ...)  

DAN penandaan akan menjadi

SELECT * FROM items WHERE  
    EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag1)  
    AND EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag2)  
    AND ...

yang memang diakui, tidak begitu efisien untuk sejumlah besar tag pembanding. Jika Anda ingin mempertahankan jumlah tag dalam memori, Anda dapat membuat kueri untuk memulai dengan tag yang tidak sering, jadi DAN urutan akan dievaluasi lebih cepat. Bergantung pada jumlah tag yang diharapkan untuk dicocokkan dan harapan untuk mencocokkan satu pun dari mereka, ini bisa menjadi solusi yang baik, jika Anda mencocokkan 20 tag, dan berharap bahwa beberapa item acak akan cocok dengan 15 dari mereka, maka ini akan tetap berat pada database.


13

Saya hanya ingin menggarisbawahi bahwa artikel yang @Jeff Atwood ditautkan ( http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/ ) sangat teliti (Membahas manfaat dari 3 skema berbeda pendekatan) dan memiliki solusi yang baik untuk pertanyaan AND yang biasanya akan berkinerja lebih baik daripada apa yang telah disebutkan di sini sejauh ini (yaitu tidak menggunakan subquery yang berkorelasi untuk setiap istilah). Juga banyak hal bagus di komentar.

ps - Pendekatan yang dibicarakan semua orang di sini disebut sebagai solusi "Toxi" dalam artikel.


3
Saya ingat pernah membaca artikel yang bagus itu, tetapi sayangnya tautannya sudah mati sekarang. :( Adakah yang tahu tentang cermin itu?
localhost

5
tautannya sudah mati: <
Aaron

6

Anda mungkin ingin bereksperimen dengan solusi tidak-benar-database seperti implementasi Java Content Repository (misalnya Apache Jackrabbit ) dan menggunakan mesin pencari yang dibangun di atasnya seperti Apache Lucene .

Solusi ini dengan mekanisme caching yang tepat mungkin akan menghasilkan kinerja yang lebih baik daripada solusi buatan sendiri.

Namun, saya tidak benar-benar berpikir bahwa dalam aplikasi kecil atau menengah Anda akan memerlukan implementasi yang lebih canggih daripada database normal yang disebutkan dalam posting sebelumnya.

EDIT: dengan klarifikasi Anda, tampaknya lebih menarik untuk menggunakan solusi seperti JCR dengan mesin pencari. Itu akan sangat menyederhanakan program Anda dalam jangka panjang.


5

Metode termudah adalah membuat tabel tag .
Target_Type- seandainya Anda menandai beberapa tabel
Target- Kunci catatan ditandai
Tag - Teks dari sebuah tag

Meminta data akan seperti:

Select distinct target from tags   
where tag in ([your list of tags to search for here])  
and target_type = [the table you're searching]

PEMBARUAN
Berdasarkan kebutuhan Anda DAN kondisi, kueri di atas akan berubah menjadi sesuatu seperti ini

select target
from (
  select target, count(*) cnt 
  from tags   
  where tag in ([your list of tags to search for here])
    and target_type = [the table you're searching]
)
where cnt = [number of tags being searched]

1

Saya akan menyarankan @Zizzencs kedua bahwa Anda mungkin menginginkan sesuatu yang tidak sepenuhnya (R) DB-centric

Entah bagaimana, saya percaya bahwa menggunakan bidang nvarchar biasa untuk menyimpan tag dengan beberapa caching / pengindeksan yang tepat dapat menghasilkan hasil yang lebih cepat. Tapi itu hanya aku.

Saya telah menerapkan sistem penandaan menggunakan 3 tabel untuk mewakili hubungan Banyak-ke-Banyak sebelumnya (Item Tag ItemTags), tapi saya kira Anda akan berurusan dengan tag di banyak tempat, saya dapat memberitahu Anda bahwa dengan 3 tabel harus dimanipulasi / ditanya secara bersamaan setiap saat pasti akan membuat kode Anda lebih kompleks.

Anda mungkin ingin mempertimbangkan apakah kompleksitas tambahannya sepadan.


0

Anda tidak akan dapat menghindari bergabung dan masih agak dinormalisasi.

Pendekatan saya adalah memiliki Tag Table.

 TagId (PK)| TagName (Indexed)

Lalu, Anda memiliki kolom TagXREFID di tabel item Anda.

Kolom TagXREFID ini adalah FK ke tabel ke-3, saya akan menyebutnya TagXREF:

 TagXrefID | ItemID | TagId

Jadi, untuk mendapatkan semua tag untuk item akan menjadi sesuatu seperti:

SELECT Tags.TagId,Tags.TagName 
     FROM Tags,TagXref 
     WHERE TagXref.TagId = Tags.TagId 
         AND TagXref.ItemID = @ItemID

Dan untuk mendapatkan semua item untuk tag, saya akan menggunakan sesuatu seperti ini:

SELECT * FROM Items, TagXref
     WHERE TagXref.TagId IN 
          ( SELECT Tags.TagId FROM Tags
                WHERE Tags.TagName = @TagName; )
     AND Items.ItemId = TagXref.ItemId;

Untuk DAN sekelompok tag bersama-sama, Anda akan sedikit mengubah pernyataan di atas untuk menambahkan AND Tags.TagName = @ TagName1 AND Tags.TagName = @ TagName2 dll ... dan secara dinamis membuat kueri.


0

Yang ingin saya lakukan adalah memiliki sejumlah tabel yang mewakili data mentah, jadi dalam hal ini Anda akan melakukannya

Items (ID pk, Name, <properties>)
Tags (ID pk, Name)
TagItems (TagID fk, ItemID fk)

Ini berfungsi cepat untuk waktu penulisan, dan menjaga semuanya tetap normal, tetapi Anda juga dapat mencatat bahwa untuk setiap tag, Anda harus bergabung dengan tabel dua kali untuk setiap tag lebih lanjut yang ingin Anda AND, jadi itu lambat dibaca.

Solusi untuk meningkatkan membaca adalah dengan membuat tabel caching pada perintah dengan mengatur prosedur tersimpan yang pada dasarnya membuat tabel baru yang mewakili data dalam format yang diratakan ...

CachedTagItems(ID, Name, <properties>, tag1, tag2, ... tagN)

Kemudian Anda dapat mempertimbangkan seberapa sering tabel Item Tagged harus tetap up to date, jika ada di setiap sisipan, lalu panggil prosedur tersimpan dalam peristiwa penyisipan kursor. Jika ini adalah tugas per jam, maka siapkan pekerjaan per jam untuk menjalankannya.

Sekarang untuk benar-benar pintar dalam pengambilan data, Anda harus membuat prosedur tersimpan untuk mendapatkan data dari tag. Daripada menggunakan kueri bersarang dalam pernyataan kasus besar-besaran, Anda ingin meneruskan satu parameter tunggal yang berisi daftar tag yang ingin Anda pilih dari database, dan mengembalikan kumpulan catatan Item. Ini akan menjadi yang terbaik dalam format biner, menggunakan operator bitwise.

Dalam format biner, mudah dijelaskan. Katakanlah ada empat tag untuk ditugaskan ke suatu item, dalam biner kita bisa menyatakan itu

0000

Jika keempat tag ditugaskan ke objek, objek akan terlihat seperti ini ...

1111

Jika hanya dua yang pertama ...

1100

Maka itu hanya kasus menemukan nilai-nilai biner dengan 1s dan nol di kolom yang Anda inginkan. Menggunakan operator Bitwise SQL Server, Anda dapat memeriksa bahwa ada 1 di kolom pertama menggunakan kueri yang sangat sederhana.

Periksa tautan ini untuk mencari tahu lebih lanjut .


0

Mengutip apa yang dikatakan orang lain: triknya tidak ada dalam skema , itu ada dalam kueri .

Skema naif Entitas / Label / Tag adalah cara yang tepat untuk pergi. Namun seperti yang Anda lihat, tidak segera jelas cara melakukan kueri DAN dengan banyak tag.

Cara terbaik untuk mengoptimalkan kueri itu akan tergantung pada platform, jadi saya akan merekomendasikan untuk menandai ulang pertanyaan Anda dengan RDBS Anda dan mengubah judul menjadi sesuatu seperti "Cara optimal untuk melakukan DAN permintaan pada basis data penandaan".

Saya punya beberapa saran untuk MS SQL, tetapi akan menahan diri jika itu bukan platform yang Anda gunakan.


6
Anda mungkin tidak boleh menahan diri untuk tidak memberikan informasi tentang teknologi tertentu karena orang lain yang mencoba bekerja dalam domain masalah ini mungkin sebenarnya menggunakan teknologi itu dan akan mendapat manfaat.
Bryan Rehbein

0

Variasi untuk jawaban di atas adalah mengambil id tag, mengurutkannya, menggabungkan sebagai ^ string yang dipisahkan dan hash mereka. Maka cukup kaitkan hash ke item. Setiap kombinasi tag menghasilkan kunci baru. Untuk melakukan pencarian DAN, cukup buat kembali hash dengan id tag yang diberikan dan cari. Mengubah tag pada suatu item akan menyebabkan hash dibuat ulang. Item dengan set tag yang sama berbagi kunci hash yang sama.


4
Dengan pendekatan ini Anda hanya dapat mencari entri dengan set tag yang sama persis - itu selalu sepele. Dalam pertanyaan awal saya, saya ingin menemukan entri yang memiliki semua tag yang saya query, dan mungkin lebih.
Christian Berg

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.