Question

Je me demandais quel était le meilleur moyen d'implémenter un système de balises, tel que celui utilisé sur SO. J'y pensais, mais je ne peux pas trouver une bonne solution évolutive.

Je pensais avoir une solution de base à 3 tables: avoir une table tags , une table articles et une table tag_to_articles .

Est-ce la meilleure solution à ce problème ou existe-t-il des alternatives? En utilisant cette méthode, la table deviendrait extrêmement volumineuse dans le temps, et pour la recherche, ce n'est pas trop efficace, je suppose. Par ailleurs, l’exécution rapide de la requête n’est pas très importante.

Était-ce utile?

La solution

Je pense que vous trouverez cet article intéressant: Tags: Schémas de base de données

  

Le problème: vous voulez avoir un schéma de base de données où vous pouvez marquer un   signet (ou un article de blog ou quoi que ce soit) avec autant de balises que vous voulez.   Ensuite, vous souhaitez exécuter des requêtes pour contraindre les signets à un   union ou intersection de tags. Vous souhaitez également exclure (par exemple: moins)   certaines balises du résultat de la recherche.

& # 8220; MySQLicious & # 8221; solution

Dans cette solution, le schéma n'a qu'une seule table, il est dénormalisé. Ce type s'appelle & # 8220; solution MySQLicious & # 8221; parce que MySQLicious importe les données del.icio.us dans une table ayant cette structure.

entrer la description de l'image ici  entrez la description de l'image ici

Intersection (AND) Requête pour & # 8220; recherche + Webservice + semweb & # 8221;:

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

Union (OR) Requête pour & # 8220; recherche | webservice | semweb & # 8221;:

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

Moins Requête pour & # 8220; recherche + webservice-semweb & # 8221;

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

& # 8220; Scuttle & # 8221; solution

Scuttle organise ses données dans deux tableaux. Cette table & # 8220; scCategories & # 8221; est la balise & # 8220; balise & possède une clé étrangère pour la balise & # 8220; bookmark & ??# 8221;.

entrer la description de l'image ici

Intersection (AND) Requête pour & bookmark + webservice + 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

D'abord, toutes les combinaisons signet-tag sont recherchées, où le tag est & # 8220; bookmark & ??# 8221 ;, & # 8220; webservice & # 8221; ou & # 8220; semweb & # 8221; (c.category IN ('bookmark', 'webservice', 'semweb')), seuls les signets ayant obtenu les trois balises recherchées sont pris en compte (HAVING COUNT (b.bId) = 3).

Union (OR) Requête pour le & n ° 8220; signet | webservice | semweb & # 8221;: Laissez simplement de côté la clause HAVING et vous aurez un syndicat:

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

Moins (exclusion) Requête pour le signet + webservice-semweb & # 8221 ;, signifiant: signet ET webservice ET 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

Si vous omettez l'option HAVING COUNT, la requête relative au & bookmark | webservice-semweb & # 8221;.

& # 8220; Toxi & # 8221; solution

Toxi a mis au point une structure à trois tables. Via le tableau tagmap & # 8221; les signets et les balises sont liés de n à m. Chaque balise peut être utilisée avec différents signets et inversement. Ce schéma de base de données est également utilisé par wordpress. Les requêtes sont quasiment identiques à celles du & # 8220; scuttle & # 8221; solution.

entrer la description de l'image ici

Intersection (AND) Requête pour le & n ° 8220; signet + 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
HAVING COUNT( b.id )=3

Union (OR) Requête pour le signet | 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

Moins (exclusion) Requête pour le signet + webservice-semweb & # 8221 ;, signifiant: signet ET webservice ET 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

Si vous omettez l'option HAVING COUNT, la requête relative au & bookmark | webservice-semweb & # 8221;.

Autres conseils

Rien de mal avec votre solution à trois tables.

Une autre option consiste à limiter le nombre de balises pouvant être appliquées à un article (comme 5 dans SO) et à les ajouter directement à la table des articles.

La normalisation de la base de données présente des avantages et des inconvénients, tout comme le câblage dans une seule table présente des avantages et des inconvénients.

Rien ne dit que vous ne pouvez pas faire les deux. Il est contraire aux paradigmes de bases de données relationnelles de répéter des informations, mais si l'objectif est la performance, vous devrez peut-être rompre avec les paradigmes.

