Pregunta

Me preguntaba cuál es la mejor manera de implementar un sistema de etiquetas, como el que se usa en SO. Estaba pensando en esto, pero no puedo encontrar una buena solución escalable.

Estaba pensando en tener una solución básica de 3 tablas: tener una tabla tags , una tabla articles y una tabla tag_to_articles .

¿Es esta la mejor solución a este problema o existen alternativas? Usando este método, la tabla sería extremadamente grande en tiempo, y supongo que para la búsqueda no es demasiado eficiente. Por otro lado, no es tan importante que la consulta se ejecute rápidamente.

¿Fue útil?

Solución

Creo que encontrará interesante esta publicación de blog: Etiquetas: Esquemas de base de datos

  

El problema: desea tener un esquema de base de datos donde pueda etiquetar un   marcador (o una publicación de blog o lo que sea) con tantas etiquetas como desee.   Más tarde, entonces, desea ejecutar consultas para restringir los marcadores a un   Unión o intersección de las etiquetas. También quieres excluir (digamos: menos)   algunas etiquetas del resultado de búsqueda.

& # 8220; MySQLicious & # 8221; solución

En esta solución, el esquema tiene solo una tabla, está desnormalizado. Este tipo se llama & # 8220; solución MySQLicious & # 8221; porque MySQLicious importa datos del.icio.us a una tabla con esta estructura.

ingrese la descripción de la imagen aquí  ingrese la descripción de la imagen aquí

Intersección (Y) Consulta para & # 8220; search + webservice + semweb & # 8221 ;:

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

Unión (OR) Consulta para & # 8220; buscar | servicio web | semweb & # 8221 ;:

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

Menos Consulta para & # 8220; search + webservice-semweb & # 8221;

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

& # 8220; Scuttle & # 8221; solución

Scuttle organiza sus datos en dos tablas. Esa tabla & # 8220; scCategories & # 8221; es la etiqueta & # 8220; & # 8221; -table y tiene una clave externa para la marca & # 8220; & # 8221; -table.

ingrese la descripción de la imagen aquí

Intersección (Y) Consulta para & # 8220; 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

Primero, se buscan todas las combinaciones de marcadores y etiquetas, donde la etiqueta es & # 8220; bookmark & ??# 8221 ;, & # 8220; webservice & # 8221; o & # 8220; semweb & # 8221; (c.category IN ('bookmark', 'webservice', 'semweb')), entonces solo se tienen en cuenta los marcadores que tienen las tres etiquetas buscadas (HAY COUNT (b.bId) = 3).

Unión (OR) Consulta para & # 8220; bookmark | webservice | semweb & # 8221 ;: Simplemente omita la cláusula HAVING y tendrá un sindicato:

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

Menos (Exclusión) Consulta para & # 8220; bookmark + webservice-semweb & # 8221 ;, es decir: bookmark AND webservice AND NOT 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

Dejando el valor HAVING COUNT lleva a la consulta para & # 8220; bookmark | webservice-semweb & # 8221 ;.


& # 8220; Toxi & # 8221; solución

Toxi creó una estructura de tres tablas. A través de la tabla & # 8220; tagmap & # 8221; los marcadores y las etiquetas están relacionados n-to-m. Cada etiqueta se puede utilizar junto con diferentes marcadores y viceversa. Este esquema DB también es utilizado por wordpress. Las consultas son muy similares a las de & # 8220; scuttle & # 8221; solución.

ingrese la descripción de la imagen aquí

Intersección (Y) Consulta para & # 8220; bookmark + 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

Unión (OR) Consulta para & # 8220; bookmark | 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

Menos (Exclusión) Consulta para & # 8220; bookmark + webservice-semweb & # 8221 ;, es decir: bookmark AND webservice AND NOT 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

Dejando el valor HAVING COUNT lleva a la consulta para & # 8220; bookmark | webservice-semweb & # 8221 ;.

Otros consejos

No hay nada malo con tu solución de tres mesas.

Otra opción es limitar el número de etiquetas que se pueden aplicar a un artículo (como 5 en SO) y agregarlas directamente a su tabla de artículos.

La normalización de la base de datos tiene sus ventajas e inconvenientes, al igual que el cableado en una sola tabla tiene ventajas e inconvenientes.

