database-design - Diseño de base de datos SQL recomendado para etiquetas o etiquetado




tags data-modeling tagging (6)

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

¿Me falta una buena práctica para las etiquetas?


Answers

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, hace que el desarrollo sea mucho más simple: en la solución de tres tablas para insertar y actualizar un item , debe buscar la tabla de Tag para ver si ya hay entradas. Entonces tienes que unirte a ellos 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 de base de datos principales que hará: generar todas las Tags para un 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 tomar más espacio en la base de datos (lo que podría llevar a más operaciones de disco, lo que es más lento) y no se normalizará, lo que podría generar inconsistencias.

El argumento de 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 tan grande. Se podría argumentar que la consulta para el título de la etiqueta es mucho más rápida en una pequeña tabla que contiene cada etiqueta solo una vez y esto ciertamente es cierto. Pero teniendo en cuenta los ahorros por no tener que unirte y el hecho de que puedes construir un buen índice en 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 es un poco discutible también. Las etiquetas son campos de texto libre y no se espera ninguna operación como 'cambiar el nombre de todas las etiquetas "foo" a "barra"'.

Así que tldr: me gustaría ir a la solución de dos mesas. (De hecho, lo haré. Encontré este artículo para ver si hay argumentos válidos en su contra).


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 artículo.

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

Ahora, mientras se buscan artículos para una etiqueta, será súper rápido.


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

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


Si está utilizando una base de datos que admite map-reduce, como couchdb, almacenar etiquetas en un campo de texto sin formato o en un campo de lista es 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 conteo de la cantidad de veces que se encontró esa etiqueta. Es muy similar a contar las ocurrencias de una palabra en un texto .


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 el conjunto de claves externas que se ejecutan en una base de datos adecuada, deberían funcionar bien y escalarse correctamente.

Table: Item
Columns: ItemID, Title, Content

Table: Tag
Columns: TagID, Title

Table: ItemTag
Columns: ItemID, TagID

Si desea eliminar los duplicados, aquí hay una manera mucho más sencilla de hacerlo que tener que encontrar filas pares / impares en una sub-selección triple:

SELECT id, name, email 
FROM users u, users u2
WHERE u.name = u2.name AND u.email = u2.email AND u.id > u2.id

Y así, para eliminar:

DELETE FROM users
WHERE id IN (
    SELECT id/*, name, email*/
    FROM users u, users u2
    WHERE u.name = u2.name AND u.email = u2.email AND u.id > u2.id
)

Mucho más fácil de leer y entender IMHO

Nota: El único problema es que tiene que ejecutar la solicitud hasta que no haya filas eliminadas, ya que solo elimina 1 de cada duplicado cada vez





sql database-design tags data-modeling tagging