Abrufen von Daten für das Histogramm


82

Gibt es eine Möglichkeit, Bin-Größen in MySQL anzugeben? Im Moment versuche ich die folgende SQL-Abfrage:

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

Die Daten, die generiert werden, sind gut genug, aber es gibt einfach zu viele Zeilen. Was ich brauche, ist eine Möglichkeit, die Daten in vordefinierten Fächern zu gruppieren. Ich kann dies in einer Skriptsprache tun, aber gibt es eine Möglichkeit, dies direkt in SQL zu tun?

Beispiel:

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

Was ich suche:

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

Ich denke, dies kann nicht auf einfache Weise erreicht werden, aber ein Verweis auf eine verwandte gespeicherte Prozedur wäre auch in Ordnung.


Ich bin mir nicht ganz sicher, was Sie fragen. Beispielausgabe könnte helfen.
Berek Bryan

Es tut uns leid! Habe gerade meinen Beitrag mit einem Beispiel aktualisiert.
Legende

Antworten:


159

Dies ist ein Beitrag über eine superschnelle und schmutzige Methode zum Erstellen eines Histogramms in MySQL für numerische Werte.

Es gibt mehrere andere Möglichkeiten, Histogramme zu erstellen, die mithilfe von CASE-Anweisungen und anderen Arten komplexer Logik besser und flexibler sind. Diese Methode überzeugt mich immer wieder, da sie für jeden Anwendungsfall so einfach zu ändern und so kurz und prägnant ist. Das ist wie man es macht:

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

Ändern Sie einfach numeric_value in Ihre Spalte, ändern Sie das Rundungsinkrement und fertig. Ich habe die Balken logarithmisch skaliert, damit sie bei großen Werten nicht zu stark wachsen.

numeric_value sollte in der ROUNDing-Operation basierend auf dem Rundungsinkrement versetzt werden, um sicherzustellen, dass der erste Bucket so viele Elemente enthält wie die folgenden Buckets.

zB mit ROUND (numeric_value, -1) wird numeric_value im Bereich [0,4] (5 Elemente) in den ersten Bucket gestellt, während [5,14] (10 Elemente) in den zweiten, [15,24] in den dritten, es sei denn, numeric_value wird über ROUND (numeric_value - 5, -1) entsprechend versetzt.

Dies ist ein Beispiel für eine solche Abfrage einiger zufälliger Daten, die ziemlich süß aussehen. Gut genug für eine schnelle Auswertung der Daten.

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

Einige Hinweise: Bereiche, die nicht übereinstimmen, werden nicht in der Zählung angezeigt. In der Zählspalte wird keine Null angezeigt. Außerdem verwende ich hier die ROUND-Funktion. Sie können es genauso einfach durch TRUNCATE ersetzen, wenn Sie der Meinung sind, dass es für Sie sinnvoller ist.

Ich fand es hier http://blog.shlomoid.com/2011/08/how-to-quickly-create-histogram-in.html


1
Ab MySQL 8.0.3 können Sie jetzt Histogrammstatistiken erstellen, um dem Optimierer weitere Statistiken bereitzustellen - siehe mysqlserverteam.com/histogram-statistics-in-mysql
Jaro

Sie benötigen nicht einmal den "Balken" -Teil der Abfrage. Die Zahlen selbst bilden bereits ein logarithmisches Balkendiagramm / Histogramm.
Enharmonic

31

Die Antwort von Mike DelGaudio ist die Art und Weise, wie ich es mache, aber mit einer kleinen Änderung:

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

Der Vorteil? Sie können die Behälter so groß oder so klein machen, wie Sie möchten. Behälter der Größe 100? floor(mycol/100)*100. Behälter der Größe 5? floor(mycol/5)*5.

Bernardo.


Wie Carillonator sagte, sollte Ihre Gruppe von & Order by Better bin_floor oder 1 - Ill upvote sein, wenn Sie es korrigieren, dies ist die beste Antwort für mich
BM

Fair genug, @bm. Geändert wie vom Glockenspiel vorgeschlagen.
Bernardo Siu

und wenn Sie einen schöneren Spaltennamen wollen, können Sie tunconcat(floor(mycol/5)*5," to ",floor(mycol/5)*5+5)
alex9311

Dies ist round(mycol, -2)aus der akzeptierten Antwort tatsächlich besser als einfach , da der Benutzer jeden nicht dezimalen "Bereich" definieren kann. Ich würde nur roundanstelle von verwenden, floorda es die Zahlen richtig rundet.
Meridius

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

