Empfohlenes SQL-Datenbankdesign für Tags oder Tagging [geschlossen]

288

Ich habe von einigen Möglichkeiten gehört, Tagging zu implementieren. Verwenden einer Zuordnungstabelle zwischen TagID und ItemID (macht für mich Sinn, aber skaliert sie?), Hinzufügen einer festen Anzahl möglicher TagID-Spalten zu ItemID (scheint eine schlechte Idee zu sein), Beibehalten von Tags in einer durch Kommas getrennten Textspalte (Sounds) verrückt, könnte aber funktionieren). Ich habe sogar gehört, dass jemand eine spärliche Matrix empfohlen hat, aber wie wachsen die Tag-Namen dann elegant?

Fehlt mir eine bewährte Methode für Tags?

Dlamblin
quelle
9
Okay, das ist Frage Nr. 20856, die (fast) gleiche Frage ist Nr. 48475, die mindestens zwei Wochen nach dieser Frage gestellt wurde.
Dlamblin
9
Eine weitere interessante Frage ist "Wie implementiert SO Tags?"
Mostafa
1
Eine weitere interessante Frage lautet: "Würden Sie sie internationalisieren und wenn ja, wie?"
DanMan
1
Interessanter Vergleich (Postgres-spezifisch): databaseSoup.com/2015/01/tag-all-things.html
a_horse_with_no_name

Antworten:

406

Drei Tabellen (eine zum Speichern aller Elemente, eine für alle Tags und eine für die Beziehung zwischen den beiden), die ordnungsgemäß indiziert sind und deren Fremdschlüssel in einer ordnungsgemäßen Datenbank ausgeführt werden, sollten gut funktionieren und ordnungsgemäß skaliert werden.

Table: Item
Columns: ItemID, Title, Content

Table: Tag
Columns: TagID, Title

Table: ItemTag
Columns: ItemID, TagID
Yaakov Ellis
quelle
32
Dies ist als "Toxi" -Lösung bekannt. Weitere Informationen dazu finden Sie hier: howto.philippkeller.com/2005/04/24/Tags-Database-schemas
The Pixel Developer
16
Eine Sache, die hier nicht gezeigt wird, sind hierarchische "Tags" oder Kategorien in der Tag-Tabelle. Dies ist häufig auf Websites erforderlich, die Kategorien und Unterkategorien aufweisen, jedoch die Flexibilität der Kennzeichnung benötigen. Zum Beispiel Rezept-Sites, Autoteile-Sites, Geschäftsverzeichnisse usw. Diese Datentypen passen normalerweise nicht nur in eine einzelne Kategorie, daher ist das Markieren die Antwort, aber Sie müssen so etwas wie das verschachtelte Set-Modell oder das Adjacency List-Modell verwenden in Ihrer Tag-Tabelle.
HK1
5
Ich stimme mit HK1 überein, ist es mit der obigen Struktur möglich + Tabelle: TagGroup Spalten: TagGropuId, Titeltabelle: Tag Spalten: TagID, Titel, TagGroupId
Thunder
Wenn ich eine CSS-Spalte zur Tabelle hinzufügen möchte, füge ich eine CSS-Spalte zur Tag-Tabelle hinzu.
Amitābha
10
@ftvs: Link wieder unterbrochen, der neue Link ist howto.philippkeller.com/2005/04/24/Tags-Database-schemas
Hansaplast
83

Normalerweise würde ich Yaakov Ellis zustimmen, aber in diesem speziellen Fall gibt es eine andere praktikable Lösung:

Verwenden Sie zwei Tabellen:

Table: Item
Columns: ItemID, Title, Content
Indexes: ItemID

Table: Tag
Columns: ItemID, Title
Indexes: ItemId, Title

Dies hat einige wesentliche Vorteile:

Erstens macht es die Entwicklung viel einfacher: In der Drei-Tabellen-Lösung zum Einfügen und Aktualisieren müssen itemSie die TagTabelle nachschlagen, um festzustellen , ob bereits Einträge vorhanden sind. Dann musst du sie mit neuen verbinden. Dies ist keine triviale Aufgabe.

