Datenbankdesign für Tagging

171

Wie würden Sie eine Datenbank entwerfen, die die folgenden Tagging-Funktionen unterstützt:

  • Elemente können eine große Anzahl von Tags haben
  • Die Suche nach allen Elementen, die mit einem bestimmten Satz von Tags versehen sind, muss schnell erfolgen (die Elemente müssen ALLE Tags enthalten, es handelt sich also um eine UND-Suche, nicht um eine ODER-Suche).
  • Das Erstellen / Schreiben von Elementen kann langsamer sein, um ein schnelles Nachschlagen / Lesen zu ermöglichen

Idealerweise sollte die Suche aller Elemente, die mit (mindestens) einer Menge von n angegebenen Tags versehen sind, mit einer einzigen SQL-Anweisung durchgeführt werden. Da die Anzahl der zu suchenden Tags sowie die Anzahl der Tags für ein Element unbekannt sind und möglicherweise hoch sind, ist die Verwendung von JOINs unpraktisch.

Irgendwelche Ideen?


Vielen Dank für alle bisherigen Antworten.

Wenn ich mich jedoch nicht irre, zeigen die angegebenen Antworten, wie eine ODER-Suche nach Tags durchgeführt wird. (Wählen Sie alle Elemente mit einem oder mehreren von n Tags aus.) Ich suche eine effiziente UND-Suche. (Wählen Sie alle Elemente mit ALL n Tags aus - und möglicherweise mehr.)

Christian Berg
quelle

Antworten:

22

Über ANDing: Es hört sich so an, als würden Sie nach der Operation "relationale Teilung" suchen. Dieser Artikel behandelt die relationale Aufteilung auf prägnante und dennoch verständliche Weise.

Über die Leistung: Ein Bitmap-basierter Ansatz klingt intuitiv so, als würde er gut zur Situation passen. Ich bin jedoch nicht davon überzeugt, dass es eine gute Idee ist, die Bitmap-Indizierung "manuell" zu implementieren, wie digiguru vorschlägt: Es klingt nach einer komplizierten Situation, wenn neue Tags hinzugefügt werden (?). Einige DBMS (einschließlich Oracle) bieten jedoch Bitmap-Indizes an, die möglicherweise irgendwie funktionieren von Nutzen sein, da ein integriertes Indizierungssystem die potenzielle Komplexität der Indexpflege beseitigt; Darüber hinaus sollte ein DBMS, das Bitmap-Indizes anbietet, in der Lage sein, diese bei der Ausführung des Abfrageplans angemessen zu berücksichtigen.