Nada dice que no puedes hacer ambas cosas. Va contra los paradigmas de base de datos relacionales para repetir información, pero si el objetivo es el rendimiento, es posible que tenga que romper los paradigmas.

Su implementación de tres tablas propuesta funcionará para el etiquetado.

Usos de desbordamiento de pila, sin embargo, diferente implementación. Almacenan etiquetas en la columna varchar en la tabla de publicaciones en texto plano y utilizan la indexación de texto completo para obtener las publicaciones que coinciden con las etiquetas. Por ejemplo, posts.tags = " sistema de algoritmo que etiqueta las mejores prácticas " . Estoy seguro de que Jeff lo ha mencionado en alguna parte, pero no recuerdo dónde.

La solución propuesta es la mejor, si no es la única manera práctica que se me ocurre para abordar la relación de muchos a muchos entre etiquetas y artículos. Así que mi voto es para 'sí, sigue siendo el mejor'. Sin embargo, estaría interesado en cualquier alternativa.

Si su base de datos admite matrices indexables (como PostgreSQL, por ejemplo), recomendaría una solución completamente desnormalizada: almacenar etiquetas como una matriz de cadenas en la misma tabla. Si no, una tabla secundaria que asigna objetos a etiquetas es la mejor solución. Si necesita almacenar información adicional contra las etiquetas, puede usar una tabla de etiquetas separada, pero no tiene sentido introducir una segunda combinación para cada búsqueda de etiquetas.

Me gustaría sugerir MySQLicious optimizado para un mejor rendimiento. Antes de eso, los inconvenientes de la solución Toxi (tabla 3) son

Si tiene millones de preguntas y tiene 5 etiquetas en cada una, habrá 5 millones de entradas en la tabla de mapa de etiquetas. Así que primero tenemos que filtrar 10 mil entradas de mapa de etiquetas basadas en la búsqueda de etiquetas y luego filtrar nuevamente las preguntas correspondientes de esas 10 mil. Entonces, al filtrar si la identificación artical es simple numérica, entonces está bien, pero si es un tipo de UUID (32 varchar), entonces filtrar necesita una comparación más grande aunque esté indexada.

Mi solución:

Cada vez que se cree una nueva etiqueta, tenga counter ++ (base 10), y convierta ese contador en base64. Ahora cada nombre de etiqueta tendrá un ID base64. y pasar esta identificación a la interfaz de usuario junto con el nombre. De esta manera, tendrá un máximo de dos ID de caracteres hasta que tengamos 4095 etiquetas creadas en nuestro sistema. Ahora concatene estas múltiples etiquetas en cada columna de etiqueta de tabla de preguntas. Añada también el delimitador y hágalo clasificado.

Así que la mesa se ve así

 ingrese la descripción de la imagen aquí

Durante la consulta, consulte el ID en lugar del nombre de etiqueta real. Dado que es CLASIFICADO , y en la etiqueta serán más eficientes ( LIKE '% | a |% | c |% | f |% ).

Tenga en cuenta que el delimitador de espacio único no es suficiente y necesitamos un doble delimitador para diferenciar etiquetas como sql y mysql porque LIKE "% sql% " también devolverá los resultados de mysql . Debería estar LIKE "% | sql |% "

Sé que la búsqueda no está indexada, pero es posible que haya indexado en otras columnas relacionadas con el artículo como author / dateTime, de lo contrario, se obtendrá un análisis completo de la tabla.

Finalmente, con esta solución, no se requiere una unión interna donde se deben comparar millones de registros con 5 millones de registros en condición de unión.

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

Notas:

  • Esto es mejor que TOXI, ya que no pasa por muchas más: muchas tablas que dificultan la optimización.
  • Claro, mi enfoque puede ser un poco más abultado (que TOXI) debido a las etiquetas redundantes, pero eso es un pequeño porcentaje de la base de datos entera , y las mejoras de rendimiento pueden ser significativas.
  • Es altamente escalable.
  • No tiene (porque no necesita) un PK AUTO_INCREMENT sustituto. Por lo tanto, es mejor que Scuttle.
  • MySQLicious apesta porque no puede usar un índice ( LIKE con el comodín líder ; falsos resultados en las subcadenas)
  • Para MySQL, asegúrate de usar ENGINE = InnoDB para obtener efectos de 'clustering'.

Discusiones relacionadas (para MySQL):
muchos: muchos optimización de tablas de mapeo
listas ordenadas

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top