Dann werden Abfragen einfacher (und möglicherweise schneller). Es gibt drei wichtige Datenbankabfragen, die Sie ausführen werden: Alle Tagsfür einen Itemausgeben, eine Tag-Cloud zeichnen und alle Elemente für einen Tag-Titel auswählen.

Alle Tags für einen Artikel:

3-Tabelle:

SELECT Tag.Title 
  FROM Tag 
  JOIN ItemTag ON Tag.TagID = ItemTag.TagID
 WHERE ItemTag.ItemID = :id

2-Tabelle:

SELECT Tag.Title
FROM Tag
WHERE Tag.ItemID = :id

Tag-Cloud:

3-Tabelle:

SELECT Tag.Title, count(*)
  FROM Tag
  JOIN ItemTag ON Tag.TagID = ItemTag.TagID
 GROUP BY Tag.Title

2-Tabelle:

SELECT Tag.Title, count(*)
  FROM Tag
 GROUP BY Tag.Title

Artikel für einen Tag:

3-Tabelle:

SELECT Item.*
  FROM Item
  JOIN ItemTag ON Item.ItemID = ItemTag.ItemID
  JOIN Tag ON ItemTag.TagID = Tag.TagID
 WHERE Tag.Title = :title

2-Tabelle:

SELECT Item.*
  FROM Item
  JOIN Tag ON Item.ItemID = Tag.ItemID
 WHERE Tag.Title = :title

Es gibt jedoch auch einige Nachteile: Es kann mehr Speicherplatz in der Datenbank beanspruchen (was zu mehr langsameren Festplattenoperationen führen kann) und es wird nicht normalisiert, was zu Inkonsistenzen führen kann.

Das Größenargument ist nicht so stark, da Tags naturgemäß ziemlich klein sind, sodass die Größenerhöhung nicht groß ist. Man könnte argumentieren, dass die Abfrage nach dem Tag-Titel in einer kleinen Tabelle, die jedes Tag nur einmal enthält, viel schneller ist, und dies ist sicherlich wahr. Wenn man jedoch die Einsparungen berücksichtigt, die entstehen, wenn man nicht beitreten muss, und die Tatsache, dass man einen guten Index darauf aufbauen kann, könnte dies leicht kompensiert werden. Dies hängt natürlich stark von der Größe der von Ihnen verwendeten Datenbank ab.

Das Inkonsistenzargument ist ebenfalls ein wenig umstritten. Tags sind Freitextfelder und es wird keine Operation wie "Alle Tags" foo "in" bar "umbenennen" erwartet.

Also tldr: Ich würde mich für die Zwei-Tabellen-Lösung entscheiden. (In der Tat werde ich. Ich habe diesen Artikel gefunden, um zu sehen, ob es gültige Argumente dagegen gibt.)

Scheintod
quelle
Bedeutet "Index: ItemId, Title" einen Index für jeden oder einen Index, der beide enthält?
DanMan
Normalerweise zwei Indizes. Kann jedoch von der verwendeten Datenbank abhängen.
Scheintod
1
Ist ItemId und Tag in der Tag-Tabelle ein zusammengesetzter Schlüssel? oder hast du auch eine PK?
Rippo
2
Auf diese Weise können Sie keine "nicht verwendeten" Tags erstellen, sodass für ein Element die Funktion "Tag hinzufügen" ausgeführt werden muss. Auf der anderen Methode kann die Funktion "Tag hinzufügen" unabhängig ausgeführt werden
Gianluca Ghettini
1
@ Quilang. Ich glaube immer noch, dass es davon abhängt, was Sie vorhaben :) Ich habe es auf beide Arten in verschiedenen Projekten implementiert. In meinem letzten Fall hatte ich eine 3-Tabellen-Lösung, weil ich einen "Tag-Typ" (oder andere Metainformationen zum Tag) benötigte und Code von einem engen Cousin von Tags: Parametern wiederverwenden konnte. Aber im selben Projekt habe ich genau diese Methode für einen noch engeren Cousin verwendet: Flaggen (z. B. "verkauft", "neu", "heiß")
Scheintod
38

