Conception de base de données SQL recommandée pour les balises ou le balisage [fermé]

288

J'ai entendu parler de quelques façons d'implémenter le balisage; utiliser une table de mappage entre TagID et ItemID (cela a du sens pour moi, mais est-ce que ça évolue?), ajouter un nombre fixe de colonnes TagID possibles à ItemID (cela semble être une mauvaise idée), Conserver les balises dans une colonne de texte séparée par des virgules (sons fou mais pourrait fonctionner). J'ai même entendu quelqu'un recommander une matrice clairsemée, mais comment les noms de balises grandissent-ils avec élégance?

Suis-je en train de manquer une meilleure pratique pour les tags?

dlamblin
la source
9
D'accord, c'est la question # 20856, la (presque) même question est # 48475 posée au moins deux semaines après que cette question a été posée.
dlamblin
9
Une autre question intéressante est "Comment SO implémente les balises?"
Mostafa
1
Une autre question intéressante est "Voulez-vous les internationaliser, et si oui, comment?"
DanMan
1
Comparaison intéressante (spécifique à Postgres): databasesoup.com/2015/01/tag-all-things.html
a_horse_with_no_name

Réponses:

406

Trois tables (une pour stocker tous les éléments, une pour toutes les balises et une pour la relation entre les deux), correctement indexées, avec des clés étrangères définies s'exécutant sur une base de données appropriée, devraient fonctionner correctement et évoluer correctement.

Table: Item
Columns: ItemID, Title, Content

Table: Tag
Columns: TagID, Title

Table: ItemTag
Columns: ItemID, TagID
Yaakov Ellis
la source
32
C'est ce qu'on appelle la solution «Toxi», vous pouvez trouver des informations supplémentaires à ce sujet ici: howto.philippkeller.com/2005/04/24/Tags-Database-schemas
The Pixel Developer
16
Les "balises" ou catégories hiérarchiques du tableau des balises ne sont pas représentées ici. Cela est généralement nécessaire sur les sites qui ont des catégories et des sous-catégories mais qui ont besoin de la flexibilité du balisage. Par exemple, les sites de recettes, les sites de pièces automobiles, les répertoires d'entreprises, etc. Ces types de données ne rentrent généralement pas dans une seule catégorie, le marquage est la réponse, mais vous devez utiliser quelque chose comme le modèle d'ensemble imbriqué ou le modèle de liste d'adjacence dans votre table de balises.
HK1
5
Je suis d'accord avec HK1 est-il possible avec la structure ci-dessus + Table: Colonnes TagGroup: TagGropuId, Table de titre: Colonnes Tag: TagID, Title, TagGroupId
Thunder
quand je veux ajouter la colonne css à la table, j'ajouterai la colonne css à la table des balises?
Amitābha
10
@ftvs: lien à nouveau rompu, le nouveau lien est howto.philippkeller.com/2005/04/24/Tags-Database-schemas
hansaplast
83

Normalement, je serais d'accord avec Yaakov Ellis, mais dans ce cas particulier, il existe une autre solution viable:

Utilisez deux tableaux:

Table: Item
Columns: ItemID, Title, Content
Indexes: ItemID

Table: Tag
Columns: ItemID, Title
Indexes: ItemId, Title

Cela présente des avantages majeurs:

Tout d'abord, cela rend le développement beaucoup plus simple: dans la solution à trois tables pour l'insertion et la mise à jour, itemvous devez rechercher la Tagtable pour voir s'il y a déjà des entrées. Ensuite, vous devez les rejoindre avec de nouveaux. Ce n'est pas une tâche triviale.

Ensuite, cela rend les requêtes plus simples (et peut-être plus rapides). Il y a trois requêtes de base de données principales que vous allez faire: Tout générer Tagspour un Item, dessiner un nuage de tags et sélectionner tous les éléments pour un titre de tag.

Toutes les étiquettes pour un article:

3 tables:

SELECT Tag.Title 
  FROM Tag 
  JOIN ItemTag ON Tag.TagID = ItemTag.TagID
 WHERE ItemTag.ItemID = :id

2 tables:

SELECT Tag.Title
FROM Tag
WHERE Tag.ItemID = :id

Tag-Cloud:

3 tables:

SELECT Tag.Title, count(*)
  FROM Tag
  JOIN ItemTag ON Tag.TagID = ItemTag.TagID
 GROUP BY Tag.Title

2 tables:

SELECT Tag.Title, count(*)
  FROM Tag
 GROUP BY Tag.Title

Articles pour une étiquette:

3 tables:

SELECT Item.*
  FROM Item
  JOIN ItemTag ON Item.ItemID = ItemTag.ItemID
  JOIN Tag ON ItemTag.TagID = Tag.TagID
 WHERE Tag.Title = :title

2 tables:

SELECT Item.*
  FROM Item
  JOIN Tag ON Item.ItemID = Tag.ItemID
 WHERE Tag.Title = :title

Mais il y a aussi quelques inconvénients: cela pourrait prendre plus d'espace dans la base de données (ce qui pourrait entraîner plus d'opérations sur le disque, ce qui est plus lent) et il n'est pas normalisé, ce qui pourrait entraîner des incohérences.

L'argument de la taille n'est pas si fort car la nature même des balises est qu'elles sont normalement assez petites, donc l'augmentation de la taille n'est pas grande. On pourrait faire valoir que la requête pour le titre de la balise est beaucoup plus rapide dans un petit tableau qui ne contient chaque balise qu'une seule fois et c'est certainement vrai. Mais compte tenu des économies réalisées pour ne pas avoir à adhérer et du fait que vous pouvez construire un bon indice sur elles, cela pourrait facilement compenser cela. Cela dépend bien sûr fortement de la taille de la base de données que vous utilisez.

