it-swarm.com.de

So implementieren Sie das Tag-System

Ich habe mich gefragt, was der beste Weg ist, ein Tag-System zu implementieren, wie das für SO verwendete. Ich habe darüber nachgedacht, aber ich kann keine gute skalierbare Lösung finden.

Ich dachte an eine einfache 3-Tabellen-Lösung: eine tags-Tabelle, eine articles-Tabelle und eine tag_to_articles-Tabelle.

Ist dies die beste Lösung für dieses Problem oder gibt es Alternativen? Mit dieser Methode würde die Tabelle mit der Zeit extrem groß werden, und für die Suche ist dies nicht zu effizient, nehme ich an. Auf der anderen Seite ist es nicht so wichtig, dass die Abfrage schnell ausgeführt wird.

80
Saif Bechan

Ich glaube, Sie werden diesen Blogbeitrag interessant finden: Tags: Datenbankschemas

Das Problem: Sie möchten ein Datenbankschema haben, in dem Sie eine .__-Datei kennzeichnen können. Lesezeichen (oder einen Blogbeitrag oder was auch immer) mit beliebig vielen Tags . Später möchten Sie Abfragen ausführen, um die Lesezeichen auf eine .__ zu beschränken. Vereinigung oder Schnittmenge von Tags. Sie möchten auch ausschließen (sprich: minus) einige Tags aus dem Suchergebnis.

"MySQLicious" -Lösung

In dieser Lösung hat das Schema nur eine Tabelle, es wird denormalisiert. Dieser Typ wird "MySQLicious-Lösung" genannt, da MySQLicious Daten von del.icio.us in eine Tabelle mit dieser Struktur importiert.

enter image description hereenter image description here

Intersection (AND) Abfrage für "search + webservice + semweb":

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

Union (OR) Abfrage für "search | webservice | semweb":

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

Minus Abfrage für "search + webservice-semweb"

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

"Scuttle" -Lösung

Scuttle organisiert seine Daten in zwei Tabellen. Diese Tabelle "scCategories" ist die "Tag" -Tabelle und hat einen Fremdschlüssel für die "Lesezeichen" -Tabelle.

enter image description here

Intersection (AND) Abfrage für "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

Zuerst werden alle Lesezeichen-Tag-Kombinationen durchsucht, wobei das Tag "Lesezeichen", "Webservice" oder "Semweb" (c.category IN ("Lesezeichen", "Webservice", "Semweb")) und dann nur die Lesezeichen, die das sind haben alle drei gesuchten Tags berücksichtigt (HAVING COUNT (b.bId) = 3).

Union (OR) Abfrage für "bookmark | webservice | semweb": Lassen Sie die HAVING-Klausel weg und Sie haben 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 (Ausschluss) Abfrage für "bookmark + webservice-semweb", d. H. Bookmark UND 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

Das Weglassen des HAVING COUNT führt zur Abfrage für "bookmark | webservice-semweb".


"Toxi" -Lösung

Toxi hatte eine Struktur mit drei Tischen. Über die Tabelle „Tagmap“ stehen die Lesezeichen und die Tags in n-zu-m-Beziehung. Jedes Tag kann zusammen mit verschiedenen Lesezeichen verwendet werden und umgekehrt. Dieses DB-Schema wird auch von WordPress verwendet .. Die Abfragen sind die gleichen wie in der "Scuttle" -Lösung.

enter image description here

Intersection (AND) Abfrage für "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

Union (OR) Abfrage für "Lesezeichen | 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 (Ausschluss) Abfrage für "bookmark + webservice-semweb", d. H. Bookmark UND 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

Das Weglassen des HAVING COUNT führt zur Abfrage für "bookmark | webservice-semweb".

108

Mit Ihrer Drei-Tisch-Lösung ist nichts falsch.

Eine weitere Option besteht darin, die Anzahl der Tags zu begrenzen, die auf einen Artikel angewendet werden können (z. B. 5 in SO), und diese direkt Ihrer Artikeltabelle hinzufügen.

Die Normalisierung der Datenbank hat ihre Vor- und Nachteile, genau wie die Dinge, die hart in einer Tabelle liegen, Vor- und Nachteile haben.

Nichts sagt, dass Sie nicht beides tun können. Es widerspricht relationalen DB-Paradigmen, Informationen zu wiederholen, aber wenn Leistung das Ziel ist, müssen Sie möglicherweise die Paradigmen brechen.

8
John

Ihre vorgeschlagene Implementierung mit drei Tabellen funktioniert für das Tagging.