Wenn Sie eine Datenbank verwenden, die Map-Reduction unterstützt, wie Couchdb, ist das Speichern von Tags in einem Nur-Text- oder Listenfeld in der Tat der beste Weg. Beispiel:

tagcloud: {
  map: function(doc){ 
    for(tag in doc.tags){ 
      emit(doc.tags[tag],1) 
    }
  }
  reduce: function(keys,values){
    return values.length
  }
}

Wenn Sie dies mit group = true ausführen, werden die Ergebnisse nach Tag-Namen gruppiert und es wird sogar angegeben, wie oft dieses Tag gefunden wurde. Es ist dem Zählen der Vorkommen eines Wortes im Text sehr ähnlich .

Nick Retallack
quelle
4
+1 Schön, auch einige NoSQL-Implementierungen zu sehen.
Xeoncross
@ NickRetallack Der Link funktioniert nicht. Wenn Sie könnten, aktualisieren Sie bitte diese Antwort.
Xralf
Ok, ich habe den Link durch einen zu archive.org ersetzt
Nick Retallack
13

Verwenden Sie eine einzelne formatierte Textspalte [1] zum Speichern der Tags und verwenden Sie eine leistungsfähige Volltextsuchmaschine, um diese zu indizieren. Andernfalls treten beim Versuch, boolesche Abfragen zu implementieren, Skalierungsprobleme auf.

Wenn Sie Details zu den vorhandenen Tags benötigen, können Sie diese entweder in einer inkrementell verwalteten Tabelle verfolgen oder einen Stapeljob ausführen, um die Informationen zu extrahieren.

[1] Einige RDBMS bieten sogar einen nativen Array-Typ, der möglicherweise noch besser für die Speicherung geeignet ist, da kein Parsing-Schritt erforderlich ist, jedoch Probleme bei der Volltextsuche verursachen kann.

David Schmitt
quelle
Kennen Sie eine Volltextsuchmaschine, die keine Variationen eines Wortes findet? Wenn Sie beispielsweise nach einem Buch suchen, werden Bücher zurückgegeben? Was machen Sie auch mit Tags wie "c ++"? SQL Server würde beispielsweise die Pluszeichen im Index entfernen. Vielen Dank.
Jonathan Wood
Versuchen Sie Sphinx - sphinxsearch.com
Roman
Dieses dreiteilige Tutorial kann für diejenigen nützlich sein, die diesen Weg gehen (Volltextsuche). Es verwendet native PostgreSQL-Funktionen: shisaa.jp/postset/postgresql-full-text-search-part-1.html
Will
Ist dies in Bezug auf die Leistung besser als die ausgewählte Antwort?
Wie wäre es mit dem Speichern mit varchar 255, durch Kommas getrennten Tags und dem Hinzufügen eines kfull-Textindex?
9

Ich habe die Tags immer in einer separaten Tabelle gespeichert und dann eine Zuordnungstabelle erstellt. Natürlich habe ich auch noch nie etwas wirklich Großes gemacht.

Mit einer "Tags" -Tabelle und einer Map-Tabelle ist es ziemlich trivial, Tag-Clouds und dergleichen zu generieren, da Sie SQL einfach zusammenstellen können, um eine Liste von Tags mit der Anzahl der verwendeten Tags zu erhalten.

Mark Biek
quelle
6
Dies ist noch einfacher, wenn Sie keine Zuordnungstabelle verwenden :)
Scheintod
0

Ich würde folgendes Design vorschlagen: Artikeltabelle: Itemid, taglist1, taglist2
Dies ist schnell und erleichtert das Speichern und Abrufen der Daten auf Artikelebene .

Erstellen Sie parallel eine weitere Tabelle: Tags-Tags machen Tags nicht zu eindeutigen Bezeichnern. Wenn in der zweiten Spalte, die beispielsweise 100 Elemente enthält, nicht genügend Speicherplatz vorhanden ist, wird eine weitere Zeile erstellt.

Jetzt bei der Suche nach Elementen für ein Tag wird es super schnell sein.

user236575
quelle
en.wikipedia.org/wiki/First_normal_form Obwohl es Ausnahmen gibt, können Sie denormalisieren, aber nicht hier
Dheeraj