Etiket sistemi nasıl uygulanır


90

SO'da kullanılana benzer bir etiket sistemini uygulamanın en iyi yolunun ne olduğunu merak ediyordum. Bunu düşünüyordum ama ölçeklenebilir iyi bir çözüm bulamıyorum.

Temel bir 3 masalı çözüme sahip olmayı düşünüyordum: bir tagsmasa, bir articlesmasa ve bir tag_to_articlesmasaya sahip olmak.

Bu soruna en iyi çözüm bu mu yoksa alternatifler var mı? Bu yöntemi kullanarak tablo zamanla aşırı derecede genişleyecektir ve bunun arama için çok verimli olmadığını varsayıyorum. Öte yandan, sorgunun hızlı çalışması o kadar da önemli değildir.


Yanıtlar:


119

Bu blog gönderisini ilginç bulacağınıza inanıyorum: Etiketler: Veritabanı şemaları

Sorun: Bir yer imini (veya bir blog gönderisini veya herhangi bir şeyi) istediğiniz kadar etiketle etiketleyebileceğiniz bir veritabanı şemasına sahip olmak istiyorsunuz. Daha sonra, yer imlerini etiketlerin birleşimi veya kesişimiyle sınırlamak için sorgular çalıştırmak istersiniz. Ayrıca bazı etiketleri arama sonucundan hariç tutmak (örneğin: eksi) istersiniz.

"MySQLicious" çözümü

Bu çözümde, şema sadece bir tabloya sahiptir, normalden arındırılmıştır. MySQLicious, del.icio.us verilerini bu yapıya sahip bir tabloya aktardığı için bu türe "MySQLicious çözüm" adı verilir.

görüntü açıklamasını buraya giringörüntü açıklamasını buraya girin

Kesişim (VE) "arama + web hizmeti + semweb" için sorgu:

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

"Arama | web hizmeti | semweb" için Union (OR) Sorgusu:

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

Eksi "arama + webservice-semweb" için Sorgu

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

"Scuttle" çözümü

Scuttle , verilerini iki tablo halinde düzenler. Bu "scCategories" tablosu "etiket" tablosudur ve "yer imi" tablosuna bir yabancı anahtarı vardır.

görüntü açıklamasını buraya girin

Kesişim (VE) "yer işareti + web hizmeti + semweb" için sorgu:

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

İlk olarak, etiketin "yer imi", "web hizmeti" veya "semweb" olduğu (c.category IN ("yer işareti", "webservice", "semweb")) tüm yer imi-etiket kombinasyonları aranır, ardından yalnızca aranan üç etiketin tümü dikkate alınır (SAYISI VAR (b.bId) = 3).

"Yer imi | webservice | semweb" için Union (OR) Sorgusu: HAVING yan tümcesini atlayın ve sendika var:

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

Eksi (Hariç Tutma) "yer imi + webservice-semweb" için Sorgu, yani: yer imi VE web hizmeti VE semweb DEĞİL.

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

SAYISI OLMAKTAN ayrılmak, "yer işareti | webservice-semweb" için Sorguya yol açar.


"Toxi" çözümü

Toxi , üç masalı bir yapı buldu. Tablo "etiket eşleme" aracılığıyla yer imleri ve etiketler n'den m'ye ilişkilidir. Her etiket farklı yer imleriyle birlikte kullanılabilir ve bunun tersi de geçerlidir. Bu DB şeması aynı zamanda wordpress tarafından da kullanılmaktadır. Sorgular, "scuttle" çözümüyle tamamen aynı.

görüntü açıklamasını buraya girin

Kesişim (VE) "yer işareti + web hizmeti + semweb" için sorgu

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

"Yer işareti | webservice | semweb" için Union (OR) Sorgusu

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

Eksi (Hariç Tutma) "yer imi + webservice-semweb" için Sorgu, yani: yer imi VE web hizmeti VE semweb DEĞİL.

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

SAYISI OLMAKTAN ayrılmak, "yer işareti | webservice-semweb" için Sorguya yol açar.


