Cara menerapkan sistem tag

90

Saya bertanya-tanya apa cara terbaik untuk menerapkan sistem tag, seperti yang digunakan pada SO. Saya sedang memikirkan hal ini, tetapi saya tidak dapat menemukan solusi skalabel yang baik.

Saya berpikir untuk memiliki solusi 3 tabel dasar: memiliki tagsmeja, tabel, articlesdan tag_to_articlesmeja.

Apakah ini solusi terbaik untuk masalah ini, atau adakah alternatifnya? Dengan menggunakan metode ini tabel akan menjadi sangat besar pada waktunya, dan untuk pencarian ini saya asumsikan tidak terlalu efisien. Di sisi lain, tidak penting bahwa kueri dijalankan dengan cepat.

Saif Bechan
sumber

Jawaban:

119

Saya yakin Anda akan menemukan posting blog yang menarik ini: Tag: skema database

Masalah: Anda ingin memiliki skema database di mana Anda dapat menandai bookmark (atau posting blog atau apa pun) dengan tag sebanyak yang Anda inginkan. Kemudian, Anda ingin menjalankan kueri untuk membatasi bookmark ke gabungan atau persimpangan tag. Anda juga ingin mengecualikan (katakanlah: minus) beberapa tag dari hasil pencarian.

Solusi "MySQLicious"

Dalam solusi ini, skema hanya memiliki satu tabel, itu dinormalisasi. Jenis ini disebut "solusi MySQLicious" karena MySQLicious mengimpor data del.icio.us ke dalam tabel dengan struktur ini.

masukkan deskripsi gambar di sinimasukkan deskripsi gambar di sini

Intersection (AND) Query untuk “search + webservice + semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

Kueri Union (OR) untuk “search | webservice | semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

Minus Query untuk "search + webservice-semweb"

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

Solusi "Scuttle"

Scuttle mengatur datanya dalam dua tabel. Tabel "scCategories" adalah "tag" -tabel dan memiliki kunci asing ke "bookmark" -tabel.

masukkan deskripsi gambar di sini

Intersection (AND) Query untuk "bookmark + webservice + semweb":

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

Pertama, semua kombinasi bookmark-tag dicari, di mana tag-nya adalah "bookmark", "webservice" atau "semweb" (c.category IN ('bookmark', 'webservice', 'semweb')), lalu hanya bookmark yang sudah mendapatkan ketiga tag yang dicari diperhitungkan (HAVING COUNT (b.bId) = 3).

Union (OR) Query untuk “bookmark | webservice | semweb”: Tinggalkan klausa HAVING dan Anda memiliki union:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

Minus (Exclusion) Query untuk “bookmark + webservice-semweb”, yaitu: bookmark AND webservice AND NOT semweb.

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

Meninggalkan HAVING COUNT mengarah ke Query untuk “bookmark | webservice-semweb”.


Solusi "Toxi"

Toxi membuat struktur tiga meja. Melalui tabel "tagmap", bookmark dan tag terkait n-to-m. Setiap tag dapat digunakan bersama dengan bookmark yang berbeda dan sebaliknya. Skema DB ini juga digunakan oleh wordpress. Kueri cukup sama seperti pada solusi "scuttle".

masukkan deskripsi gambar di sini

Intersection (AND) Query untuk "bookmark + webservice + semweb"

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

Kueri Union (OR) untuk “bookmark | webservice | semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

Minus (Exclusion) Query untuk “bookmark + webservice-semweb”, yaitu: bookmark AND webservice AND NOT semweb.

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

Meninggalkan HAVING COUNT mengarah ke Query untuk “bookmark | webservice-semweb”.

Nick Dandoulakis
sumber
3
penulis posting blog itu di sini. Blog tidak lagi diblokir oleh Chrome (kerentanan wordpress bodoh, sekarang dipindahkan ke tumblr). Kudos untuk mengubahnya menjadi penurunan harga
hansaplast
hai @Pippipp. Oke, mengedit jawaban saya. BTW, terima kasih atas kiriman yang bagus di sistem tag database.
Nick Dandoulakis
1
Sekadar catatan: Jika Anda ingin Query Intersection untuk solusi Toxi juga menampilkan bookmark jika Anda mencari 'bookmark' DAN 'webservice', Anda perlu mengubah "HAVING COUNT (b.id) = 3" dari 3 menjadi "sizeof (array ('bookmark', 'webservice'))". Hanya detail kecil jika Anda berencana menggunakan ini sebagai fungsi kueri tag dinamis.
racun20
3
ada tautan untuk perbandingan kinerja untuk berbagai solusi yang disebutkan di pos?
kampta
@kampta, tidak, saya tidak punya tautan apa pun.
Nick Dandoulakis
8

