Diseño de base de datos SQL recomendado para etiquetas o etiquetado [cerrado]

288

He oído hablar de algunas formas de implementar el etiquetado; usando una tabla de mapeo entre TagID y ItemID (tiene sentido para mí, pero ¿escala?), agregando un número fijo de posibles columnas de TagID a ItemID (parece una mala idea), manteniendo las etiquetas en una columna de texto que está separada por comas (sonidos loco pero podría funcionar). Incluso he oído que alguien recomienda una matriz dispersa, pero ¿cómo crecen con gracia los nombres de las etiquetas?

¿Me estoy perdiendo una práctica recomendada para las etiquetas?

dlamblin
fuente
99
De acuerdo, esta es la pregunta # 20856, la (casi) misma pregunta es la # 48475 formulada al menos dos semanas después de esta pregunta.
dlamblin
99
Otra pregunta interesante es "¿Cómo implementa SO las etiquetas?"
Mostafa
1
Otra pregunta interesante es "¿Los internacionalizaría? Y si es así, ¿cómo?"
DanMan
1
Comparación interesante (específica de Postgres): bases de
datosoup.com/2015/01/tag-all-things.html

Respuestas:

406

Tres tablas (una para almacenar todos los elementos, una para todas las etiquetas y otra para la relación entre las dos), correctamente indexadas, con claves externas establecidas en una base de datos adecuada, deberían funcionar bien y escalar correctamente.

Table: Item
Columns: ItemID, Title, Content

Table: Tag
Columns: TagID, Title

Table: ItemTag
Columns: ItemID, TagID
Yaakov Ellis
fuente
32
Esto se conoce como la solución "Toxi", puede encontrar información adicional al respecto aquí: howto.philippkeller.com/2005/04/24/Tags-Database-schemas
The Pixel Developer
16
Una cosa que no se muestra aquí son las "etiquetas" jerárquicas o categorías en la tabla de etiquetas. Esto se necesita comúnmente en sitios que tienen categorías y subcategorías pero necesitan la flexibilidad de etiquetado. Por ejemplo, sitios de recetas, sitios de autopartes, directorios de empresas, etc. Estos tipos de datos generalmente no encajan en una sola categoría, por lo que el etiquetado es la respuesta, pero debe usar algo como el Modelo de conjunto anidado o el Modelo de lista de adyacencia en tu tabla de etiquetas.
HK1
55
Me agrree con HK1 es posible con la estructura anterior + Tabla: Columnas TagGroup: TagGropuId, título de la tabla: Columnas de la etiqueta: TagID, Título, TagGroupId
trueno
cuando quiero agregar una columna css a la tabla, ¿agregaré una columna css a la tabla de etiquetas?
Amitābha
10
@ftvs: enlace nuevamente roto, el nuevo enlace es howto.philippkeller.com/2005/04/24/Tags-Database-schemas
hansaplast
83

Normalmente estaría de acuerdo con Yaakov Ellis, pero en este caso especial hay otra solución viable:

Use dos tablas:

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

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

Esto tiene algunas ventajas importantes:

Primero, hace que el desarrollo sea mucho más simple: en la solución de tres tablas para insertar y actualizar item, debe buscar elTag tabla para ver si ya hay entradas. Entonces tienes que unirlos con otros nuevos. Esta no es una tarea trivial.

Luego hace que las consultas sean más simples (y quizás más rápidas). Hay tres consultas principales en la base de datos que hará: generar todas Tagspara una Item, dibujar una nube de etiquetas y seleccionar todos los elementos para un título de etiqueta.

Todas las etiquetas para un artículo:

3-mesa:

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

2-mesa:

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

Nube de etiquetas:

3-mesa:

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

2-mesa:

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

Artículos para una etiqueta:

3-mesa:

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

2-mesa:

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

Pero también hay algunos inconvenientes: podría tomar más espacio en la base de datos (lo que podría conducir a más operaciones de disco, lo que es más lento) y no está normalizado, lo que podría generar inconsistencias.

El argumento del tamaño no es tan fuerte porque la naturaleza misma de las etiquetas es que normalmente son bastante pequeñas, por lo que el aumento de tamaño no es grande. Se podría argumentar que la consulta del título de la etiqueta es mucho más rápida en una tabla pequeña que contiene cada etiqueta solo una vez y esto ciertamente es cierto. Pero teniendo en cuenta los ahorros por no tener que unirse y el hecho de que puede construir un buen índice sobre ellos podría compensarlo fácilmente. Por supuesto, esto depende en gran medida del tamaño de la base de datos que está utilizando.

El argumento de inconsistencia también es un poco discutible. Las etiquetas son campos de texto libre y no se espera ninguna operación como 'cambiar el nombre de todas las etiquetas "foo" a "bar"'.