Stack Overflow verwendet jedoch eine andere Implementierung. Sie speichern Tags in der Spalte varchar in der Posts-Tabelle im Klartext und verwenden die Volltextindizierung zum Abrufen von Posts, die den Tags entsprechen. Zum Beispiel posts.tags = "algorithm system tagging best-practices". Ich bin sicher, dass Jeff dies irgendwo erwähnt hat, aber ich vergesse wo.

6
Juha Syrjälä

Die vorgeschlagene Lösung ist die beste - wenn nicht die einzige praktikable - Methode, die ich mir vorstellen kann, um die Viele-zu-Viele-Beziehung zwischen Tags und Artikeln anzusprechen. Ich stimme also für "Ja, es ist immer noch das Beste". Ich würde mich jedoch für Alternativen interessieren.

3
David Thomas

Ich möchte das optimierte MySQLicious-System für eine bessere Leistung vorschlagen. Vorher sind die Nachteile von Toxi (3-Tisch) -Lösung 

Wenn Sie Millionen von Fragen haben und es jeweils 5 Tags enthält, werden in der Tagmap-Tabelle 5 Millionen Einträge angezeigt. Wir müssen also zunächst 10 Tausend Tagmap-Einträge basierend auf der Tag-Suche herausfiltern und dann die entsprechenden Fragen dieser Zehntausender erneut herausfiltern. Wenn also herausgefiltert wird, ob die artische ID einfach numerisch ist, ist sie in Ordnung, aber wenn es sich um eine Art UUID (32 varchar) handelt, muss beim Filtern ein größerer Vergleich durchgeführt werden, obwohl er indiziert ist. 

Meine Lösung:

Wenn ein neues Tag erstellt wird, verwenden Sie counter ++ (Basis 10) und konvertieren Sie diesen Zähler in base64. Jetzt hat jeder Tag-Name eine base64-ID. und übergeben Sie diese ID zusammen mit dem Namen ..__ an die Benutzeroberfläche. Auf diese Weise haben Sie maximal zwei Zeichen, bis wir 4095 Tags in unserem System erstellt haben. Verketten Sie nun diese mehreren Tags in jeder Tagspalte der Fragetabelle. Fügen Sie auch Trennzeichen hinzu und sortieren Sie es.

So sieht der Tisch so aus

 enter image description here

Bei der Abfrage Abfrage der ID anstelle des realen Tagnamens ..__ Da es SORTIERT ist, ist die Variable and für das Tag effizienter (LIKE '%|a|%|c|%|f|%). 

Beachten Sie, dass das Trennzeichen für ein einzelnes Leerzeichen nicht ausreicht und wir ein doppeltes Trennzeichen benötigen, um Tags wie sql und mysql zu unterscheiden, da LIKE "%sql%" auch mysql-Ergebnisse zurückgibt. Sollte LIKE "%|sql|%" sein

Ich weiß, dass die Suche nicht indiziert ist, aber Sie haben möglicherweise auch auf andere Spalten, die sich auf Artikel beziehen, wie "author/dateTime", indexiert. Andernfalls führt dies zu einem vollständigen Tabellenscan.

Schließlich ist bei dieser Lösung kein Inner Join erforderlich, bei dem Millionen Datensätze mit 5 Millionen Datensätzen verglichen werden müssen.

2

Wenn Ihre Datenbank indizierbare Arrays unterstützt (wie zum Beispiel PostgreSQL), würde ich eine vollständig denormalisierte Lösung empfehlen - Tags als Array von Strings in derselben Tabelle speichern. Wenn nicht, ist eine sekundäre Tabelle die Zuordnung von Objekten zu Tags die beste Lösung. Wenn Sie zusätzliche Informationen für Tags speichern müssen, können Sie eine separate Tagtabelle verwenden. Es ist jedoch sinnlos, für jede Tag-Suche einen zweiten Join einzuführen.

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

Anmerkungen:

  • Dies ist besser als bei TOXI, da es nicht viele zusätzliche Tabellen durchläuft: viele, was die Optimierung schwierig macht.
  • Natürlich ist mein Ansatz aufgrund der redundanten Tags möglicherweise etwas sperriger (als TOXI), aber dies ist ein kleiner Prozentsatz der Datenbank whole, und die Leistungsverbesserungen können erheblich sein.
  • Es ist hoch skalierbar.
  • Es hat nicht (weil es nicht benötigt) einen Ersatz AUTO_INCREMENT PK. Daher ist es besser als Scuttle.
  • MySQLicious ist scheiße, weil es keinen Index verwenden kann (LIKE mit leading Platzhalter; falsche Treffer auf Teilzeichenfolgen).
  • Verwenden Sie für MySQL unbedingt ENGINE = InnoDB, um Clustering-Effekte zu erhalten.

Verwandte Diskussionen (für MySQL):
viele: viele Mapping-Tabellen-Optimierung
geordnete Listen

0
Rick James