Coincidencia SQL de muchos a muchos
-
09-06-2019 - |
Pregunta
Estoy implementando un sistema de etiquetado para un sitio web.Hay varias etiquetas por objeto y varios objetos por etiqueta.Esto se logra manteniendo una tabla con dos valores por registro, uno para los identificadores del objeto y la etiqueta.
Estoy buscando escribir una consulta para encontrar los objetos que coincidan con un conjunto determinado de etiquetas.Supongamos que tengo los siguientes datos (en formato [objeto] -> [etiquetas]*)
apple -> fruit red food
banana -> fruit yellow food
cheese -> yellow food
firetruck -> vehicle red
Si quiero hacer coincidir (rojo), debería obtener manzana y camión de bomberos.Si quiero combinar (fruta, comida), debo obtener (manzana, plátano).
¿Cómo escribo una consulta SQL y hago lo que quiero?
@Jeremy Ruten,
Gracias por tu respuesta.La notación utilizada se utilizó para proporcionar algunos datos de muestra: mi base de datos tiene una tabla con 1 ID de objeto y 1 etiqueta por registro.
En segundo lugar, mi problema es que necesito obtener todos los objetos que coincidan con todas las etiquetas.Sustituyendo tu OR por un AND así:
SELECT object WHERE tag = 'fruit' AND tag = 'food';
No produce resultados cuando se ejecuta.
Solución
Dado:
- tabla de objetos (id de clave principal)
- tabla de etiquetas de objetos (claves externas objectId, tagid)
tabla de etiquetas (identificación de clave principal)
SELECT distinct o.* from object o join objecttags ot on o.Id = ot.objectid join tags t on ot.tagid = t.id where t.Name = 'fruit' or t.name = 'food';
Esto parece al revés, ya que desea y, pero el problema es que 2 etiquetas no están en la misma fila y, por lo tanto, y no produce nada, ya que 1 sola fila no puede ser a la vez una fruta y un alimento.Esta consulta generalmente generará duplicados, porque obtendrá 1 fila de cada objeto, por etiqueta.
Si realmente deseas hacer un y en este caso, necesitarás un group by
, y un having count = <number of ors>
en tu consulta por ejemplo.
SELECT distinct o.name, count(*) as count
from object o join objecttags ot on o.Id = ot.objectid
join tags t on ot.tagid = t.id
where t.Name = 'fruit' or t.name = 'food'
group by o.name
having count = 2;
Otros consejos
Oh Dios mío, puedo haber interpretado mal tu comentario original.
La forma más fácil de hacer esto en SQL sería tener tres tablas:
1) Tags ( tag_id, name )
2) Objects (whatever that is)
3) Object_Tag( tag_id, object_id )
Luego, puede hacer prácticamente cualquier pregunta que desee sobre los datos de manera rápida, fácil y eficiente (siempre que indexe adecuadamente). Si quieres ponerte elegante, también puedes permitir etiquetas de varias palabras (hay una manera elegante y una forma menos elegante, creo).
Supongo que eso es lo que tienes, por lo que este SQL a continuación funcionará:
La forma literal:
SELECT obj
FROM object
WHERE EXISTS( SELECT *
FROM tags
WHERE tag = 'fruit'
AND oid = object_id )
AND EXISTS( SELECT *
FROM tags
WHERE tag = 'Apple'
AND oid = object_id )
También hay otras formas de hacerlo, como:
SELECT oid
FROM tags
WHERE tag = 'Apple'
INTERSECT
SELECT oid
FROM tags
WHERE tag = 'Fruit'
@Kyle: su consulta debería ser más como:
SELECT object WHERE tag IN ('fruit', 'food');
Su consulta buscaba filas en las que la etiqueta fuera fruta y comida, lo que es imposible de ver porque el campo solo puede tener un valor, no ambos al mismo tiempo.
Combina la sugerencia de Steve M. con la de Jeremy y obtendrás un solo registro con lo que estás buscando:
select object
from tblTags
where tag = @firstMatch
and (
@secondMatch is null
or
(object in (select object from tblTags where tag = @secondMatch)
)
Ahora, eso no escala muy bien, pero obtendrá lo que está buscando. Creo que hay una mejor manera de hacerlo para que pueda tener fácilmente un número N de elementos coincidentes sin un gran impacto en el código, pero actualmente se me escapa.
Recomiendo el siguiente esquema.
Objects: objectID, objectName
Tags: tagID, tagName
ObjectTag: objectID,tagID
Con la siguiente consulta.
select distinct
objectName
from
ObjectTab ot
join object o
on o.objectID = ot.objectID
join tabs t
on t.tagID = ot.tagID
where
tagName in ('red','fruit')
Sugeriría que su tabla tenga 1 etiqueta por registro, así:
apple -> fruit
apple -> red
apple -> food
banana -> fruit
banana -> yellow
banana -> food
Entonces podrías simplemente
SELECT object WHERE tag = 'fruit' OR tag = 'food';
Si realmente quieres hacerlo a tu manera, puedes hacerlo así:
SELECT object WHERE tag LIKE 'red' OR tag LIKE '% red' OR tag LIKE 'red %' OR tag LIKE '% red %';