Votre implémentation à trois tables proposée fonctionnera pour le marquage.

Le débordement de pile utilise cependant une implémentation différente. Ils stockent les balises dans la colonne varchar de la table posts en texte brut et utilisent l'indexation de texte intégral pour récupérer les publications qui correspondent aux balises. Par exemple, posts.tags = " le système algorithmique en matière de marquage des meilleures pratiques ". Je suis sûr que Jeff l’a mentionné quelque part, mais j’oublie où.

La solution proposée est la meilleure solution, si ce n’est la seule possible, à laquelle je puisse penser pour traiter la relation plusieurs à plusieurs entre les balises et les articles. Donc, mon vote est «oui, c'est toujours le meilleur». Je serais intéressé par toute alternative cependant.

Si votre base de données prend en charge les tableaux indexables (tels que PostgreSQL, par exemple), je vous recommanderais une solution entièrement dénormalisée: les balises stockées sous forme de tableau de chaînes sur la même table. Sinon, une table secondaire mappant des objets sur des balises est la meilleure solution. Si vous devez stocker des informations supplémentaires sur les balises, vous pouvez utiliser une table de balises distincte, mais il est inutile d’introduire une deuxième jointure pour chaque recherche de balise.

Je voudrais suggérer une optimisation de MySQLicious pour de meilleures performances. Auparavant, les inconvénients de la solution Toxi (3 table) sont

.

Si vous avez des millions de questions et que chacune contient 5 balises, le tableau tagmap contiendra 5 millions d'entrées. Nous devons donc d’abord filtrer 10 000 entrées de tagmap en fonction de la recherche par étiquette, puis filtrer à nouveau les questions correspondantes de ces 10 000. Donc, tout en filtrant si l'identifiant artistique est numérique simple, c'est correct, mais s'il s'agit d'une sorte d'UUID (32 varchar), le filtrage nécessite une comparaison plus importante bien qu'il soit indexé.

Ma solution:

Chaque fois qu'une nouvelle balise est créée, prenez le compteur ++ (base 10) et convertissez ce compteur en base64. Maintenant, chaque nom de balise aura un identifiant base64. et passez cet identifiant à l'interface utilisateur avec nom. De cette façon, vous aurez un maximum de deux caractères jusqu'à ce que 4095 tags soient créés dans notre système. Concaténez maintenant ces multiples balises dans chaque colonne de balises du tableau de questions. Ajoutez également un délimiteur et triez-le.

Donc, la table ressemble à ceci

 entrer la description de l'image ici

Lors de l'interrogation, interrogez sur l'id au lieu du vrai nom de la balise. Comme il s'agit de SORTED , les conditions et sur la balise seront plus efficaces ( LIKE '% | a |% | c |% | f |% ).

Notez que le séparateur d'espace simple n'est pas suffisant et que nous avons besoin d'un double séparateur pour différencier les balises telles que sql et mysql car LIKE "% sql% " <. / code> renverra également les résultats mysql . Doit être LIKE "% | sql |% "

Je sais que la recherche n'est pas indexée, mais vous avez peut-être indexé sur d'autres colonnes liées à un article tel que author / dateTime, sinon cela mènera à une analyse complète du tableau.

Enfin, avec cette solution, aucune jointure interne n'est requise, où il faut comparer des millions d'enregistrements avec 5 millions d'enregistrements conditionnés.

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

Notes:

  • C’est mieux que TOXI en ce sens qu’il ne passe pas très longtemps: beaucoup de tables, ce qui rend l’optimisation difficile.
  • Bien sûr, mon approche peut être légèrement plus volumineuse (que TOXI) en raison des balises redondantes, mais il s’agit d’un faible pourcentage de la base de données entière et les améliorations des performances peuvent être significatives.
  • Il est hautement évolutif.
  • Il n’a pas (car il n’a pas besoin de) de clé de substitution AUTO_INCREMENT . Par conséquent, il vaut mieux que Scuttle.
  • MySQLicious craint car il ne peut pas utiliser d’index ( LIKE avec un caractère générique ; faux résultats sur les sous-chaînes)
  • Pour MySQL, assurez-vous d'utiliser ENGINE = InnoDB afin d'obtenir des effets de "clustering".

Discussions associées (pour MySQL):
plusieurs: optimisation de nombreuses tables de mappage
listes ordonnées

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top