Domanda

Mi chiedevo quale fosse il modo migliore per implementare un sistema di tag, come quello usato su SO. Ci stavo pensando ma non riesco a trovare una buona soluzione scalabile.

Stavo pensando di avere una soluzione base di 3 tabelle: avere una tabella tag , una tabella articoli e una tabella tag_to_articles .

È la soluzione migliore a questo problema o ci sono alternative? Usando questo metodo la tabella diventerebbe estremamente grande nel tempo, e per la ricerca di questo non è troppo efficiente presumo. D'altra parte non è così importante che la query venga eseguita rapidamente.

È stato utile?

Soluzione

Credo che troverai interessante questo post del blog: Tag: Schemi di database

  

Il problema: vuoi avere uno schema di database in cui puoi taggare a   segnalibro (o un post di blog o altro) con tutti i tag che desideri.   Successivamente, si desidera eseguire query per vincolare i segnalibri a   unione o intersezione di tag. Volete anche escludere (diciamo: meno)   alcuni tag dal risultato della ricerca.

& # 8220; MySQLicious & # 8221; Soluzione

In questa soluzione, lo schema ha solo una tabella, è denormalizzato. Questo tipo è chiamato & # 8220; soluzione MySQLicious & # 8221; perché MySQLicious importa i dati del.icio.us in una tabella con questa struttura.

inserisci qui la descrizione dell'immagine  inserisci qui la descrizione dell'immagine

Intersezione (AND) Richiesta per & # 8220; ricerca + servizio web + semweb & # 8221 ;:

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

Unione (OR) Richiedi & # 8220; cerca | webservice | semweb & # 8221 ;:

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

Minus Richiesta per & # 8220; ricerca + webservice-semweb & # 8221;

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

& # 8220; Scuttle & # 8221; Soluzione

Scuttle organizza i suoi dati in due tabelle. Quella tabella & # 8220; scCategories & # 8221; è il tag & # 8220; & # 8221; -table e ha una chiave esterna per & # 8220; segnalibro & # 8221; -table.

inserisci qui la descrizione dell'immagine

Intersezione (AND) Richiesta per & # 8220; segnalibro + servizio web + semweb & # 8221 ;:

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

Per prima cosa, vengono cercate tutte le combinazioni di tag di segnalibri, dove il tag è & # 8220; segnalibro & # 8221 ;, & # 8220; servizio web & # 8221; o & # 8220; semweb & # 8221; (c.category IN ('bookmark', 'webservice', 'semweb')), quindi vengono presi in considerazione solo i segnalibri per i quali sono stati cercati tutti e tre i tag (HUNT COUNT (b.bId) = 3).

Unione (OR) Richiesta per & # 8220; segnalibro | webservice | semweb & # 8221 ;: Basta lasciare fuori la clausola HAVING e hai l'unione:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

Meno (esclusione) Richiesta per & # 8220; segnalibro + webservice-semweb & # 8221 ;, ovvero: segnalibro E servizio web E NON 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

Lasciando fuori il CONTO IN ESAME, viene visualizzata la query per & # 8220; segnalibro | webservice-semweb & # 8221 ;.


& # 8220; Toxi & # 8221; Soluzione

Toxi ha creato una struttura a tre tabelle. Tramite la tabella & # 8220; tagmap & # 8221; i segnalibri e i tag sono correlati da n a m. Ogni tag può essere utilizzato insieme a segnalibri diversi e viceversa. Questo schema DB è utilizzato anche da wordpress. Le query sono quasi le stesse di & # 8220; scuttle & # 8221; soluzione.

inserisci qui la descrizione dell'immagine

Intersezione (AND) Richiesta per & # 8220; segnalibro + servizio web + semweb & # 8221;

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

Unione (OR) Richiesta per & # 8220; segnalibro | webservice | semweb & # 8221;

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

Meno (esclusione) Richiesta per & # 8220; segnalibro + webservice-semweb & # 8221 ;, ovvero: segnalibro E servizio web E NON 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

Lasciando fuori il CONTO IN ESAME, viene visualizzata la query per & # 8220; segnalibro | webservice-semweb & # 8221 ;.

Altri suggerimenti

Nulla di sbagliato con la tua soluzione a tre tavoli.

Un'altra opzione è limitare il numero di tag che possono essere applicati a un articolo (come 5 in SO) e aggiungerli direttamente alla tabella degli articoli.

La normalizzazione del DB ha i suoi vantaggi e svantaggi, proprio come il cablaggio in una tabella presenta vantaggi e svantaggi.

