Tengo un conjunto de ciudades que tienen una relación de varios a varios con un conjunto de etiquetas. El usuario me da una colección de etiquetas (que pueden contener duplicados!), y debo devolver una lista de entradas coincidentes, ordenadas por relevancia.Consulta SQL para buscar por etiquetas múltiples con clasificación de relevancia
El Datos
He aquí algunos datos de ejemplo para ilustrar el problema:
ciudades:
--------------------
| id | city |
--------------------
| 1 | Atlanta |
| 2 | Baltimore |
| 3 | Cleveland |
| 4 | Denver |
| 5 | Eugene |
--------------------
Etiquetas:
------
| id |
------
| 1 |
| 2 |
| 3 |
| 4 |
------
Las ciudades etiquetados como esto:
Atlanta: 1, 2
Baltimore: 3
Cleveland: 1, 3, 4
Denver: 2, 3
Eugene: 1, 4
... lo que la tabla CityTags parece:
------------------------
| city_id | tag_id |
------------------------
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 3 |
| 3 | 4 |
| 4 | 2 |
| 4 | 3 |
| 5 | 1 |
| 5 | 4 |
------------------------
Ejemplo 1
Si el usuario me da los ID de etiqueta: [1, 3, 3, 4], yo quiero contar cuántos partidos que tengo para cada una de las etiquetas, y devolver un resultado relevancia-clasificado como:
------------------------
| city | matches |
------------------------
| Cleveland | 4 |
| Baltimore | 2 |
| Eugene | 2 |
| Atlanta | 1 |
| Denver | 1 |
------------------------
Desde Cleveland acertó los cuatro etiquetas, es en primer lugar, seguido de Baltimore y Eugene, que cada uno tenía partido de dos etiquetas, etc.
Ejemplo 2
Un ejemplo más para hacer una buena medida. Para la búsqueda de [2, 2, 2, 3, 4], que tendríamos:
------------------------
| city | matches |
------------------------
| Denver | 4 |
| Atlanta | 3 |
| Cleveland | 2 |
| Baltimore | 1 |
| Eugene | 1 |
------------------------
SQL
Si hago caso de las repetidas etiquetas, entonces es trivial:
SELECT name,COUNT(name) AS relevance FROM
(SELECT name FROM cities,citytags
WHERE id=city_id AND tag_id IN (1,3,3,4)) AS matches
GROUP BY name ORDER BY relevance DESC;
Pero eso no es lo que necesito. Necesito respetar los duplicados. ¿Alguien puede sugerir cómo puedo lograr esto?
Solución en Postgresql
Aha! Una mesa temporal es lo que necesitaba. Postgresql me permite hacer esto con su sintaxis CON. Aquí está la solución:
WITH search(tag) AS (VALUES (1), (3), (3), (4))
SELECT name, COUNT(name) AS relevance FROM cities
INNER JOIN citytags ON cities.id=citytags.city_id
INNER JOIN search ON citytags.tag_id=search.tag
GROUP BY name ORDER BY relevance DESC;
Muchas gracias a los que respondieron.
¿Cómo ingresa el usuario su lista de etiquetas? ¿Teclean una lista separada por comas que luego concatenas en la consulta? – mellamokb