web-dev-qa-db-fra.com

Obtention de données pour un tracé d'histogramme

Existe-t-il un moyen de spécifier les tailles de bacs dans MySQL? En ce moment, j'essaie la requête SQL suivante:

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

Les données générées sont suffisantes, mais il y a tout simplement trop de lignes. Ce dont j'ai besoin, c'est d'un moyen de regrouper les données dans des bacs prédéfinis. Je peux le faire à partir d'un langage de script, mais existe-t-il un moyen de le faire directement en SQL?

Exemple:

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

Ce que je recherche:

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

Je suppose que cela ne peut pas être réalisé de manière simple, mais une référence à toute procédure stockée connexe serait également très bien.

71
Legend

Ceci est un article sur un moyen super rapide et sale de créer un histogramme dans MySQL pour les valeurs numériques.

Il existe plusieurs autres façons de créer des histogrammes qui sont meilleurs et plus flexibles, en utilisant des instructions CASE et d'autres types de logique complexe. Cette méthode me gagne à maintes reprises car elle est tellement facile à modifier pour chaque cas d'utilisation, et si courte et concise. Voici comment procéder:

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

Changez simplement numeric_value en quelque soit votre colonne, changez l'incrément d'arrondi, et c'est tout. J'ai fait en sorte que les barres soient à l'échelle logarithmique, afin qu'elles n'augmentent pas trop lorsque vous avez de grandes valeurs.

numeric_value doit être décalé dans l'opération ROUNDing, en fonction de l'incrément d'arrondi, afin de garantir que le premier compartiment contient autant d'éléments que les compartiments suivants.

par exemple. avec ROUND (numeric_value, -1), numeric_value dans la plage [0,4] (5 éléments) sera placé dans le premier compartiment, tandis que [5,14] (10 éléments) dans le second, [15,24] dans le troisième, sauf si numeric_value est décalé de manière appropriée via ROUND (numeric_value - 5, -1).

Ceci est un exemple d'une telle requête sur certaines données aléatoires qui semble assez douce. Assez bon pour une évaluation rapide des données.

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

Quelques notes: les plages qui n'ont aucune correspondance n'apparaîtront pas dans le décompte - vous n'aurez pas de zéro dans la colonne de décompte. J'utilise également la fonction ROUND ici. Vous pouvez tout aussi facilement le remplacer par TRUNCATE si vous pensez que cela a plus de sens pour vous.

Je l'ai trouvé ici http://blog.shlomoid.com/2011/08/how-to-quickly-create-histogram-in.html

142
Jaro

La réponse de Mike DelGaudio est la façon dont je le fais, mais avec un léger changement:

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

L'avantage? Vous pouvez faire des bacs aussi grands ou aussi petits que vous le souhaitez. Des bacs de taille 100? floor(mycol/100)*100. Des bacs de taille 5? floor(mycol/5)*5.

Bernardo.

26
Bernardo Siu
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

Les bacs de table contiennent les colonnes min_value et max_value qui définissent les bacs. notez que l'opérateur "join ... on x BETWEEN y and z" est inclusif.

table1 est le nom de la table de données

16
Ofri Raviv

La réponse d'Ofri Raviv est très proche mais incorrecte. La count(*) sera 1 même s'il n'y a aucun résultat dans un intervalle d'histogramme. La requête doit être modifiée pour utiliser un sum conditionnel:

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;
11
David West
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 ....

Tant qu'il n'y a pas trop d'intervalles, c'est une assez bonne solution.

9
sammy

Cela devrait fonctionner. Pas si élégant mais quand même:

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

3
Renaud

J'ai créé une procédure qui peut être utilisée pour générer automatiquement une table temporaire pour les bacs en fonction d'un nombre ou d'une taille spécifié, pour une utilisation ultérieure avec la solution d'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((@binmax-@binmin)/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

Cela ne générera le nombre d'histogrammes que pour les cases remplies. David West devrait avoir raison dans sa correction, mais pour une raison quelconque, les bacs non peuplés n'apparaissent pas dans le résultat pour moi (malgré l'utilisation d'un LEFT JOIN - je ne comprends pas pourquoi).

3
Dologan
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 
2
Zebra

En plus d'une excellente réponse https://stackoverflow.com/a/10363145/916682 , vous pouvez utiliser l'outil de graphique phpmyadmin pour un résultat agréable:

enter image description here

enter image description here

1
zub0r

Regroupement de largeur égale dans un nombre donné de bacs:

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;

Notez que le 0.0000001 est là pour vous assurer que les enregistrements avec la valeur égale à max (col) ne font pas leur propre bac uniquement par eux-mêmes. De plus, la constante additive est là pour s'assurer que la requête n'échoue pas lors de la division par zéro lorsque toutes les valeurs de la colonne sont identiques.

Notez également que le nombre de casiers (10 dans l'exemple) doit être écrit avec une marque décimale pour éviter la division entière (la largeur de la corbeille non ajustée peut être décimale).

0
user824276