2009-11-19 21 views
68

¿Hay alguna forma de especificar tamaños de contenedor en MySQL? En este momento, estoy tratando la siguiente consulta SQL:Obtención de datos para la gráfica del histograma

select total, count(total) from faults GROUP BY total; 

Los datos que se está generando es lo suficientemente bueno, pero hay demasiadas filas. Lo que necesito es una forma de agrupar los datos en contenedores predefinidos. Puedo hacer esto desde un lenguaje de scripts, pero ¿hay alguna manera de hacerlo directamente en SQL?

Ejemplo:

+-------+--------------+ 
| total | count(total) | 
+-------+--------------+ 
| 30 |   1 | 
| 31 |   2 | 
| 33 |   1 | 
| 34 |   3 | 
| 35 |   2 | 
| 36 |   6 | 
| 37 |   3 | 
| 38 |   2 | 
| 41 |   1 | 
| 42 |   5 | 
| 43 |   1 | 
| 44 |   7 | 
| 45 |   4 | 
| 46 |   3 | 
| 47 |   2 | 
| 49 |   3 | 
| 50 |   2 | 
| 51 |   3 | 
| 52 |   4 | 
| 53 |   2 | 
| 54 |   1 | 
| 55 |   3 | 
| 56 |   4 | 
| 57 |   4 | 
| 58 |   2 | 
| 59 |   2 | 
| 60 |   4 | 
| 61 |   1 | 
| 63 |   2 | 
| 64 |   5 | 
| 65 |   2 | 
| 66 |   3 | 
| 67 |   5 | 
| 68 |   5 | 
------------------------ 

Lo que estoy buscando:

+------------+---------------+ 
| total  | count(total) | 
+------------+---------------+ 
| 30 - 40 |   23 | 
| 40 - 50 |   15 | 
| 50 - 60 |   51 | 
| 60 - 70 |   45 | 
------------------------------ 

supongo que esto no se puede lograr de una manera recta hacia adelante, pero una referencia a cualquier procedimiento almacenado relacionados estaría bien, así .

+0

no estoy seguro de lo que está preguntando. ejemplo de salida puede ayudar. –

+0

¡Lo siento! Acabo de actualizar mi publicación con un ejemplo. – Legend

Respuesta

127

Esta es una publicación sobre una manera súper rápida y sucia de crear un histograma en MySQL para valores numéricos.

Existen muchas otras formas de crear histogramas que son mejores y más flexibles, utilizando sentencias CASE y otros tipos de lógica compleja. Este método me gana una y otra vez, ya que es tan fácil modificarlo para cada caso de uso, y tan breve y conciso. Esta es la forma en que lo hace :

SELECT ROUND(numeric_value, -2) AS bucket, 
     COUNT(*)     AS COUNT, 
     RPAD('', LN(COUNT(*)), '*') AS bar 
FROM my_table 
GROUP BY bucket; 

Sólo cambia numeric_value a lo que su columna está, cambie el incremento redondeo , y eso es todo. Hice que las barras estén en escala logarítmica , para que no crezcan demasiado cuando tenga valores grandes.

numeric_value debe compensarse en la operación REDONDEAR, en función del incremento de redondeo, para garantizar que el primer depósito contenga tantos elementos como los siguientes intervalos.

p. Ej. con ROUND (numeric_value, -1), numeric_value en el rango [0,4] (5 elementos) se colocarán en el primer cubo, mientras que [5,14] (10 elementos) en el segundo, [15,24] en el tercero, a menos que numeric_value se compensa apropiadamente mediante ROUND (numeric_value - 5, -1).

Este es un ejemplo de dicha consulta en algunos datos aleatorios que se ve bonito dulce. Lo suficientemente bueno para una evaluación rápida de los datos.

+--------+----------+-----------------+ 
| bucket | count | bar    | 
+--------+----------+-----------------+ 
| -500 |  1 |     | 
| -400 |  2 | *    | 
| -300 |  2 | *    | 
| -200 |  9 | **    | 
| -100 |  52 | ****   | 
|  0 | 5310766 | *************** | 
| 100 | 20779 | **********  | 
| 200 |  1865 | ********  | 
| 300 |  527 | ******   | 
| 400 |  170 | *****   | 
| 500 |  79 | ****   | 
| 600 |  63 | ****   | 
| 700 |  35 | ****   | 
| 800 |  14 | ***    | 
| 900 |  15 | ***    | 
| 1000 |  6 | **    | 
| 1100 |  7 | **    | 
| 1200 |  8 | **    | 
| 1300 |  5 | **    | 
| 1400 |  2 | *    | 
| 1500 |  4 | *    | 
+--------+----------+-----------------+ 

Algunas notas: Rangos que no tienen ningún partido que no aparecerán en la cuenta - que no van a tener un cero en la columna de la cuenta. Además, estoy usando la función REDONDA aquí. Simplemente puede reemplazarlo con TRUNCATE si cree que tiene más sentido para usted.

lo encontré aquí http://blog.shlomoid.com/2011/08/how-to-quickly-create-histogram-in.html

+0

A partir de MySQL 8.0.3, ahora tener la capacidad de crear estadísticas de histograma para proporcionar más estadísticas al optimizador; consulte http://mysqlserverteam.com/histogram-statistics-in-mysql/ – Jaro

16
SELECT b.*,count(*) as total FROM bins b 
left outer join table1 a on a.value between b.min_value and b.max_value 
group by b.min_value 

