2011-10-10 8 views
6

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.

+0

¿Cómo ingresa el usuario su lista de etiquetas? ¿Teclean una lista separada por comas que luego concatenas en la consulta? – mellamokb

Respuesta

3

Si la lista de usuarios aparece como una lista separada por comas, puede intentar convertirla en una tabla temporal y unirse a ella en su lugar. No sé la sintaxis relveant para PosteGRE, así que aquí está la idea de MySQL:

create temporary table usertags (tag_id int); 
insert usertags values (1),(3),(3),(4); 

SELECT name, COUNT(name) AS relevance 
FROM cities 
JOIN citytags on cities.id = citytags.city_id 
JOIN usertags on citytags.tag_id = usertags.tag_id 
GROUP BY name ORDER BY relevance DESC; 

la conversión de la lista separada por comas al código anterior sería tan simple como hacer un reemplazar todos , a ),( usando el idioma del lado del servidor y, a continuación, incrústelo en una instrucción VALUES para rellenar la tabla temporal.

Demo (MySQL): http://www.sqlize.com/1qNThhD9tC

+0

Whoa! sqlize.com es increíble! Gracias! Eso es justo lo que necesitaba. –

1

palillo todas las etiquetas en una tabla y luego unirse en lugar de incluirlos en una lista IN.

CREATE TABLE #input (
    tag_id INT NOT NULL 
) 
; 

INSERT INTO #input 
      SELECT 1 
UNION ALL SELECT 3 
UNION ALL SELECT 3 
UNION ALL SELECT 4 
; 

SELECT 
    city.name, 
    search.relevance 
FROM 
    city 
INNER JOIN 
(
    SELECT 
    city_id, 
    COUNT(*) AS relevance 
    FROM 
    citytags 
    INNER JOIN 
    #input 
     ON #input.tag_id = citytags.tag_id 
    GROUP BY 
    city_id 
) 
    AS search 
    ON search.city_id = city.id 
ORDER BY 
    search.relevance DESC 
; 
Cuestiones relacionadas