Nulla dice che non puoi fare entrambe le cose. Va contro i paradigmi di DB relazionali per ripetere le informazioni, ma se l'obiettivo è la prestazione potresti dover rompere i paradigmi.

L'implementazione delle tre tabelle proposta funzionerà per la codifica.

Lo overflow dello stack utilizza tuttavia un'implementazione diversa. Memorizzano i tag nella colonna varchar nella tabella dei post in testo normale e utilizzano l'indicizzazione di testo completo per recuperare i post che corrispondono ai tag. Ad esempio posts.tags = " sistema di algoritmi che codifica le migliori pratiche " . Sono sicuro che Jeff lo abbia menzionato da qualche parte, ma dimentico dove.

La soluzione proposta è la migliore, se non l'unica possibile, a cui riesco a pensare per affrontare la relazione molti-a-molti tra tag e articoli. Quindi il mio voto è per "sì, è ancora il migliore". Sarei interessato a qualsiasi alternativa però.

Se il tuo database supporta array indicizzabili (come PostgreSQL, ad esempio), consiglierei una soluzione completamente denormalizzata: archivia i tag come un array di stringhe sulla stessa tabella. In caso contrario, una tabella secondaria che associa gli oggetti ai tag è la soluzione migliore. Se è necessario archiviare informazioni aggiuntive rispetto ai tag, è possibile utilizzare una tabella di tag separata, ma non ha senso introdurre un secondo join per ogni ricerca di tag.

Vorrei suggerire MySQLicious ottimizzato per prestazioni migliori. Prima di ciò, gli svantaggi della soluzione Toxi (3 tavoli) è

Se hai milioni di domande e ha 5 tag in ognuna, allora ci saranno 5 milioni di voci nella tabella tagmap. Quindi, prima dobbiamo filtrare 10 mila voci di tagmap in base alla ricerca di tag, quindi filtrare di nuovo le domande corrispondenti di quei 10 mila. Quindi, filtrando se l'id artistico è un semplice numero numerico, allora è ok, ma se è una specie di UUID (32 varchar), il filtraggio richiede un confronto più ampio sebbene sia indicizzato.

La mia soluzione:

Ogni volta che viene creato un nuovo tag, avere counter ++ (base 10) e convertire quel contatore in base64. Ora ogni nome di tag avrà un ID base64. e passa questo ID all'interfaccia utente insieme al nome. In questo modo avrai un massimo di due caratteri fino a quando non saranno creati 4095 tag nel nostro sistema. Ora concatena questi tag multipli in ogni colonna di tag della tabella delle domande. Aggiungi delimitatore e rendilo ordinato.

Quindi la tabella è simile a questa

 inserisci qui la descrizione dell'immagine

Durante l'interrogazione, esegui una query sull'ID anziché sul nome del tag reale. Poiché è ORDINATO , le condizioni e sul tag saranno più efficienti ( LIKE '% | a |% | c |% | f |% ).

Nota che il delimitatore di spazio singolo non è sufficiente e abbiamo bisogno del doppio delimitatore per differenziare tag come sql e mysql perché LIKE "% sql% " restituirà anche i risultati mysql . Dovrebbe essere LIKE "% | sql |% "

So che la ricerca non è indicizzata, ma potresti aver indicizzato su altre colonne relative ad articoli come autore / dataTempo altrimenti porterà alla scansione completa della tabella.

Infine, con questa soluzione, non è richiesto alcun join interno in cui milioni di record devono essere confrontati con 5 milioni di record in condizioni di join.

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

Note:

  • Questo è meglio di TOXI in quanto non ne passa molti di più: molte tabelle che rendono difficile l'ottimizzazione.
  • Certo, il mio approccio potrebbe essere leggermente più voluminoso (rispetto a TOXI) a causa dei tag ridondanti, ma questa è una piccola percentuale del database intero e i miglioramenti delle prestazioni potrebbero essere significativi.
  • È altamente scalabile.
  • Non ha (perché non ha bisogno) un surrogato AUTO_INCREMENT PK. Quindi, è meglio di Scuttle.
  • MySQLicious fa schifo perché non può usare un indice ( LIKE con carattere iniziale ; hit falsi su sottostringhe)
  • Per MySQL, assicurati di utilizzare ENGINE = InnoDB per ottenere effetti di "clustering".

Discussioni correlate (per MySQL):
molti: molte ottimizzazioni delle tabelle di mappatura
elenchi ordinati

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top