Diseño de base de datos SQL recomendado para etiquetas o etiquetado [cerrado]
He oído hablar de algunas formas de implementar el etiquetado; usar una tabla de mapeo entre TagID y ItemID (tiene sentido para mí, pero ¿se escala?), agregar un número fijo de posibles columnas TagID a ItemID (parece una mala idea), mantener etiquetas en una columna de texto separada por comas (suena loco pero podría funcionar). Incluso escuché a alguien recomendar una matriz dispersa, pero entonces, ¿cómo crecen con gracia los nombres de las etiquetas?
¿Me estoy perdiendo una de las mejores prácticas para las etiquetas?
Tres tablas (una para almacenar todos los elementos, una para todas las etiquetas y otra para la relación entre las dos), indexadas correctamente, con claves externas configuradas ejecutándose en una base de datos adecuada, deberían funcionar bien y escalar adecuadamente.
Table: Item
Columns: ItemID, Title, Content
Table: Tag
Columns: TagID, Title
Table: ItemTag
Columns: ItemID, TagID
Normalmente estaría de acuerdo con Yaakov Ellis pero en este caso especial hay otra solución viable:
Utilice dos tablas:
Table: Item
Columns: ItemID, Title, Content
Indexes: ItemID
Table: Tag
Columns: ItemID, Title
Indexes: ItemId, Title
Esto tiene algunas ventajas importantes:
Primero, simplifica mucho el desarrollo: en la solución de tres tablas para insertar y actualizar, item
debe buscar la Tag
tabla para ver si ya hay entradas. Luego hay que unirlos con otros nuevos. Ésta no es una tarea trivial.
Luego simplifica las consultas (y quizás más rápido). Hay tres consultas principales a la base de datos que deberá realizar: generar todo Tags
para uno 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-Tabla:
SELECT Tag.Title
FROM Tag
JOIN ItemTag ON Tag.TagID = ItemTag.TagID
WHERE ItemTag.ItemID = :id
2-Tabla:
SELECT Tag.Title
FROM Tag
WHERE Tag.ItemID = :id
Nube de etiquetas:
3-Tabla:
SELECT Tag.Title, count(*)
FROM Tag
JOIN ItemTag ON Tag.TagID = ItemTag.TagID
GROUP BY Tag.Title
2-Tabla:
SELECT Tag.Title, count(*)
FROM Tag
GROUP BY Tag.Title
Artículos para una etiqueta:
3-Tabla:
SELECT Item.*
FROM Item
JOIN ItemTag ON Item.ItemID = ItemTag.ItemID
JOIN Tag ON ItemTag.TagID = Tag.TagID
WHERE Tag.Title = :title
2-Tabla:
SELECT Item.*
FROM Item
JOIN Tag ON Item.ItemID = Tag.ItemID
WHERE Tag.Title = :title
Pero también hay algunos inconvenientes: podría ocupar más espacio en la base de datos (lo que podría generar más operaciones en el 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 que supone no tener que inscribirse y el hecho de que se puede crear un buen índice sobre ellos, esto podría compensarse fácilmente. Por supuesto, esto depende en gran medida del tamaño de la base de datos que esté utilizando.
El argumento de la 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: optaría por la solución de dos tablas. (De hecho, lo haré. Encontré este artículo para ver si hay argumentos válidos en contra).
Si está utilizando una base de datos que admite 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 apariciones de una palabra en un texto .
Utilice una única columna de texto formateado[1] para almacenar las etiquetas y utilice un motor de búsqueda de texto completo capaz para indexarlas. 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 nativo que podría ser incluso más adecuado para el almacenamiento al no necesitar un paso de análisis, pero podría causar problemas con la búsqueda de texto completo.
Siempre mantuve las etiquetas en una tabla separada y luego tuve una tabla de mapeo. Por supuesto, tampoco he hecho nunca nada a gran escala.
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 crear SQL fácilmente para obtener una lista de etiquetas con recuentos de la frecuencia con la que se usa cada etiqueta.