Die Tabellenfächer enthalten die Spalten min_value und max_value, die die Fächer definieren. Beachten Sie, dass der Operator "join ... on x ZWISCHEN y und z" inklusive ist.

Tabelle1 ist der Name der Datentabelle


2
Warum ist die Syntaxfarbe für SQL so schlecht? Wie kann ich das verbessern? Vielleicht sollte ich es auf Meta posten;)
Ofri Raviv

2
In diesem Fall ist eine Vorlagentabelle erforderlich, um min und max zu definieren. Nur mit SQL ist das nicht möglich.
Cesar

SQL Guru! Genau das, was ich wollte. Ich denke, beim Erstellen der Bins-Tabelle ist Vorsicht geboten. Ansonsten funktioniert alles perfekt. :) Danke. Ich habe gerade ein Python-Skript fertig geschrieben, aber das ist genau das, was ich brauchte ...
Legende

@Legend: Eigentlich bin ich ein ziemlicher N00B, wenn es um SQL geht. Aber das war eine coole und nützliche Frage, also gefiel mir die Übung ...
Ofri Raviv

1
Es ist wichtig, die Antwort von @David West (die hier ein Kommentar sein sollte) darüber zu sehen, wie der COUNT (*) 1 erzeugt, wenn er Null erzeugen soll. Das ist vielleicht kein großes Problem für Sie, aber es kann statistische Daten verzerren und Sie ein wenig albern aussehen lassen, wenn jemand es bemerkt :)
Christopher Schultz

11

Die Antwort von Ofri Raviv ist sehr nah, aber falsch. Das count(*)wird sein , 1selbst wenn es Null resultiert in einem Histogramm - Intervall. Die Abfrage muss geändert werden, um eine Bedingung zu verwenden 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;

10
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 ....

Solange es nicht zu viele Intervalle gibt, ist dies eine ziemlich gute Lösung.


1
+1 Dies ist die einzige Lösung, die es ermöglicht, dass Behälter unterschiedlicher Größe sind
Gabe Moothart

großartig - keine Notwendigkeit für zusätzliche Tische
NiRR

+1 Dies ist die flexibelste Lösung imo und scheint am besten für den Anwendungsfall zu passen, in SQL binieren zu wollen. In jedem Fall, in dem Bin-Bereiche programmgesteuert abgeleitet werden müssen, ist es wahrscheinlich besser, dies außerhalb von SQL zu tun. wieder imo
Ryan McCoy

4

Ich habe eine Prozedur erstellt, mit der automatisch eine temporäre Tabelle für Bins gemäß einer bestimmten Anzahl oder Größe generiert werden kann, die später mit der Lösung von Ofri Raviv verwendet werden kann.

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

Dadurch wird die Histogrammanzahl nur für die ausgefüllten Bins generiert. David West sollte in seiner Korrektur Recht haben, aber aus irgendeinem Grund erscheinen unbewohnte Behälter für mich nicht im Ergebnis (trotz der Verwendung eines LEFT JOIN - ich verstehe nicht warum).


3

Das sollte funktionieren. Nicht so elegant, aber trotzdem:

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

über Mike DelGaudio


3
SELECT
    CASE
        WHEN total <= 30 THEN "0-30"
        WHEN total <= 40 THEN "31-40"       
        WHEN total <= 50 THEN "41-50"
        ELSE "50-"
    END as Total,
    count(*) as count
GROUP BY Total 
ORDER BY Total;

2

Gleiches Binning in eine bestimmte Anzahl von Bins:

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;

Beachten Sie, dass 0,0000001 vorhanden ist, um sicherzustellen, dass die Datensätze mit dem Wert max (col) nicht allein einen eigenen Bin erstellen. Die additive Konstante soll außerdem sicherstellen, dass die Abfrage bei der Division durch Null nicht fehlschlägt, wenn alle Werte in der Spalte identisch sind.

Beachten Sie auch, dass die Anzahl der Bins (im Beispiel 10) mit einer Dezimalstelle geschrieben werden sollte, um eine Ganzzahldivision zu vermeiden (die nicht angepasste bin_width kann dezimal sein).


Dies WITH something ASist sehr nützlich, wenn Sie den Wert berechnen müssen, der in die Fächer gelangt.
Rúnar Berg

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.