获取直方图的数据


82

有没有一种方法可以指定MySQL中的bin大小?现在,我正在尝试以下SQL查询:

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

生成的数据足够好,但是行太多。我需要的是一种将数据分组到预定义的bin中的方法。我可以从脚本语言执行此操作,但是有没有办法直接在SQL中执行此操作?

例:

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

我在寻找什么:

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

我猜想这不能直接实现,但是对任何相关存储过程的引用也可以。


我不确定您要问什么。示例输出可能会有所帮助。
Berek Bryan

抱歉! 刚刚用示例更新了我的帖子。
传奇

Answers:


159

这是一篇有关在MySQL中为数值创建直方图的超级快捷方法的文章。

使用CASE语句和其他类型的复杂逻辑,还有多种其他方法可以创建更好,更灵活的直方图。这种方法一次又一次地赢得了我,因为它很容易针对每个用例进行修改,而且简洁明了。这是您的操作方式:

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

只需将numeric_value更改为您的列,更改舍入增量就可以了。我已经将条形图设为对数刻度,这样当您具有较大的值时,它们就不会增长太多。

为了确保第一个存储桶包含的元素与后续存储桶一样多,应在ROUNDing操作中根据舍入增量对numeric_value进行偏移。

例如,使用ROUND(numeric_value,-1),范围[0,4](5个元素)中的numeric_value将被放置在第一个存储桶中,而[5,14](10个元素)在第二个存储桶中,[15,24]在第三个存储桶中,除非numeric_value通过ROUND(numeric_value-5,-1)适当偏移。

这是对一些看起来很漂亮的随机数据进行这种查询的示例。足够用于快速评估数据。

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

一些注意事项:不匹配的范围不会出现在计数中-计数列中不会有零。另外,我在这里使用ROUND函数。如果您觉得更有意义,可以用TRUNCATE轻松替换它。

我在这里找到它http://blog.shlomoid.com/2011/08/how-to-quickly-create-histogram-in.html


1
从MySQL 8.0.3开始,您现在可以创建直方图统计信息以便向优化程序提供更多统计信息-请参见mysqlserverteam.com/histogram-statistics-in-mysql
Jaro,

您甚至不需要查询的“栏”部分;数字本身已经形成对数条形图/直方图。
enharmonic

31

Mike DelGaudio的答案是我这样做的方式,但有一点变化:

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

优势?您可以根据需要使垃圾箱变大或变小。大小为100的垃圾桶?floor(mycol/100)*100。5号垃圾桶?floor(mycol/5)*5

贝尔纳多。


正如carillonator所说,按顺序排列,您的组最好是bin_floor或1-如果您改正,将不适用于投票,这对我来说是最佳答案
BM

公平,@ bm。根据carillonator的建议进行了更改。
萧伯纳(Bernardo Siu)

如果您想使用更好的列名,可以执行concat(floor(mycol/5)*5," to ",floor(mycol/5)*5+5)
alex9311 2016年

实际上,这比round(mycol, -2)接受的答案要简单,因为它允许用户定义任何非十进制的“范围”。我会使用round而不是floor因为它可以正确地四舍五入数字。
子午线

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

表容器包含定义容器的min_value和max_value列。请注意,运算符“在x和y和z之间的...联接...”是包含端点的。

table1是数据表的名称


2
为什么SQL的语法着色如此糟糕?我该如何改善?也许我应该在meta上发布它;)
Ofri Raviv

2
在这种情况下,必须使用模板表来定义最小和最大。仅无法使用SQL。
塞萨尔

SQL大师!正是我想要的。我想在创建bins表时应该小心。否则,一切都会正常进行。:) 谢谢。我刚写完一个python脚本,但这正是我所需要的...
Legend

@Legend:实际上,对于SQL来说我是n00b。但这是一个很酷且有用的问题,所以我喜欢该练习...
Ofri Raviv

1
重要的是要看到@David West的答案(这里应该是评论),关于COUNT(*)如何在应该产生零的时候产生1。对于您来说,这可能不是什么大问题,但如果有人注意到,它可能会使统计数据倾斜并使您看起来有些傻:)
Christopher Schultz 2015年

11

Ofri Raviv的答案非常接近,但不正确。该count(*)会是1即使有任何结果的直方图区间。需要修改查询以使用条件查询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 ....

只要没有太多的时间间隔,这是一个很好的解决方案。


1
+1这是这里唯一允许垃圾箱具有不同大小的解决方案
Gabe Moothart 2012年

太好了-不需要其他桌子
NiRR

+1这是最灵活的imo解决方案,似乎最适合希望从SQL内部进行装箱的用例。在需要以编程方式派生bin范围的任何情况下,最好在SQL外部执行该操作。再次imo
Ryan McCoy

4

我制作了一个过程,该过程可用于根据指定的数量或大小自动为垃圾箱生成临时表,以供以后与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

这将只为填充的箱生成直方图计数。大卫·韦斯特(David West)的改正应该是对的,但是由于某种原因,对我而言,未填充的垃圾箱不会出现在结果中(尽管使用了LEFT JOIN,但我不明白为什么)。


3

那应该工作。不太优雅,但仍然:

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

通过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

将等宽合并到给定数量的合并中:

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;

请注意,这里有0.0000001来确保值等于max(col)的记录不会仅仅使其自身成为自己的bin。另外,该列中的附加常数可确保当列中的所有值都相同时查询不会因除零而失败。

另请注意,bin的计数(在示例中为10)应写有小数点,以避免整数除法(未调整的bin_width可以为十进制)。


WITH something AS,如果你要计算进入进垃圾箱的价值是非常有用的。
朗尔·伯格

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.