Получение данных для построения гистограммы

82

Есть ли способ указать размеры ящиков в MySQL? Прямо сейчас я пытаюсь выполнить следующий SQL-запрос:

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

Генерируемые данные достаточно хороши, но строк слишком много. Что мне нужно, так это способ сгруппировать данные в заранее определенные ячейки. Я могу сделать это на языке сценариев, но есть ли способ сделать это непосредственно в 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
Сожалею! Только что обновил мой пост примером.
Legend

Ответы:

162

Это сообщение об очень быстром и грязном способе создания гистограммы в 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 на то, что у вас есть, измените шаг округления, и все. Я сделал столбцы в логарифмическом масштабе, чтобы они не слишком сильно увеличивались при больших значениях.

numeric_value следует смещать в операции ROUNDing на основе приращения округления, чтобы гарантировать, что первый сегмент содержит столько элементов, сколько следующие сегменты.

например, с ROUND (numeric_value, -1), numeric_value в диапазоне [0,4] (5 элементов) будет помещен в первую корзину, а [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

Jaro
источник
1
Начиная с MySQL 8.0.3, теперь у вас есть возможность создавать статистику гистограмм, чтобы предоставить оптимизатору больше статистики - см. Mysqlserverteam.com/histogram-statistics-in-mysql
Jaro
Вам даже не нужна «полоса» в запросе; сами числа уже образуют логарифмическую столбчатую диаграмму / гистограмму.
энгармонический
31

Майк ДельГаудио отвечает так, как я это делаю, но с небольшими изменениями:

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 сказал, что ваша группа by & order by лучше должна быть bin_floor или 1. Я буду поддерживать, если вы исправите это, это лучший ответ для меня
BM
Достаточно честно, @bm. Поменял по предложению карильонатора.
Bernardo Siu
и если вы хотите более красивое имя столбца, вы можете сделатьconcat(floor(mycol/5)*5," to ",floor(mycol/5)*5+5)
alex9311
На самом деле это лучше, чем просто round(mycol, -2)из принятого ответа, поскольку он позволяет пользователю определять любой недесятичный «диапазон». Я бы просто использовал roundвместо, floorпоскольку он правильно округляет числа.
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

Ячейки таблицы содержат столбцы min_value и max_value, которые определяют ячейки. обратите внимание, что оператор «соединить ... на x МЕЖДУ y и z» является включительным.

table1 - это имя таблицы данных

Офри Равив
источник
2
Почему синтаксическая расцветка SQL такая плохая? Как я могу это улучшить? Может, стоит выложить это на мета;)
Офри Равив
2
В этом случае необходима таблица-шаблон для определения минимума и максимума. Только с SQL это невозможно.
Cesar
SQL Guru! Именно то, что я хотел. Думаю, при создании таблицы бинов следует соблюдать осторожность. В остальном все работает отлично. :) Благодарю вас. Я только что закончил писать скрипт на Python, но это как раз то, что мне нужно ...
Legend
@Legend: Вообще-то, когда дело доходит до SQL, я большой новичок. но это был классный и полезный вопрос, поэтому мне понравилось упражнение ...
Офри Равив
1
Важно увидеть ответ @David West (который должен был быть здесь комментарием) о том, как COUNT (*) производит 1, когда он должен давать ноль. Возможно, это не большая проблема для вас, но может исказить статистические данные и заставить вас выглядеть немного глупо, если кто-то заметит :)
Кристофер Шульц
11

Ответ Офри Равива очень близок, но неверен. 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 Это единственное решение, которое позволяет использовать бункеры разного размера
Гейб Мутхарт
отлично - дополнительные столы не нужны
NiRR
+1 Это наиболее гибкое решение imo, и, кажется, лучше всего подходит для случая использования bin изнутри SQL. В любом случае, когда диапазоны бинов должны быть получены программно, это, вероятно, лучше сделать за пределами SQL. снова imo
Райан Маккой
4

Я создал процедуру, которую можно использовать для автоматического создания временной таблицы для ящиков в соответствии с указанным числом или размером для дальнейшего использования с решением Офри Равива.

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

Это сгенерирует счетчик гистограмм только для заполненных интервалов. Дэвид Уэст должен быть прав в своем исправлении, но по какой-то причине незаселенные корзины не отображаются в результате для меня (несмотря на использование 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

через Майка ДельГаудио

Renaud
источник
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), сами по себе не создают его собственной корзины. Кроме того, аддитивная константа предназначена для того, чтобы запрос не завершился ошибкой при делении на ноль, когда все значения в столбце идентичны.

Также обратите внимание, что количество бинов (10 в примере) должно быть записано с десятичной меткой, чтобы избежать целочисленного деления (нескорректированная bin_width может быть десятичной).

user824276
источник
Это WITH something ASочень полезно, если вам нужно вычислить значение, которое попадает в ячейки.
Rúnar Berg