L'argument de l'incohérence est également un peu théorique. Les balises sont des champs de texte libres et il n'y a aucune opération attendue comme 'renommer toutes les balises "foo" en "bar"'.

Donc tldr: Je choisirais la solution à deux tables. (En fait, je vais le faire. J'ai trouvé cet article pour voir s'il existe des arguments valables contre.)

Scheintod
la source
"Index: ItemId, Title" signifie-t-il un index pour chacun ou un index contenant les deux?
DanMan
Normalement deux index. Cela peut cependant dépendre de la base de données que vous utilisez.
Scheintod
1
Dans la table des balises, ItemId et Tag sont-ils une clé composite? ou avez-vous aussi un PK?
Rippo
2
de cette façon, vous ne pouvez pas créer de balises "inutilisées", donc une fonction "ajouter une balise" doit être effectuée sur un élément. Sur l'autre méthode, la fonction "ajouter une balise" peut être effectuée indépendamment
Gianluca Ghettini
1
@Quilang. Je crois toujours que cela dépend de ce que vous faites :) Je l'ai implémenté dans les deux sens dans différents projets. Dans ma dernière, je me suis retrouvé avec une solution à 3 tables car j'avais besoin d'un "type de balise" (ou d'autres métadonnées sur la balise) et je pouvais réutiliser du code provenant d'un proche cousin de balises: paramètres. Mais dans le même projet, j'ai utilisé exactement cette méthode pour un cousin encore plus proche: drapeaux (par exemple, «vendu», «nouveau», «chaud»)
Scheintod
38

Si vous utilisez une base de données qui prend en charge la réduction de carte, comme couchdb, le stockage de balises dans un champ de texte brut ou un champ de liste est en effet le meilleur moyen. Exemple:

tagcloud: {
  map: function(doc){ 
    for(tag in doc.tags){ 
      emit(doc.tags[tag],1) 
    }
  }
  reduce: function(keys,values){
    return values.length
  }
}

L'exécution de ce paramètre avec group = true regroupera les résultats par nom de balise et renverra même un décompte du nombre de fois où cette balise a été rencontrée. C'est très similaire à compter les occurrences d'un mot dans le texte .

Nick Retallack
la source
4
+1 Ravi de voir également certaines implémentations NoSQL.
Xeoncross
@NickRetallack Le lien ne fonctionne pas. Si vous le pouvez, veuillez mettre à jour cette réponse.
xralf
Ok j'ai remplacé le lien par un sur archive.org
Nick Retallack
13

Utilisez une seule colonne de texte formaté [1] pour stocker les balises et utilisez un moteur de recherche de texte intégral capable de l'indexer. Sinon, vous rencontrerez des problèmes de mise à l'échelle lorsque vous essayez d'implémenter des requêtes booléennes.

Si vous avez besoin de détails sur les balises dont vous disposez, vous pouvez soit en assurer le suivi dans un tableau géré de manière incrémentielle, soit exécuter un travail par lots pour extraire les informations.

[1] Certains SGBDR fournissent même un type de tableau natif qui pourrait être encore mieux adapté au stockage en n'ayant pas besoin d'une étape d'analyse, mais pourrait causer des problèmes avec la recherche en texte intégral.

David Schmitt
la source
Connaissez-vous un moteur de recherche en texte intégral qui ne trouve pas de variations sur un mot? Par exemple, la recherche de livre renvoie des livres? De plus, que faites-vous des balises comme "c ++"? SQL Server, par exemple, supprimerait les signes plus dans l'index. Merci.
Jonathan Wood
Essayez Sphinx - sphinxsearch.com
Roman
Ce tutoriel en 3 parties peut être utile pour ceux qui empruntent cette voie (recherche plein texte). Il utilise PostgreSQL installations natives: shisaa.jp/postset/postgresql-full-text-search-part-1.html
Will
est-ce mieux que la réponse choisie en termes de performances?
que diriez-vous de stocker en utilisant varchar 255, des balises séparées par des virgules et d'ajouter un index de texte kfull dessus?
9

J'ai toujours conservé les balises dans une table distincte, puis j'ai eu une table de mappage. Bien sûr, je n'ai jamais rien fait à très grande échelle non plus.

Avoir une table de «balises» et une table de carte rend la génération de nuages ​​de balises assez simple, car vous pouvez facilement assembler SQL pour obtenir une liste de balises avec un décompte de la fréquence d'utilisation de chaque balise.

Mark Biek
la source
6
C'est encore plus facile si vous n'utilisez pas de table de mappage :)
Scheintod
0

Je suggérerais la conception suivante: Tableau des éléments: Itemid, taglist1, taglist2
ce sera rapide et facilitera la sauvegarde et la récupération des données au niveau de l'élément.

En parallèle, créez une autre table: les balises balises ne font pas de balise un identifiant unique et si vous manquez d'espace dans la 2ème colonne qui contient disons 100 éléments, créez une autre ligne.

Maintenant, tout en recherchant des éléments pour une balise, ce sera super rapide.

user236575
la source
en.wikipedia.org/wiki/First_normal_form bien qu'il y ait des exceptions à cela, vous pouvez dénormaliser, mais pas ici
Dheeraj