Tidak ada yang salah dengan solusi tiga meja Anda.

Pilihan lainnya adalah membatasi jumlah tag yang dapat diterapkan ke artikel (seperti 5 di SO) dan menambahkannya langsung ke tabel artikel Anda.

Normalisasi DB memiliki kelebihan dan kekurangan, seperti hal-hal yang memasang kabel ke dalam satu tabel memiliki kelebihan dan kekurangan.

Tidak ada yang mengatakan Anda tidak bisa melakukan keduanya. Ini bertentangan dengan paradigma DB relasional untuk mengulang informasi, tetapi jika tujuannya adalah kinerja, Anda mungkin harus mematahkan paradigma tersebut.

John
sumber
Ya, meletakkan tag langsung ke tabel artikel pasti akan menjadi opsi, meskipun ada beberapa kelemahan pada metode ini. Jika Anda menyimpan 5 tag dalam bidang yang dipisahkan koma seperti (tag1,2,3,4), ini akan menjadi metode yang mudah. Pertanyaannya adalah apakah pencarian akan berjalan lebih cepat. Misalnya seseorang ingin melihat semuanya dengan tag1, Anda harus melihat seluruh tabel artikel. Ini akan menjadi kurang dari itu melalui tabel tag_to_article. Tapi sekali lagi, tabel tags_to_article lebih ramping. Hal lain adalah Anda harus meledak setiap kali di php, saya tidak tahu apakah ini membutuhkan waktu.
Saif Bechan
Jika Anda melakukan keduanya (tag dengan artikel, dan dalam tabel terpisah) maka ini memberi Anda kinerja baik untuk pencarian pasca-sentris dan untuk pencarian tag-sentris. Pengorbanan adalah beban menjaga informasi yang berulang. Selain itu, dengan membatasi jumlah tag, Anda dapat memasukkan masing-masing tag ke kolomnya sendiri. Cukup Pilih * dari artikel Di mana XXXXX dan pergi; tidak perlu meledak.
Yohanes
6

Penerapan tiga tabel yang Anda usulkan akan berfungsi untuk pemberian tag.

Stack overflow menggunakan implementasi yang berbeda. Mereka menyimpan tag ke kolom varchar di tabel posting dalam teks biasa dan menggunakan pengindeksan teks lengkap untuk mengambil posting yang cocok dengan tag. Misalnya posts.tags = "algorithm system tagging best-practices". Saya yakin Jeff telah menyebutkan ini di suatu tempat tetapi saya lupa di mana.

Juha Syrjälä
sumber
4
Ini sepertinya sangat tidak efisien. Bagaimana dengan urutan tag? Atau tag terkait? (seperti "proses" yang mirip dengan "algoritme" atau semacamnya)
Richard Duerr
3

Solusi yang diusulkan adalah yang terbaik -jika bukan satu-satunya cara yang dapat dipraktikkan- yang dapat saya pikirkan untuk menangani hubungan banyak-ke-banyak antara tag dan artikel. Jadi pilihan saya adalah 'ya, itu masih yang terbaik.' Saya akan tertarik pada alternatif apa pun.

David mengatakan kembalikan Monica
sumber
Saya setuju. Tabel Tag dan TagMap ini memiliki ukuran catatan yang kecil dan jika diindeks dengan benar tidak akan menurunkan kinerja secara drastis. Membatasi jumlah tag per item juga bisa menjadi ide yang bagus.
PanJanek
2

Jika database Anda mendukung array yang dapat diindeks (seperti PostgreSQL, misalnya), saya akan merekomendasikan solusi yang sepenuhnya didenormalisasi - simpan tag sebagai array string pada tabel yang sama. Jika tidak, tabel sekunder yang memetakan objek ke tag adalah solusi terbaik. Jika Anda perlu menyimpan informasi tambahan terhadap tag, Anda dapat menggunakan tabel tag terpisah, tetapi tidak ada gunanya memperkenalkan gabungan kedua untuk setiap pencarian tag.

