2009-12-17 9 views
6

en MySQL, quiero seleccionar la parte inferior 2 artículos de cada categoríade consultas SQL para seleccionar inferior 2 de cada categoría

Category Value 
1  1.3 
1  4.8 
1  3.7 
1  1.6 
2  9.5 
2  9.9 
2  9.2 
2  10.3 
3  4 
3  8 
3  16 

Dándome:

Category Value 
1  1.3 
1  1.6 
2  9.5 
2  9.2 
3  4 
3  8 

Antes de que migraron de sqlite3 que tenía para seleccionar primero el más bajo de cada categoría, y luego excluir todo lo que se haya unido a eso, tuve que volver a seleccionar el más bajo de cada categoría. Entonces cualquier cosa igual a la nueva más baja o menos en una categoría ganada. Esto también elegiría más de 2 en caso de un empate, lo cual era molesto ... También tuvo un tiempo de ejecución realmente largo.

Mi objetivo final es contar la cantidad de veces que un individuo está en uno de los 2 más bajos de una categoría (también hay un campo de nombre) y esta es la única parte que no sé cómo hacer. Gracias

+0

¿Existe también un identificador que es único para cada fila? –

+1

Como no te gustaron los lazos, ¿cómo planeas evitarlos? Cualquier solución que se le ocurra a alguien tendrá que lidiar con los vínculos de alguna manera, por lo que debe tratar de deletrear específicamente las reglas que los rigen. –

+2

Etiqueté esta pregunta con 'greatest-n-per-group' porque es similar a muchas otras preguntas formuladas en StackOverflow con esa etiqueta. Aunque entiendo que estás pidiendo los * menos * valores por grupo, la técnica para resolverlo es la misma. –

Respuesta

4

Usted podría intentar esto:

SELECT * FROM (
    SELECT c.*, 
     (SELECT COUNT(*) 
     FROM user_category c2 
     WHERE c2.category = c.category 
     AND c2.value < c.value) cnt 
    FROM user_category c) uc 
WHERE cnt < 2 

hay que darle los resultados deseados, pero comprueba si el rendimiento es aceptable.

+0

Eso no funciona. Está regresando 9.2 y 10.3 para la categoría 2. –

+0

Lamento escuchar eso. Lo intenté y me funciona. ¿Podría verificar si sus datos de prueba son correctos? ¡Gracias! –

+0

Sí, lo ingresé exactamente como aparece arriba, en el mismo orden y todo. Los valores para la categoría 1 vuelven correctos, (1.3 y 1.6), pero para la categoría 2 es incorrecto, y para 2 también (devuelve 4 y 16). Además, esta consulta ni siquiera se ejecuta hasta que se da un alias a la primera subselección. –

1

Una unión debería funcionar. No estoy seguro del rendimiento en comparación con la solución de Peter.

SELECT smallest.category, MIN(smallest.value) 
    FROM categories smallest 
GROUP BY smallest.category 
UNION 
SELECT second_smallest.category, MIN(second_smallest.value) 
    FROM categories second_smallest 
    WHERE second_smallest.value > (SELECT MIN(smallest.value) FROM categories smallest WHERE second.category = second_smallest.category) 
GROUP BY second_smallest.category 
+0

Hay un error tipográfico en la cláusula where de la selección secundaria, debe ser "WHERE smallest.category = second_smallest.category". –

+1

Además, esto no dará los resultados correctos si hay un empate para el valor más pequeño en una categoría determinada. –

+0

Para deshacerse de las ataduras, ¿solo agrega DISTINCT? –

8
SELECT c1.category, c1.value 
FROM catvals c1 
LEFT OUTER JOIN catvals c2 
    ON (c1.category = c2.category AND c1.value > c2.value) 
GROUP BY c1.category, c1.value 
HAVING COUNT(*) < 2; 

probado con MySQL 5.1.41 con los datos de prueba. Salida:

+----------+-------+ 
| category | value | 
+----------+-------+ 
|  1 | 1.30 | 
|  1 | 1.60 | 
|  2 | 9.20 | 
|  2 | 9.50 | 
|  3 | 4.00 | 
|  3 | 8.00 | 
+----------+-------+ 

(. Las cifras decimales adicionales se deben a que declaré la columna de la value como NUMERIC(9,2))

Al igual que otras soluciones, esto produce más de 2 filas por categoría si hay lazos. Hay formas de construir la condición de unión para resolver eso, pero necesitaríamos usar una clave principal o clave única en su tabla, y también tendríamos que saber cómo se resolvió el .

+0

esto es genial! ¡Exactamente lo que estaba buscando! ¡Gracias! –

1

Aquí hay una solución muy generalizada, que funcionaría para seleccionar las primeras n filas para cada categoría. Esto funcionará incluso si hay duplicados en el valor.

/* creating temporary variables */ 
mysql> set @cnt = 0; 
mysql> set @trk = 0; 

/* query */ 
mysql> select Category, Value 
     from (select *, 
       @cnt:=if(@trk = Category, @cnt+1, 0) cnt, 
       @trk:=Category 
       from user_categories 
       order by Category, Value) c1 
     where c1.cnt < 2; 

Aquí está el resultado.

+----------+-------+ 
| Category | Value | 
+----------+-------+ 
|  1 | 1.3 | 
|  1 | 1.6 | 
|  2 | 9.2 | 
|  2 | 9.5 | 
|  3 |  4 | 
|  3 |  8 | 
+----------+-------+ 

Esto se prueba en MySQL 5.0.88 Tenga en cuenta que el valor inicial de la variable @trk no debe ser el menor valor del campo Categoría.

1

Aquí hay una solución que maneja los duplicados correctamente. Nombre de la tabla es 'zzz' y las columnas son int y float

select 
    smallest.category category, min(smallest.value) value 
from 
    zzz smallest 
group by smallest.category 

union 

select 
    second_smallest.category category, min(second_smallest.value) value 
from 
    zzz second_smallest 
where 
    concat(second_smallest.category,'x',second_smallest.value) 
    not in (-- recreate the results from the first half of the union 
     select concat(c.category,'x',min(c.value)) 
     from zzz c 
     group by c.category 
    ) 
group by second_smallest.category 

order by category 

Advertencias:

  • Si sólo hay un valor para una categoría determinada, entonces se devuelve sólo eso sola entrada.
  • Si hubiera un ID de registro único para cada fila, no necesitaría todos los concats para simular una clave única.

Su kilometraje puede variar,

--Mark

Cuestiones relacionadas