Entonces tldr: Yo elegiría la solución de dos tablas. (De hecho, voy a hacerlo. Encontré este artículo para ver si hay argumentos válidos en su contra).

Scheintod
fuente
¿"Index: ItemId, Title" significa un índice para cada uno o un índice que contiene ambos?
DanMan
Normalmente dos índices. Sin embargo, podría depender de la base de datos que esté utilizando.
Scheintod
1
En la tabla de etiquetas, ¿ItemId y Tag son una clave compuesta? o tienes un PK también?
Rippo
2
de esta manera no puede crear etiquetas "no utilizadas", por lo que se debe realizar una función "agregar etiqueta" en un elemento. En el otro método, la función "agregar etiqueta" se puede realizar de forma independiente
Gianluca Ghettini
1
@Quilang. Todavía creo que depende de lo que estés haciendo :) Lo implementé de ambas maneras en diferentes proyectos. En mi último, terminé con una solución de 3 tablas porque necesitaba un "tipo de etiqueta" (u otra información meta en la etiqueta) y podía reutilizar algún código de un primo cercano de etiquetas: parámetros. Pero en el mismo proyecto usé exactamente este método para un primo aún más cercano: banderas (por ejemplo, 'vendido', 'nuevo', 'caliente')
Scheintod
38

Si está utilizando una base de datos que admite la reducción de mapas, como couchdb, almacenar etiquetas en un campo de texto sin formato o en un campo de lista es, de hecho, la mejor manera. Ejemplo:

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

Ejecutar esto con group = true agrupará los resultados por nombre de etiqueta e incluso devolverá un recuento del número de veces que se encontró esa etiqueta. Es muy similar a contar las ocurrencias de una palabra en el texto .

Nick Retallack
fuente
44
+1 Es bueno ver algunas implementaciones de NoSQL también.
Xeoncross
@NickRetallack El enlace no funciona. Si pudiera, actualice esta respuesta.
xralf
Ok, reemplacé el enlace con uno para archive.org
Nick Retallack
13

Use una sola columna de texto formateado [1] para almacenar las etiquetas y use un motor de búsqueda de texto completo capaz de indexar esto. De lo contrario, se encontrará con problemas de escala al intentar implementar consultas booleanas.

Si necesita detalles sobre las etiquetas que tiene, puede realizar un seguimiento en una tabla mantenida de forma incremental o ejecutar un trabajo por lotes para extraer la información.

[1] Algunos RDBMS incluso proporcionan un tipo de matriz nativa que podría ser aún más adecuada para el almacenamiento al no necesitar un paso de análisis, pero puede causar problemas con la búsqueda de texto completo.

David Schmitt
fuente
¿Conoces algún motor de búsqueda de texto completo que no encuentre variaciones en una palabra? Por ejemplo, ¿buscar libros devuelve libros? Además, ¿qué haces con etiquetas como "c ++"? SQL Server, por ejemplo, eliminaría los signos más en el índice. Gracias.
Jonathan Wood
Prueba Sphinx - sphinxsearch.com
Roman
Este tutorial de 3 partes puede ser útil para aquellos que van por esta ruta (búsqueda de texto completo). Está utilizando las instalaciones nativas de PostgreSQL: shisaa.jp/postset/postgresql-full-text-search-part-1.html
Será
¿Es esto mejor que la respuesta seleccionada en términos de rendimiento?
¿qué tal almacenar en el uso de varchar 255, etiquetas separadas por comas y agregar índice de texto kfull en él?
9

Siempre mantuve las etiquetas en una tabla separada y luego tuve una tabla de mapeo. Por supuesto, nunca he hecho nada a gran escala tampoco.

Tener una tabla de "etiquetas" y una tabla de mapas hace que sea bastante trivial generar nubes de etiquetas y demás, ya que puede armar fácilmente SQL para obtener una lista de etiquetas con recuentos de la frecuencia con la que se usa cada etiqueta.

Mark Biek
fuente
66
Esto es aún más fácil si no usa una tabla de mapeo :)
Scheintod
0

Sugeriría el siguiente diseño: Tabla de elementos: Itemid, taglist1, taglist2
esto será rápido y facilitará guardar y recuperar los datos a nivel de elemento.

Paralelamente, cree otra tabla: las etiquetas de etiqueta no hacen que la etiqueta sea un identificador único y si se queda sin espacio en la segunda columna que contiene, digamos 100 elementos, cree otra fila.

Ahora, mientras busca elementos para una etiqueta, será súper rápido.

user236575
fuente
en.wikipedia.org/wiki/First_normal_form aunque hay excepciones a esto, puedes desnormalizar, pero no aquí
Dheeraj