Los contenedores de tabla contienen columnas min_value y max_value que definen los contenedores. tenga en cuenta que el operador "join ... on x BETWEEN yy z" es inclusivo.

tabla1 es el nombre de la tabla de datos de respuesta de

+2

¿Por qué la coloración de sintaxis para SQL es tan mala? ¿Cómo puedo mejorar esto? Tal vez debería publicarlo en meta;) –

+0

@Ofri Raviv Sí, deberías! – Ismael

+2

En este caso es necesaria una tabla de plantilla para definir min an max. Solo con SQL no es posible. – Cesar

9

Ofri Raviv está muy cerca pero incorrecta. El count(*) será 1 incluso si no hay resultados en un intervalo de histograma. La consulta necesita ser modificado para utilizar un condicional sum:

SELECT b.*, SUM(a.value IS NOT NULL) AS total FROM bins b 
    LEFT JOIN a ON a.value BETWEEN b.min_value AND b.max_value 
GROUP BY b.min_value; 
3

hice un procedimiento que puede ser utilizado para generar automáticamente una tabla temporal para contenedores de acuerdo con un número o tamaño especificado, para su uso posterior con la solución de Ofri Raviv .

CREATE PROCEDURE makebins(numbins INT, binsize FLOAT) # binsize may be NULL for auto-size 
BEGIN 
SELECT FLOOR(MIN(colval)) INTO @binmin FROM yourtable; 
SELECT CEIL(MAX(colval)) INTO @binmax FROM yourtable; 
IF binsize IS NULL 
    THEN SET binsize = CEIL((@[email protected])/numbins); # CEIL here may prevent the potential creation a very small extra bin due to rounding errors, but no good where floats are needed. 
END IF; 
SET @currlim = @binmin; 
WHILE @currlim + binsize < @binmax DO 
    INSERT INTO bins VALUES (@currlim, @currlim+binsize); 
    SET @currlim = @currlim + binsize; 
END WHILE; 
INSERT INTO bins VALUES (@currlim, @maxbin); 
END; 

DROP TABLE IF EXISTS bins; # be careful if you have a bins table of your own. 
CREATE TEMPORARY TABLE bins (
minval INT, maxval INT, # or FLOAT, if needed 
KEY (minval), KEY (maxval));# keys could perhaps help if using a lot of bins; normally negligible 

CALL makebins(20, NULL); # Using 20 bins of automatic size here. 

SELECT bins.*, count(*) AS total FROM bins 
LEFT JOIN yourtable ON yourtable.value BETWEEN bins.minval AND bins.maxval 
GROUP BY bins.minval 

Esto generará el recuento de histogramas solo para los contenedores que se llenan. David West debería tener razón en su corrección, pero por alguna razón, los contenedores no poblados no aparecen en el resultado para mí (a pesar del uso de un LEFT JOIN, no entiendo por qué).

3

Eso debería funcionar. No es tan elegante pero aún así:

select count(mycol - (mycol mod 10)) as freq, mycol - (mycol mod 10) as label 
from mytable 
group by mycol - (mycol mod 10) 
order by mycol - (mycol mod 10) ASC 

través Mike DelGaudio

21

la respuesta de Mike Delgaudio es la manera de hacerlo, pero con un ligero cambio:

select floor(mycol/10)*10 as bin_floor, count(*) 
from mytable 
group by 1 
order by 1 

La ventaja? Puede hacer que los contenedores sean lo más grandes o pequeños que desee. Cubos de tamaño 100? floor(mycol/100)*100. Cubos de tamaño 5? floor(mycol/5)*5.

Bernardo.

+0

agrupar por 1 orden por 1 – carillonator

+0

como carillonator dijo que su grupo por orden debe ser bin_floor o 1 - Voto arriba si lo corrige, esta es la mejor respuesta para mí –

+0

Lo suficientemente justo, @bm. Cambiado según lo sugerido por carillonator. –

9
select "30-34" as TotalRange,count(total) as Count from table_name 
    where total between 30 and 34 
union (
select "35-39" as TotalRange,count(total) as Count from table_name 
    where total between 35 and 39) 
union (
select "40-44" as TotalRange,count(total) as Count from table_name 
    where total between 40 and 44) 
union (
select "45-49" as TotalRange,count(total) as Count from table_name 
    where total between 45 and 49) 
etc .... 

Mientras que no hay demasiados intervalos, esta es una solución bastante buena.

+1

+1 Esta es la única solución que permite que los contenedores sean diferentes tamaño –

+0

excelente - sin necesidad de tablas adicionales – NiRR

1
select case when total >= 30 and total <= 40 THEN "30-40"  
     else when total >= 40 and total <= 50 then "40-50" 
     else "50-60" END as Total , count(total) 
group by Total 
0

de igual ancho se van a agrupar en un recuento determinado de contenedores:

WITH bins AS(
    SELECT min(col) AS min_value 
     , ((max(col)-min(col))/10.0) + 0.0000001 AS bin_width 
    FROM cars 
) 
SELECT tab.*, 
    floor((col-bins.min_value)/bins.bin_width) AS bin 
FROM tab, bins; 

Tenga en cuenta que el 0.0000001 está ahí para asegurarse de que los registros con el valor igual a max (col) no constituyan su propio contenedor por sí mismo. Además, la constante aditiva está ahí para garantizar que la consulta no falle en la división por cero cuando todos los valores en la columna son idénticos.

También tenga en cuenta que el recuento de bins (10 en el ejemplo) se debe escribir con una marca decimal para evitar la división de enteros (el bin_width no ajustado puede ser decimal).

Cuestiones relacionadas