Отримання даних для графіку гістограми


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

Я думаю, цього неможливо досягти прямо, але посилання на будь-яку схожу процедуру, що зберігається, також було б чудовим.


я не зовсім впевнений, що ви запитуєте. приклад виводу може допомогти.
Берек Брайан,

Вибачте! Просто оновив мій пост на прикладі.
Легенда

Відповіді:


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 має бути зміщено в операції ОКРУГЛЕННЯ на основі приросту округлення, щоб забезпечити, щоб перший сегмент містив стільки елементів, скільки наступні сегменти.

наприклад, з ROUND (числове_значення, -1), числове_значення в діапазоні [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


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, ваша група за & порядок краще має бути bin_floor або 1 - поганий голос, якщо ви це виправите, це найкраща відповідь для мене
BM

Досить справедливо, @bm. Змінено, як запропонував carillonator.
Бернардо Сіу

а якщо ви хочете отримати приємнішу назву стовпцяconcat(floor(mycol/5)*5," to ",floor(mycol/5)*5+5)
alex9311

Це насправді краще, ніж просто 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 така погана? Як я можу це покращити? Можливо, мені слід розмістити це на мета;)
Офрі Равів,

2
У цьому випадку необхідна шаблонна таблиця, щоб визначити мінімум максимум. Тільки з SQL неможливо.
Цезар

Гуру SQL! Саме те, що я хотів. Я думаю, слід бути обережним при створенні таблиці сміттєвих ящиків. Інакше все працює ідеально. :) Дякую. Я щойно закінчив писати скрипт на python, але це саме те, що мені потрібно було ...
Легенда

@Legend: Насправді, я справді є n00b, коли справа стосується 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, і воно, мабуть, найкраще відповідає випадку використання, коли потрібно смітити з 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

Це генерує кількість гістограм лише для заповнених бункерів. Девід Вест повинен бути правий у своїй корекції, але з якоїсь причини незаселені бункери для мене не відображаються в результаті (незважаючи на використання ЛІВОГО ПРИЄДНАННЯ - я не розумію, чому).


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

Також зауважте, що кількість бінів (10 у прикладі) слід писати з десятковою позначкою, щоб уникнути цілочисельного поділу (невідрегульована ширина біна може бути десятковою).


Це WITH something ASдуже корисно, якщо вам потрібно обчислити значення, яке надходить у бункери.
Рунар Берг,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.