2008-08-24 12 views
3

Ich implementiere ein Tagging-System für eine Website. Es gibt mehrere Tags pro Objekt und mehrere Objekte pro Tag. Dies wird erreicht, indem eine Tabelle mit zwei Werten pro Datensatz, einer für die IDs des Objekts und des Tags, aufrechterhalten wird.SQL-Viele-zu-Viele-Abgleich

Ich bin auf der Suche nach einer Abfrage, um die Objekte zu finden, die eine bestimmte Menge von Tags entsprechen. Angenommen, ich folgende Daten hatte (in [object] -> [tags] * Format)

apple -> fruit red food 
banana -> fruit yellow food 
cheese -> yellow food 
firetruck -> vehicle red 

Wenn ich will, übereinstimmen (rot), sollte ich Apfel und Feuerwehrauto bekommen. Wenn ich zusammenpassen möchte (Obst, Essen) sollte ich (Apfel, Banane) bekommen.

Wie schreibe ich eine SQL-Abfrage tun, was ich will?

@ Jeremy Ruten,

Vielen Dank für Ihre Antwort. Die verwendete Notation wurde verwendet, um einige Beispieldaten zu geben - meine Datenbank hat eine Tabelle mit 1 Objekt-ID und 1 Tag pro Datensatz.

Zweitens ist mein Problem, dass ich alle Objekte erhalten muss, die alle Tags übereinstimmen. Ersetzen Sie Ihr OR für ein UND wie folgt:

SELECT object WHERE tag = 'fruit' AND tag = 'food'; 

Ergibt keine Ergebnisse beim Ausführen.

+0

Welche RDBMS verwenden Sie? Wie benötigen Sie das implementiert? eine gespeicherte Prozedur? Wie möchten Sie Parameter übergeben? Oder werden Sie die SQL dynamisch außerhalb des DBMS komponieren? In diesem Fall, mit welcher Programmiersprache? – van

Antwort

4

Gegeben:

  • Objekttabelle (Primärschlüssel id)
  • objecttags Tisch (Fremdschlüssel objectId, MarkierungslD)
  • Tags Tabelle (Primärschlüssel id)

    SELECT distinct o.* 
        from object o join objecttags ot on o.Id = ot.objectid 
           join tags t on ot.tagid = t.id 
    where t.Name = 'fruit' or t.name = 'food'; 
    

Dies scheint rückwärts, da Sie wollen und, aber das Problem ist, 2 Tags sind nicht in der gleichen Zeile und t darum ergibt sich und ergibt nichts, da 1 Reihe nicht gleichzeitig Frucht und Nahrung sein kann. Diese Abfrage liefert in der Regel Duplikate, da Sie pro Objekt eine Zeile jedes Objekts erhalten.

Wenn Sie wirklich tun möchten, und in diesem Fall benötigen Sie eine group by und eine having count = <number of ors> in Ihrer Abfrage zum Beispiel.

SELECT distinct o.name, count(*) as count 
    from object o join objecttags ot on o.Id = ot.objectid 
        join tags t on ot.tagid = t.id 
    where t.Name = 'fruit' or t.name = 'food' 
group by o.name 
    having count = 2; 
+0

Dies ist sehr ineffizient, wenn Sie Millionen von Zeilen in der Datenbank haben –

-2

Ich würde vorschlagen, Ihre Tabelle haben 1 Tag pro Platte zu machen, wie folgt aus:

apple -> fruit 
apple -> red 
apple -> food 
banana -> fruit 
banana -> yellow 
banana -> food 

dann könnte man nur

SELECT object WHERE tag = 'fruit' OR tag = 'food'; 

Wenn Sie es wirklich Sie sich doch tun möchten, können Sie könnte es wie folgt tun:

SELECT object WHERE tag LIKE 'red' OR tag LIKE '% red' OR tag LIKE 'red %' OR tag LIKE '% red %'; 
0

@ Kyle: Ihre Abfrage wie sein sollte:

Ihre Abfrage war auf der Suche nach Zeilen, bei denen das Tag sowohl Obst als auch Essen war, was unmöglich ist, da das Feld nur einen Wert haben kann, nicht beide gleichzeitig.

4

Oh mein Gott, ich habe vielleicht Ihren ursprünglichen Kommentar falsch interpretiert.

Der einfachste Weg, dies in SQL zu tun wäre, um drei Tabellen zu haben:

1) Tags (tag_id, name) 
2) Objects (whatever that is) 
3) Object_Tag(tag_id, object_id) 

Dann können Sie praktisch jede Frage, die Sie von den Daten wollen fragen, schnell, einfach und effizient (vorausgesetzt, Sie Index in angemessener Weise) . Wenn Sie Lust haben, können Sie auch Mehrwort-Tags zulassen (es gibt eine elegante und eine weniger elegante Art, die mir einfällt).

Ich gehe davon aus, dass das, was Sie haben, so dass diese SQL wird im folgenden arbeiten:

Die wörtliche Weise:

SELECT obj 
     FROM object 
    WHERE EXISTS(SELECT * 
        FROM tags 
        WHERE tag = 'fruit' 
         AND oid = object_id) 
     AND EXISTS(SELECT * 
        FROM tags 
        WHERE tag = 'Apple' 
         AND oid = object_id) 

Es gibt auch andere Möglichkeiten, wie Sie es tun können, wie zum Beispiel:

SELECT oid 
    FROM tags 
WHERE tag = 'Apple' 
INTERSECT 
SELECT oid 
    FROM tags 
WHERE tag = 'Fruit' 
0

Kombinieren Steve M. Vorschlag mit Jeremys Sie einen einzelnen Datensatz bekommen mit, was Sie suchen:

select object 
from tblTags 
where tag = @firstMatch 
and (
     @secondMatch is null 
     or 
     (object in (select object from tblTags where tag = @secondMatch) 
    ) 

Nun, das skaliert nicht sehr gut, aber es wird bekommen, was Sie suchen. Ich denke, es gibt einen besseren Weg, dies zu tun, so dass Sie leicht N Anzahl der übereinstimmenden Elemente ohne großen Einfluss auf den Code haben können, aber es entgeht mir derzeit.

0

Ich empfehle das folgende Schema.

Objects: objectID, objectName 
Tags: tagID, tagName 
ObjectTag: objectID,tagID 

Mit der folgenden Abfrage.

select distinct 
    objectName 
from 
    ObjectTab ot 
    join object o 
     on o.objectID = ot.objectID 
    join tabs t 
     on t.tagID = ot.tagID 
where 
    tagName in ('red','fruit')