3
bu blog yazısının yazarı burada. Blog artık Chrome tarafından engellenmiyor (aptal wordpress güvenlik açıkları, şimdi tumblr'a taşındı). Markdown'a dönüştürme şeref
hansaplast

merhaba @Philipp. Tamam, cevabımı düzenledim. BTW, veritabanı etiket sistemleri hakkındaki harika gönderi için teşekkürler.
Nick Dandoulakis

1
Bir not olarak: Toxi çözümü için Kesişim Sorgusunun, "yer imi" VE "web hizmeti" için arama yaptıysanız, yer işaretini de göstermesini istiyorsanız, "SAYISI VAR (b.id) = 3" 3 - "sizeof (dizi ('yer imi', 'web hizmeti'))". Bunu dinamik etiket sorgu işlevi olarak kullanmayı planlıyorsanız, yalnızca küçük bir ayrıntı.
zehirlemek 20

3
gönderide bahsedilen farklı çözümler için performans karşılaştırması için herhangi bir bağlantı var mı?
kampta

@kampta, hayır, hiç bağlantım yok.
Nick Dandoulakis

8

Üç masalı çözümünüzde yanlış bir şey yok.

Diğer bir seçenek de, bir makaleye uygulanabilecek etiket sayısını sınırlamak (SO'da 5 gibi) ve bunları doğrudan makale tablonuza eklemektir.

DB'yi normalleştirmenin avantajları ve dezavantajları vardır, tıpkı şeyleri tek bir tabloya sabitlemek gibi avantajlar ve dezavantajlar vardır.

Hiçbir şey ikisini birden yapamayacağınızı söylemez. Bilgiyi tekrarlamak ilişkisel DB paradigmalarına aykırıdır, ancak hedef performanssa paradigmaları kırmanız gerekebilir.


Evet, etiketleri doğrudan makaleler tablosuna koymak, bu yöntemin birkaç dezavantajı olmasına rağmen, kesinlikle bir seçenek olacaktır. 5 etiketi (etiket1,2,3,4) gibi virgülle ayrılmış bir alanda saklarsanız, bu kolay bir yöntem olacaktır. Soru, aramanın daha hızlı gidip gitmeyeceğidir. Örneğin birisi tag1 ile her şeyi görmek istiyorsa, tüm makale tablosuna bakmanız gerekir. Bu, tag_to_article tablosundan daha az olacaktır. Ancak yine de, tags_to_article tablosu daha incedir. Başka bir şey de her seferinde php'de patlamanız gerekmesi, bunun zaman alır mı bilmiyorum.
Saif Bechan

Her ikisini de yaparsanız (makale ile etiketler ve ayrı bir tabloda), bu size hem merkez merkezli aramalar hem de etiket merkezli aramalar için performans sağlar. Takas, tekrarlanan bilgileri muhafaza etmenin yüküdür. Ayrıca, etiket sayısını sınırlandırarak her birini kendi sütununa yerleştirebilirsiniz. Sadece XXXXX makalelerinden * Seçin ve gidin; patlamaya gerek yok.
John

6

Önerilen üç tablo uygulamanız etiketleme için çalışacaktır.

Bununla birlikte, yığın taşması farklı uygulamalar kullanır. Etiketleri varchar sütununa gönderiler tablosunda düz metin olarak saklarlar ve etiketlerle eşleşen gönderileri almak için tam metin indekslemeyi kullanırlar. Örneğin posts.tags = "algorithm system tagging best-practices". Eminim Jeff bundan bir yerlerde bahsetmiştir ama nerede olduğunu unuttum.


4
Bu süper verimsiz görünüyor. Ya etiket sırası? Veya ilgili etiketler? ("işlem" in "algoritma" ya benzer olması gibi)
Richard Duerr

3

Önerilen çözüm, etiketler ve makaleler arasındaki çoktan çoğa ilişkisini ele almak için düşünebildiğim en iyi - uygulanabilir tek yol değilse. Yani benim oyum 'evet, yine de en iyisi'. Yine de herhangi bir alternatifle ilgilenirim.


Katılıyorum. Bu Etiketler ve TagMap tabloları küçük kayıt boyutuna sahiptir ve uygun şekilde dizine eklendiğinde performansı önemli ölçüde düşürmemelidir. Öğe başına etiket sayısını sınırlamak da iyi bir fikir olabilir.
PanJanek

2

Veritabanınız indekslenebilir dizileri destekliyorsa (örneğin, PostgreSQL gibi), tamamen normal olmayan bir çözüm öneririm - etiketleri aynı tabloda bir dizi dizisi olarak saklayın. Değilse, nesneleri etiketlere eşleyen ikincil bir tablo en iyi çözümdür. Etiketlere karşı fazladan bilgi depolamanız gerekiyorsa, ayrı bir etiket tablosu kullanabilirsiniz, ancak her etiket araması için ikinci bir birleştirme getirmenin anlamı yoktur.


POstgreSQL yalnızca tamsayı dizilerindeki dizinleri destekler: postgresql.org/docs/current/static/intarray.html
Mike Chamberlain


2

Daha iyi performans için optimize edilmiş MySQLicious'ı önermek istiyorum. Bundan önce Toxi (3 tablo) çözümünün dezavantajları

Milyonlarca sorunuz varsa ve her birinde 5 etiket varsa, o zaman etiket eşleme tablosunda 5 milyon giriş olacaktır. Bu nedenle, önce etiket aramasına dayalı olarak 10 bin etiket haritası girişini filtrelememiz ve ardından bu 10 bin ile eşleşen soruları tekrar filtrelememiz gerekir. Dolayısıyla, artical id basit sayısal ise filtreleme yaparken sorun yok, ancak eğer bir tür UUID (32 varchar) ise, o zaman filtrelemenin indekslenmiş olmasına rağmen daha büyük bir karşılaştırma gerektiriyor.

Çözümüm:

Her yeni etiket oluşturulduğunda, counter ++ (10 tabanı) kullanın ve bu sayacı base64'e dönüştürün. Artık her etiket adı base64 kimliğine sahip olacaktır. ve bu kimliği kullanıcı arayüzüne adıyla birlikte iletin. Bu şekilde, sistemimizde 4095 etiket oluşturulana kadar maksimum iki karakter kimliğine sahip olacaksınız. Şimdi bu birden çok etiketi her bir soru tablosu etiketi sütununa birleştirin. Ayırıcı da ekleyin ve sıralayın.

Yani masa böyle görünüyor

görüntü açıklamasını buraya girin

Sorgulama sırasında gerçek etiket adı yerine id sorgusu yapın O olduğu için SORTED , andetiketinde durum daha verimli olacaktır ( LIKE '%|a|%|c|%|f|%).

Not tek boşluk ayraç yeterli olmadığını ve biz böyle ayırt etme etiketlere çift sınırlayıcı ihtiyaç sqlve mysqlçünkü LIKE "%sql%"dönecektir mysqlsıra sonuçları. OlmalıLIKE "%|sql|%"

Aramanın indekslenmediğini biliyorum, ancak yine de yazar / dateTime gibi makaleyle ilgili diğer sütunlarda indekslenmiş olabilirsiniz, aksi takdirde tam tablo taramasına yol açacaktır.

Son olarak, bu çözümle, birleştirme koşulundaki 5 milyon kayıtla milyon kaydın karşılaştırılması gereken iç birleştirme gerekmez.


Ekip, Lütfen yorumlarınızda bu çözümün sakıncası hakkındaki görüşlerinizi belirtin.
Kanagavelu Sugumar

@Nick Dandoulakis Lütfen yukarıdaki çözümle ilgili yorumlarınızı sağlayarak bana yardımcı olur musunuz?
Kanagavelu Sugumar

@Juha Syrjälä Yukarıdaki çözüm iyi mi?
Kanagavelu Sugumar

0
CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

Notlar:

  • Bu, TOXI'den daha iyidir, çünkü fazladan çoktan geçmez: optimizasyonu zorlaştıran birçok tablo.
  • Elbette, yaklaşımım artık etiketler nedeniyle biraz daha hantal olabilir (TOXI'den), ancak bu tüm veritabanının küçük bir yüzdesidir ve performans iyileştirmeleri önemli olabilir.
  • Oldukça ölçeklenebilir.
  • Bir vekil AUTO_INCREMENTPK'ya sahip değildir (çünkü buna ihtiyacı yoktur) . Dolayısıyla Scuttle'dan daha iyidir.
  • O bir dizin kullanamazsınız çünkü MySQLicious berbat ( LIKEile lider wild card false; hit altdizgelerin üzerine)
  • MySQL için, 'kümeleme' efektleri elde etmek için ENGINE = InnoDB'yi kullandığınızdan emin olun.

İlgili tartışmalar (MySQL için):
birçok: birçok eşleme tablosu optimizasyonu
sıralı listeler

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.