Nick Johnson
sumber
POstgreSQL hanya mendukung indeks pada array integer: postgresql.org/docs/current/static/intarray.html
Mike Chamberlain
1
Nowadys mendukung teks juga: postgresql.org/docs/9.6/static/arrays.html
luckydonald
2

Saya ingin menyarankan MySQLicious yang dioptimalkan untuk kinerja yang lebih baik. Sebelumnya kekurangan dari solusi Toxi (3 tabel) adalah

Jika Anda memiliki jutaan pertanyaan, dan masing-masing memiliki 5 tag, maka akan ada 5 juta entri di tabel tagmap. Jadi pertama-tama kita harus menyaring 10 ribu entri tagmap berdasarkan pencarian tag, lalu menyaring lagi pertanyaan yang cocok dari 10 ribu itu. Jadi saat memfilter jika id artikal adalah numerik sederhana maka tidak apa-apa, tetapi jika itu jenis UUID (32 varchar) maka memfilter membutuhkan perbandingan yang lebih besar meskipun diindeks.

Solusi saya:

Setiap kali tag baru dibuat, miliki penghitung ++ (basis 10), dan konversikan penghitung tersebut menjadi base64. Sekarang setiap nama tag akan memiliki id base64. dan berikan id ini ke UI bersama dengan nama. Dengan cara ini Anda akan memiliki maksimal dua karakter hingga kami memiliki 4095 tag yang dibuat di sistem kami. Sekarang gabungkan beberapa tag ini ke dalam setiap kolom tag tabel pertanyaan. Tambahkan juga pembatas dan buat itu diurutkan.

Jadi tabel terlihat seperti ini

masukkan deskripsi gambar di sini

Saat membuat kueri, kueri di id alih-alih nama tag asli. Karena SORTED , andkondisi pada tag akan lebih efisien ( LIKE '%|a|%|c|%|f|%).

Perhatikan bahwa pembatas spasi tunggal tidak cukup dan kita memerlukan pembatas ganda untuk membedakan tag seperti sqldan mysqlkarena LIKE "%sql%"akan mengembalikan mysqlhasil juga. SeharusnyaLIKE "%|sql|%"

Saya tahu pencarian tidak diindeks tetapi tetap saja Anda mungkin telah diindeks pada kolom lain yang terkait dengan artikel seperti author / dateTime lain akan mengarah ke pemindaian tabel lengkap.

Akhirnya dengan solusi ini, tidak ada inner join yang dibutuhkan dimana jutaan record harus dibandingkan dengan 5 juta record pada kondisi join.

Kanagavelu Sugumar
sumber
Tim, Tolong berikan masukan Anda tentang kekurangan solusi ini di komentar.
Kanagavelu Sugumar
@Nick Dandoulakis Tolong bantu saya dengan memberikan komentar Anda pada solusi di atas akan berhasil?
Kanagavelu Sugumar
@ Juha Syrjälä Apakah solusi di atas baik-baik saja?
Kanagavelu Sugumar
0
CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

Catatan:

  • Ini lebih baik daripada TOXI karena tidak melalui tabel ekstra banyak: banyak yang membuat pengoptimalan menjadi sulit.
  • Tentu, pendekatan saya mungkin sedikit lebih besar (daripada TOXI) karena tag yang berlebihan, tetapi itu adalah persentase kecil dari keseluruhan database, dan peningkatan kinerja mungkin signifikan.
  • Ini sangat skalabel.
  • Ia tidak memiliki (karena tidak membutuhkan) AUTO_INCREMENTPK pengganti . Karenanya, ini lebih baik dari Scuttle.
  • MySQLicious menyebalkan karena tidak dapat menggunakan indeks ( LIKEdengan wild card terkemuka ; false hits pada substring)
  • Untuk MySQL, pastikan untuk menggunakan ENGINE = InnoDB untuk mendapatkan efek 'clustering'.

Diskusi terkait (untuk MySQL):
banyak: banyak optimasi tabel pemetaan
memerintahkan daftar

Rick James
sumber