Progettazione di database per tag


171

Come progetteresti un database per supportare le seguenti funzionalità di tagging:

  • gli articoli possono avere un gran numero di tag
  • le ricerche di tutti gli elementi che sono taggati con un determinato set di tag devono essere veloci (gli elementi devono avere TUTTI i tag, quindi è una ricerca AND, non una ricerca OR)
  • la creazione / scrittura di elementi potrebbe essere più lenta per consentire una rapida ricerca / lettura

Idealmente, la ricerca di tutti gli elementi che sono contrassegnati con (almeno) un set di n tag specifici dovrebbe essere eseguita utilizzando una singola istruzione SQL. Poiché il numero di tag da cercare e il numero di tag su qualsiasi elemento sono sconosciuti e possono essere elevati, l'utilizzo di JOIN non è pratico.

Qualche idea?


Grazie per tutte le risposte finora.

Se non sbaglio, tuttavia, le risposte fornite mostrano come eseguire una ricerca OR sui tag. (Seleziona tutti gli elementi che hanno uno o più di n tag). Sto cercando una ricerca AND efficiente. (Seleziona tutti gli elementi che hanno TUTTI i tag n - e possibilmente di più.)

Risposte:


22

Informazioni su ANDing: Sembra che tu stia cercando l'operazione "divisione relazionale". Questo articolo tratta la divisione relazionale in modo conciso e tuttavia comprensibile.

Informazioni sulle prestazioni: un approccio basato su bitmap sembra intuitivo che si adatti bene alla situazione. Tuttavia, non sono convinto che sia una buona idea implementare l'indicizzazione bitmap "manualmente", come suggerisce digiguru: Sembra una situazione complicata ogni volta che vengono aggiunti nuovi tag (?) Ma alcuni DBMS (incluso Oracle) offrono indici bitmap che potrebbero in qualche modo essere utile, perché un sistema di indicizzazione incorporato elimina la potenziale complessità della manutenzione dell'indice; inoltre, un DBMS che offre indici bitmap dovrebbe essere in grado di considerarli in modo corretto quando si esegue il piano di query.


4
Devo dire che la risposta è un po 'miope, perché l'uso di un tipo di campo del database ti limita a un numero specifico di bit. Ciò non significa che ogni articolo sia limitato a un certo numero di tag, ma che ci possa essere solo un certo numero di tag univoci nell'intero sistema (in genere fino a 32 o 64).
Mark Renouf,

1
Supponendo un'implementazione 3nf (Domanda, Tag, Question_has_Tag) e un indice bitmap sul Tag_id in Question_has_Tag, l'indice bitmap deve essere ricostruito ogni volta che una domanda ha un tag aggiunto o rimosso. Una query del genere 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)dovrebbe andare bene e ridimensionarsi supponendo che sul tavolo centrale esistano gli indici b-tree corretti
Adam Musch,