Troels Arvin
quelle
4
Ich muss sagen, dass die Antwort etwas kurzsichtig ist, da die Verwendung eines Bitfeldtyps der Datenbank Sie auf eine bestimmte Anzahl von Bits beschränkt. Dies bedeutet nicht, dass jedes Element auf eine bestimmte Anzahl von Tags beschränkt ist, sondern dass es im gesamten System nur eine bestimmte Anzahl eindeutiger Tags geben kann (normalerweise bis zu 32 oder 64).
Mark Renouf
1
Unter der Annahme einer 3nf-Implementierung (Question, Tag, Question_has_Tag) und eines Bitmap-Index für die Tag_id in Question_has_Tag muss der Bitmap-Index jedes Mal neu erstellt werden, wenn einer Frage ein Tag hinzugefügt oder entfernt wird. Eine Abfrage wie 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)sollte in Ordnung sein und skalieren, vorausgesetzt, die richtigen B-Tree-Indizes sind in der mittleren Tabelle vorhanden
Adam Musch
Der Link "Dieser Artikel" ist tot. Ich hätte das gerne gelesen :(
mpen
3
Mark: Dieser sieht gut aus: simple-talk.com/sql/t-sql-programming/… Es ist wahrscheinlich eine neu veröffentlichte Version desjenigen, auf den ich mich bezogen habe.
Troels Arvin
Die URL des Artikels ist nicht mehr gültig
Sebastien H.
77

Hier ist ein guter Artikel zum Kennzeichnen von Datenbankschemata:

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

zusammen mit Leistungstests:

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

Beachten Sie, dass die Schlussfolgerungen dort sehr spezifisch für MySQL sind, das (zumindest 2005 zum Zeitpunkt der Erstellung) sehr schlechte Volltextindizierungseigenschaften aufwies.

Jeff Atwood
quelle
1
Ich würde auch gerne detailliertere technische Einblicke erhalten, wie Sie das Tagging-System mit SO implementiert haben. Ich denke, in einem Podcast haben Sie gesagt, Sie behalten alle Tags mit jeder Frage in einer Spalte und serialisieren / de-serialisieren sie dann im laufenden Betrieb? Ich würde gerne mehr darüber erfahren und vielleicht einige Codefragmente sehen. Ich habe mich umgesehen und Details gefunden. Gibt es einen Link, über den Sie dies bereits getan haben, bevor ich die Frage zu META stelle?
Marston A.
5
Diese Frage zu Meta enthält einige Informationen zum SO-Schema: meta.stackexchange.com/questions/1863/so-database-schema
Barrett
Die ursprünglichen Links waren tot, aber ich glaube, ich habe ihren neuen Standort gefunden. Möglicherweise möchten Sie überprüfen, ob dies die Artikel sind, auf die Sie sich bezogen haben.
Brad Larson
12
Obwohl dies von @Jeff geschrieben wurde, ist dies im Wesentlichen immer noch eine Antwort nur auf Links.
neugierigdannii
13

Ich sehe kein Problem mit einer einfachen Lösung: Tabelle für Elemente, Tabelle für Tags, Crosstable für "Tagging"

Indizes auf Kreuztabelle sollten ausreichend optimiert sein. Auswahl geeigneter Elemente wäre

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

UND Tagging wäre

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 ...

Das ist zugegebenermaßen nicht so effizient für eine große Anzahl von Vergleichstags. Wenn Sie die Anzahl der Tags im Speicher beibehalten möchten, können Sie eine Abfrage durchführen, um mit Tags zu beginnen, die nicht häufig vorkommen, sodass die AND-Sequenz schneller ausgewertet wird. Abhängig von der erwarteten Anzahl der Tags, mit denen abgeglichen werden soll, und der Erwartung, dass sie mit einem einzelnen übereinstimmen, könnte dies eine OK-Lösung sein. Wenn Sie 20 Tags abgleichen und erwarten, dass ein zufälliges Element mit 15 von ihnen übereinstimmt, ist dies immer noch schwer in einer Datenbank.

Slartibartfast
quelle
13

Ich wollte nur hervorheben, dass der Artikel, auf den @Jeff Atwood verweist ( http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/ ), sehr gründlich ist (er beschreibt die Vorzüge von 3 verschiedenen Schemata Ansätze) und hat eine gute Lösung für die UND-Abfragen, die normalerweise eine bessere Leistung erbringen als die bisher hier erwähnten (dh es wird nicht für jeden Begriff eine korrelierte Unterabfrage verwendet). Auch viele gute Sachen in den Kommentaren.

ps - Der Ansatz, über den hier alle sprechen, wird im Artikel als "Toxi" -Lösung bezeichnet.

Winston Fassett
quelle
3
Ich erinnere mich, dass ich diesen großartigen Artikel gelesen habe, aber leider ist der Link jetzt tot. :( Kennt jemand einen Spiegel davon?
localhost
5
Der Link war tot: <
Aaron
6

Möglicherweise möchten Sie mit einer nicht streng datenbankbezogenen Lösung wie einer Java Content Repository- Implementierung (z. B. Apache Jackrabbit ) experimentieren und eine darauf aufbauende Suchmaschine wie Apache Lucene verwenden .

Diese Lösung mit den geeigneten Caching-Mechanismen würde möglicherweise eine bessere Leistung liefern als eine selbst entwickelte Lösung.

Ich glaube jedoch nicht, dass Sie in einer kleinen oder mittleren Anwendung eine komplexere Implementierung benötigen würden als die in früheren Beiträgen erwähnte normalisierte Datenbank.

BEARBEITEN: Mit Ihrer Klarstellung scheint es zwingender, eine JCR-ähnliche Lösung mit einer Suchmaschine zu verwenden. Das würde Ihre Programme auf lange Sicht erheblich vereinfachen.

Zizzencs
quelle
5

Am einfachsten ist es, eine Tags- Tabelle zu erstellen .
Target_Type- für den Fall, dass Sie mehrere Tabellen markieren
Target- Der Schlüssel für den zu
Tagmarkierenden Datensatz - Der Text eines Tags

Das Abfragen der Daten wäre ungefähr so:

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

UPDATE
Basierend auf Ihrer Anforderung an AND und den Bedingungen würde sich die obige Abfrage in etwa so verwandeln

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]
Brad Bruce
quelle
1

Ich würde @Zizzencs Vorschlag unterstützen, dass Sie etwas wollen, das nicht vollständig (R) DB-zentriert ist

Irgendwie glaube ich, dass die Verwendung von einfachen nvarchar-Feldern zum Speichern dieser Tags mit einer ordnungsgemäßen Zwischenspeicherung / Indizierung zu schnelleren Ergebnissen führen kann. Aber das bin nur ich.

Ich habe zuvor Tagging-Systeme mit 3 Tabellen implementiert, um eine Viele-zu-Viele-Beziehung darzustellen (Item Tags ItemTags), aber ich nehme an, Sie werden sich an vielen Stellen mit Tags befassen. Ich kann Ihnen sagen, dass 3 Tabellen erforderlich sind Wenn Sie die ganze Zeit gleichzeitig manipuliert / abgefragt werden, wird Ihr Code definitiv komplexer.

Vielleicht möchten Sie überlegen, ob sich die zusätzliche Komplexität lohnt.

Chakrit
quelle
0

Sie können Verknüpfungen nicht vermeiden und sind dennoch etwas normalisiert.

Mein Ansatz ist es, eine Tag-Tabelle zu haben.

 TagId (PK)| TagName (Indexed)

Dann haben Sie eine TagXREFID-Spalte in Ihrer Artikeltabelle.

Diese TagXREFID-Spalte ist eine FK für eine dritte Tabelle. Ich werde sie TagXREF nennen:

 TagXrefID | ItemID | TagId

Alle Tags für einen Artikel zu erhalten, wäre ungefähr so:

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

Und um alle Elemente für ein Tag zu erhalten, würde ich Folgendes verwenden:

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

Um eine Reihe von Tags UND zu verknüpfen, müssen Sie die obige Anweisung geringfügig ändern, um AND-Tags hinzuzufügen.TagName = @ TagName1 AND Tags.TagName = @ TagName2 usw. und die Abfrage dynamisch erstellen.

FlySwat
quelle
0

Was ich gerne mache, ist eine Reihe von Tabellen, die die Rohdaten darstellen. In diesem Fall hätten Sie also

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

Dies funktioniert schnell für die Schreibzeiten und hält alles normal, aber Sie können auch beachten, dass Sie für jedes Tag zweimal Tabellen für jedes weitere Tag verknüpfen müssen, das Sie UND möchten, damit es langsam gelesen wird.

Eine Lösung zur Verbesserung des Lesens besteht darin, auf Befehl eine Caching-Tabelle zu erstellen, indem eine gespeicherte Prozedur eingerichtet wird, die im Wesentlichen eine neue Tabelle erstellt, die die Daten in einem reduzierten Format darstellt ...

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

Anschließend können Sie überlegen, wie oft die Tabelle mit markierten Elementen auf dem neuesten Stand gehalten werden muss. Wenn sie sich bei jeder Einfügung befindet, rufen Sie die gespeicherte Prozedur in einem Cursor-Einfügeereignis auf. Wenn es sich um eine stündliche Aufgabe handelt, richten Sie einen stündlichen Job ein, um sie auszuführen.

Um das Abrufen von Daten wirklich clever zu gestalten, sollten Sie eine gespeicherte Prozedur erstellen, um Daten aus den Tags abzurufen. Anstatt verschachtelte Abfragen in einer massiven case-Anweisung zu verwenden, möchten Sie einen einzelnen Parameter übergeben, der eine Liste von Tags enthält, die Sie aus der Datenbank auswählen möchten, und einen Datensatzsatz von Elementen zurückgeben. Dies ist am besten im Binärformat mit bitweisen Operatoren.

Im Binärformat ist es leicht zu erklären. Angenommen, einem Element müssen vier Tags zugewiesen werden. In Binärform könnten wir dies darstellen

0000

Wenn alle vier Tags einem Objekt zugewiesen sind, sieht das Objekt folgendermaßen aus ...

1111

Wenn nur die ersten beiden ...

1100

Dann müssen Sie nur noch die Binärwerte mit den Einsen und Nullen in der gewünschten Spalte finden. Mit den Bitwise-Operatoren von SQL Server können Sie mithilfe sehr einfacher Abfragen überprüfen, ob in der ersten Spalte eine 1 steht.

Überprüfen Sie diesen Link, um mehr zu erfahren .

Digiguru
quelle
0

Um zu paraphrasieren, was andere gesagt haben: Der Trick ist nicht im Schema , sondern in der Abfrage .

Das naive Schema von Entities / Labels / Tags ist der richtige Weg. Wie Sie gesehen haben, ist jedoch nicht sofort klar, wie eine UND-Abfrage mit vielen Tags ausgeführt werden soll.

Der beste Weg, um diese Abfrage zu optimieren, ist plattformabhängig. Ich würde daher empfehlen, Ihre Frage erneut mit Ihrem RDBS zu kennzeichnen und den Titel in "Optimale Methode zum Ausführen einer UND-Abfrage in einer Kennzeichnungsdatenbank" zu ändern.

Ich habe ein paar Vorschläge für MS SQL, werde aber darauf verzichten, falls dies nicht die Plattform ist, die Sie verwenden.

Portman
quelle
6
Sie sollten wahrscheinlich nicht darauf verzichten, Informationen über eine bestimmte Technologie zu geben, da andere Personen, die versuchen, in dieser Problemdomäne zu arbeiten, diese Technologie möglicherweise tatsächlich verwenden und davon profitieren würden.
Bryan Rehbein
0

Eine Variation der obigen Antwort besteht darin, die Tag-IDs zu nehmen, zu sortieren, als ^ getrennte Zeichenfolge zu kombinieren und sie zu hashen. Verknüpfen Sie dann einfach den Hash mit dem Element. Jede Kombination von Tags erzeugt einen neuen Schlüssel. Um eine UND-Suche durchzuführen, erstellen Sie einfach den Hash mit den angegebenen Tag-IDs neu und suchen Sie. Durch Ändern von Tags für ein Element wird der Hash neu erstellt. Elemente mit demselben Tag-Satz haben denselben Hash-Schlüssel.

nitinahuja
quelle
4
Mit diesem Ansatz können Sie nur nach Einträgen mit genau denselben Tags suchen - das ist immer trivial. In meiner ursprünglichen Frage möchte ich Einträge finden, die alle Tags enthalten, nach denen ich frage, und möglicherweise mehr.
Christian Berg
0

Wenn Sie einen Array-Typ haben, können Sie die erforderlichen Daten vorab aggregieren. Siehe diese Antwort in einem separaten Thread:

Was ist der Nutzen des Array-Typs?

Denis de Bernardy
quelle