2009-11-19 8 views
68

C'è un modo per specificare le dimensioni dei raccoglitori in MySQL? In questo momento, sto cercando la seguente query SQL:Come ottenere i dati per la trama dell'istogramma

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

I dati che viene generata è abbastanza buono, ma ci sono solo troppe righe. Quello di cui ho bisogno è un modo per raggruppare i dati in bin predefiniti. Posso farlo da un linguaggio di scripting, ma c'è un modo per farlo direttamente in SQL?

Esempio:

+-------+--------------+ 
| 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 | 
------------------------ 

Quello che sto cercando:

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

Credo che questo non può essere realizzato in modo dritto in avanti, ma un riferimento a qualsiasi stored procedure legate sarebbe bene così .

+0

Non sono esattamente sicuro di quello che stai chiedendo. l'output di esempio potrebbe aiutare. –

+0

Siamo spiacenti! Ho appena aggiornato il mio post con un esempio. – Legend

risposta

127

Questo è un post su un modo super-veloce e sporco per creare un istogramma in MySQL per valori numerici.

Esistono altri modi per creare istogrammi migliori e più flessibile, utilizzando le istruzioni CASE e altri tipi di logica complessa. Questo metodo mi vince più e più volte dal momento che è così semplice da modificare per ogni caso d'uso, e quindi breve e conciso. Questo è il modo per farlo :

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

Basta cambiare numeric_value a tutto ciò che la colonna è, cambiare il arrotondamento di incremento, e questo è tutto. Ho fatto in modo che le barre si trovino nella scala logaritmica , in modo che non crescano troppo quando si dispone di valori grandi .

valore_numerico deve essere sfalsato nell'operazione ROUND, in base all'incremento di arrotondamento, in modo da garantire che il primo segmento contenga tutti gli elementi dei seguenti bucket.

ad es. con ROUND (valore_numerico, -1), valore_numerico nell'intervallo [0,4] (5 elementi) verrà inserito nel primo segmento, mentre [5,14] (10 elementi) nel secondo, [15,24] nel terzo, a meno numeric_value viene compensato in modo appropriato tramite ROUND (valore_numerico - 5, -1).

Questo è un esempio di tale query su alcuni dati casuali che sembra piuttosto dolce . Abbastanza buono per una rapida valutazione dei dati.

+--------+----------+-----------------+ 
| 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 | *    | 
+--------+----------+-----------------+ 

Alcune note: Gamme che non hanno alcuna corrispondenza non comparirà nel conteggio - non avrà uno zero nella colonna conteggio. Inoltre, sto usando la funzione ROUND qui. Puoi facilmente sostituirlo con TRUNCATE se ritieni che abbia più senso per te.

ho trovato qui http://blog.shlomoid.com/2011/08/how-to-quickly-create-histogram-in.html

+0

A partire da MySQL 8.0.3, ora avere la possibilità di creare statistiche sugli istogrammi al fine di fornire più statistiche all'ottimizzatore - vedi 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 

I bin di tabella contengono colonne min_value e max_value che definiscono i bin. nota che l'operatore "join ... on x BETWEEN y and z" è compreso.

table1 è il nome della tabella dati di risposta di

+2

Perché la colorazione della sintassi per SQL è così grave? Come posso migliorare questo? Forse dovrei postarlo su meta;) –

+0

@Ofri Raviv Sì, dovresti! – Ismael

+2

In questo caso è necessaria una tabella di template per definire min a max. Solo con SQL non è possibile. Guru SQL – Cesar

9

Ofri Raviv è molto vicino ma non corretto. Il count(*) sarà 1 anche se non ci sono risultati a zero in un intervallo di istogramma. La query deve essere modificato per utilizzare un condizionale 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

Ho fatto una procedura che può essere utilizzato per generare automaticamente una tabella temporanea per contenitori secondo un numero o dimensione specificata, per un uso successivo con la soluzione di 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 

Questo genererà il conteggio dell'istogramma solo per i bin che sono popolati. David West dovrebbe avere ragione nella sua correzione, ma per qualche motivo, i contenitori non popolati non compaiono nel risultato per me (nonostante l'uso di un SINISTRA SINISTRA - non capisco perché).

3

Questo dovrebbe funzionare. Non è così elegante, ma ancora:

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 

via Mike DelGaudio

21

risposta di Mike DelGaudio è il modo in cui lo faccio, ma con un leggero cambiamento:

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

Il vantaggio? È possibile rendere i contenitori grandi o piccoli come si desidera. Bins di dimensioni 100? floor(mycol/100)*100. Bidoni della taglia 5? floor(mycol/5)*5.

Bernardo.

+0

gruppo per 1 ordine di 1 – carillonator

+0

come carillonator ha detto che il tuo gruppo per & order by better dovrebbe essere bin_floor o 1 - Ill upvote se la correggi, questa è la migliore risposta per me –

+0

Abbastanza onesta, @bm. Modificato come suggerito dal 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 .... 

Finché non ci sono troppi intervalli, questa è una soluzione abbastanza buona.

+1

+1 Questa è l'unica soluzione qui che consente ai bin di essere diversi dimensione –

+0

ottima - non c'è bisogno di tabelle aggiuntive – 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

larghezza uguale binning in un determinato numero di bidoni:

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; 

Si noti che lo 0,0000001 è lì per assicurarsi che i record con il valore uguale a max (col) non rendano il proprio bin solo da solo. Inoltre, la costante additiva è lì per assicurarsi che la query non abbia esito negativo sulla divisione per zero quando tutti i valori nella colonna sono identici.

Si noti inoltre che il conteggio dei bin (10 nell'esempio) deve essere scritto con un segno decimale per evitare la divisione integer (il bin_width non regolato può essere decimale).