Il link "Questo articolo" è morto. Mi sarebbe piaciuto leggere questo :(
mpen

3
Mark: Questo sembra buono: simple-talk.com/sql/t-sql-programming/… Probabilmente è una versione ripubblicata di quella a cui mi riferivo .
Troels Arvin,

l'URL dell'articolo non è più valido
Sebastien H.

77

Ecco un buon articolo sulla codifica degli schemi di database:

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

insieme a test delle prestazioni:

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

Si noti che le conclusioni sono molto specifiche per MySQL, che (almeno nel 2005 al momento in cui è stato scritto) aveva caratteristiche di indicizzazione del testo completo molto scarse.


1
Mi piacerebbe anche avere informazioni tecniche più dettagliate su come hai implementato il sistema di tagging con SO? Penso che su un podcast hai detto di tenere tutti i tag in una colonna con ogni domanda e quindi serializzarli / deserializzarli al volo? Mi piacerebbe saperne di più e magari vedere alcuni frammenti di codice. Mi sono guardato intorno e ho trovato qualche dettaglio, c'è un link dove l'hai già fatto prima di porre la domanda su META?
Marston A.,

5
Questa domanda su Meta ha alcune informazioni sullo schema SO: meta.stackexchange.com/questions/1863/so-database-schema
Barrett

I link originali erano morti, ma penso di aver trovato la loro nuova posizione. Potresti voler verificare che questi fossero gli articoli a cui ti riferivi.
Brad Larson

12
Nonostante sia stato scritto da @Jeff, questa è essenzialmente una risposta solo a link.
curiousdannii,

13

Non vedo problemi con una soluzione semplice: tabella per elementi, tabella per tag, tabella incrociata per "tagging"

Gli indici sulla tabella incrociata dovrebbero essere sufficienti per l'ottimizzazione. La selezione di voci appropriate sarebbe

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

E la codifica sarebbe

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

che è certamente, non così efficiente per un gran numero di tag di confronto. Se si desidera mantenere il conteggio dei tag in memoria, è possibile effettuare una query per iniziare con tag che non sono frequenti, quindi la sequenza AND verrebbe valutata più rapidamente. A seconda del numero previsto di tag da abbinare e dell'aspettativa di abbinamento di uno qualsiasi di questi, questa potrebbe essere una soluzione OK, se si devono abbinare 20 tag e aspettarsi che un oggetto casuale corrisponda a 15 di essi, questo sarebbe comunque pesante su un database.


13

Volevo solo sottolineare che l'articolo a cui @Jeff Atwood collega ( http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/ ) è molto approfondito (Discute i meriti di 3 diversi schemi approcci) e ha una buona soluzione per le query AND che di solito eseguiranno meglio di quanto è stato menzionato finora (ovvero non utilizza una subquery correlata per ogni termine). Anche molte cose buone nei commenti.

ps - L'approccio di cui tutti parlano qui è indicato come la soluzione "Toxi" nell'articolo.


3
Ricordo di aver letto quel fantastico articolo, ma sfortunatamente il link è morto ora. :( Qualcuno ne conosce uno specchio?
localhost

5
il collegamento era morto: <
Aaron il

6

Potresti voler sperimentare una soluzione non strettamente di database come un'implementazione di Java Content Repository (ad esempio Apache Jackrabbit ) e utilizzare un motore di ricerca basato su quello come Apache Lucene .

Questa soluzione con gli appropriati meccanismi di memorizzazione nella cache produrrebbe probabilmente prestazioni migliori rispetto a una soluzione domestica.

Tuttavia, non credo davvero che in un'applicazione di piccole o medie dimensioni occorrerebbe un'implementazione più sofisticata rispetto al database normalizzato menzionato nei post precedenti.

EDIT: con i tuoi chiarimenti sembra più avvincente utilizzare una soluzione simile a JCR con un motore di ricerca. Ciò semplificherebbe notevolmente i programmi a lungo termine.


5

Il metodo più semplice è creare una tabella di tag .
Target_Type- nel caso in cui si taggino più tabelle
Target- La chiave del record
Tagda taggare - Il testo di un tag

Interrogare i dati sarebbe qualcosa del tipo:

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

AGGIORNAMENTO
In base al requisito di AND alle condizioni, la query sopra si trasformerebbe in qualcosa del genere

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]

1

Secondo il suggerimento di @Zizzencs, potresti voler qualcosa che non sia totalmente (R) incentrato su DB

In qualche modo, credo che l'uso di semplici campi nvarchar per archiviare quei tag con una corretta memorizzazione nella cache / indicizzazione potrebbe produrre risultati più rapidi. Ma sono solo io.

In passato ho implementato sistemi di tagging utilizzando 3 tabelle per rappresentare una relazione molti-a-molti (Item Tag ItemTags), ma suppongo che avrai a che fare con tag in molti posti, posso dirti che con 3 tabelle devi essere manipolati / interrogati contemporaneamente tutto il tempo renderà sicuramente il tuo codice più complesso.

Potresti considerare se vale la complessità aggiunta.


0

Non sarai in grado di evitare join ed essere comunque in qualche modo normalizzato.

Il mio approccio è quello di avere una tabella di tag.

 TagId (PK)| TagName (Indexed)

Quindi, hai una colonna TagXREFID nella tabella degli articoli.

Questa colonna TagXREFID è un FK per una terza tabella, lo chiamerò TagXREF:

 TagXrefID | ItemID | TagId

Quindi, ottenere tutti i tag per un elemento sarebbe qualcosa del tipo:

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

E per ottenere tutti gli articoli per un tag, userei qualcosa del genere:

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

A AND un gruppo di tag insieme, è necessario modificare leggermente l'istruzione precedente per aggiungere AND Tags.TagName = @ TagName1 AND Tag.TagName = @ TagName2 ecc ... e creare dinamicamente la query.


0

Quello che mi piace fare è avere un numero di tabelle che rappresentano i dati grezzi, quindi in questo caso avresti

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

Funziona velocemente per i tempi di scrittura e mantiene tutto normalizzato, ma puoi anche notare che per ogni tag, dovrai unire le tabelle due volte per ogni ulteriore tag che vuoi AND, quindi ha una lettura lenta.

Una soluzione per migliorare la lettura è quella di creare una tabella di cache su comando impostando una procedura memorizzata che essenzialmente crea una nuova tabella che rappresenta i dati in un formato appiattito ...

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

Quindi puoi considerare la frequenza con cui la tabella degli articoli con tag deve essere aggiornata, se si trova su ogni inserimento, quindi chiama la procedura memorizzata in un evento di inserimento cursore. Se si tratta di un'attività oraria, quindi impostare un lavoro orario per eseguirlo.

Ora per essere davvero intelligente nel recupero dei dati, ti consigliamo di creare una procedura memorizzata per ottenere dati dai tag. Anziché utilizzare query nidificate in un'enorme istruzione case, si desidera passare un singolo parametro contenente un elenco di tag che si desidera selezionare dal database e restituire un set di record di elementi. Questo sarebbe meglio in formato binario, usando operatori bit per bit.

In formato binario, è facile da spiegare. Supponiamo che ci siano quattro tag da assegnare a un oggetto, in binario potremmo rappresentarlo

0000

Se tutti e quattro i tag sono assegnati a un oggetto, l'oggetto sarebbe simile a questo ...

1111

Se solo i primi due ...

1100

Quindi è solo un caso di trovare i valori binari con gli 1 e gli zeri nella colonna desiderata. Utilizzando gli operatori Bitwise di SQL Server, è possibile verificare che sia presente un 1 nella prima delle colonne utilizzando query molto semplici.

Controlla questo link per saperne di più .


0

Per parafrasare ciò che altri hanno detto: il trucco non è nello schema , è nella query .

Lo schema ingenuo di Entità / Etichette / Tag è la strada giusta da percorrere. Ma come hai visto, non è immediatamente chiaro come eseguire una query AND con molti tag.

Il modo migliore per ottimizzare quella query dipenderà dalla piattaforma, quindi consiglierei di ricodificare la tua domanda con il tuo RDBS e di cambiare il titolo in qualcosa come "Modo ottimale per eseguire una query AND su un database di tagging".

Ho alcuni suggerimenti per MS SQL, ma mi asterrò nel caso in cui non sia la piattaforma che stai utilizzando.


6
Probabilmente non dovresti trattenerti dal dare notizie su una determinata tecnologia perché altre persone che cercano di lavorare in questo settore problematico potrebbero effettivamente utilizzare quella tecnologia e trarne vantaggio.
Bryan Rehbein,

0

Una variante alla risposta sopra è prendere gli ID tag, ordinarli, combinarli come una stringa ^ separata e li hash. Quindi associa semplicemente l'hash all'elemento. Ogni combinazione di tag produce una nuova chiave. Per eseguire una ricerca AND, è sufficiente ricreare l'hash con gli ID tag e la ricerca indicati. La modifica dei tag su un elemento comporterà la ricostruzione dell'hash. Gli elementi con lo stesso set di tag condividono la stessa chiave hash.


4
Con questo approccio puoi cercare solo voci con lo stesso identico set di tag - è sempre banale. Nella mia domanda originale voglio trovare voci che hanno tutti i tag per cui cerco, e forse di più.
Christian Berg,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.