Skip to content

# SQL pie chart

MySQL Graphs SQL August 12, 2009

My other half says I’m losing it. But I think that as an enthusiast kernel developer she doesn’t have the right to criticize people. (“I like user space better!” – she exclaims upon reading this).

Shown below is a (single query) SQL-generated pie chart. I will walk through the steps towards making this happen, and conclude with what, I hope you’ll agree, are real-world, useful usage samples.

>     +----------------------------------------------------------------------+
>     | pie_chart                                                            |
>     +----------------------------------------------------------------------+
>     |                                                                      |
>     |                         ;;;;;;;;;;;;;;;;;;;;;                        |
>     |                  oooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;                 |
>     |             oooooooooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;            |
>     |          ooooooooooooooooo                 ;;;;;;;;;;;;#####         |
>     |        oooooooooooooo                           ;#############       |
>     |       oooooooooooo                                 ############      |
>     |      oooooooooooo                                   ############     |
>     |      ooooooooooo                                     ###########     |
>     |      oooooooooooo                                   ::::::::::::     |
>     |       oooooooooooo                                 ::::::::::::      |
>     |        ooooooooo:::::                           ::::::::::::::       |
>     |          o::::::::::::::::                 :::::::::::::::::         |
>     |             :::::::::::::::::::::::::::::::::::::::::::::            |
>     |                  :::::::::::::::::::::::::::::::::::                 |
>     |                         :::::::::::::::::::::                        |
>     |                                                                      |
>     | ##  red: 1 (10%)                                                     |
>     | ;;  blue: 2 (20%)                                                    |
>     | oo  orange: 3 (30%)                                                  |
>     | ::  white: 4 (40%)                                                   |
>     +----------------------------------------------------------------------+

#### Requirements

We need a generic query, which returns at least these two columns: name_column and value_column. For example, the following query will do:

>     SELECT name AS name_column, value AS value_column FROM sample_values2;
>     +-------------+--------------+
>     | name_column | value_column |
>     +-------------+--------------+
>     | red         |            1 |
>     | blue        |            2 |
>     | orange      |            3 |
>     | white       |            4 |
>     +-------------+--------------+

Find sample data in pie_data.sql.

#### Part 1: expanding the original query

We’re going to need to take the above query’s results and expand them: how much is the ratio from total, per value? As first step, accumulate values:

>     SELECT
>       name_column,
>       value_column,
>       @accumulating_value := @accumulating_value+value_column AS accumulating_value
>     FROM (
>       SELECT name AS name_column, value AS value_column FROM sample_values2
>       ) select_values,
>       (SELECT @accumulating_value := 0) select_accumulating_value
>     ;
>     +-------------+--------------+--------------------+
>     | name_column | value_column | accumulating_value |
>     +-------------+--------------+--------------------+
>     | red         |            1 |                  1 |
>     | blue        |            2 |                  3 |
>     | orange      |            3 |                  6 |
>     | white       |            4 |                 10 |
>     +-------------+--------------+--------------------+

Next, we calculate ratio of accumulating value, and present it both in [0..1] range, as well as in [0..2*PI] (radians):

>     SELECT
>       name_order,
>       name_column,
>       value_column,
>       accumulating_value,
>       accumulating_value/@accumulating_value AS accumulating_value_ratio,
>       2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
>     FROM (
>       SELECT
>         name_column,
>         value_column,
>         @name_order := @name_order+1 AS name_order,
>         @accumulating_value := @accumulating_value+value_column AS accumulating_value,
>         @aggregated_name_column := CONCAT(@aggregated_name_column, name_column, ',') AS aggregated_name_column
>       FROM (
>         SELECT name AS name_column, value AS value_column FROM sample_values2
>         ) select_values,
>         (SELECT @name_order := 0) select_name_order,
>         (SELECT @accumulating_value := 0) select_accumulating_value,
>         (SELECT @aggregated_name_column := '') select_aggregated_name_column
>       ) select_accumulating_values
>     ;
>     +------------+-------------+--------------+--------------------+--------------------------+----------------------------+
>     | name_order | name_column | value_column | accumulating_value | accumulating_value_ratio | accumulating_value_radians |
>     +------------+-------------+--------------+--------------------+--------------------------+----------------------------+
>     |          1 | red         |            1 |                  1 |                      0.1 |           0.62831853071796 |
>     |          2 | blue        |            2 |                  3 |                      0.3 |            1.8849555921539 |
>     |          3 | orange      |            3 |                  6 |                      0.6 |            3.7699111843078 |
>     |          4 | white       |            4 |                 10 |                        1 |            6.2831853071796 |
>     +------------+-------------+--------------+--------------------+--------------------------+----------------------------+

The radians value will help us decide where in the pie chart lies each value.

#### Part 2: behind the scenes of the pie chart

We now explain how the pie chart works. Later on we combine with Part 1, to produce the complete chart.

We first generate a coordinates system (see SQL graphics):

>     SELECT
>       GROUP_CONCAT(CONCAT(t2.value,'.',t1.value) order by t1.value separator ' ') as circle
>     FROM
>       tinyint_asc t1,
>       tinyint_asc t2,
>       (select @size := 10) sel_size,
>       (select @radius := (@size/2 - 1)) sel_radius
>     WHERE
>       t1.value < @size
>       AND t2.value < @size
>     GROUP BY t2.value
>     ;
>     +-----------------------------------------+
>     | circle                                  |
>     +-----------------------------------------+
>     | 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 |
>     | 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 |
>     | 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 |
>     | 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 |
>     | 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 |
>     | 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 |
>     | 6.0 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 |
>     | 7.0 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 |
>     | 8.0 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 |
>     | 9.0 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 9.9 |
>     +-----------------------------------------+

Taking a slightly big step further, we calculate the angle per coordinate, in relation to center of coordinate system. Calculation is in radians, but presented in degrees, since it’s more readable. Also, we note in which quarter of the graph each point lies.

>     SELECT
>       group_concat(
>         round(radians*180/PI())
>         order by col_number separator ' ') as circle
>     FROM (
>       SELECT
>         t1.value AS col_number,
>         t2.value AS row_number,
>         @dx := (t1.value - (@size-1)/2) AS dx,
>         @dy := ((@size-1)/2 - t2.value) AS dy,
>         @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
>         CASE
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) >= 0 THEN @abs_radians
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) <= 0 THEN PI()-@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) <= 0 THEN PI()+@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) >= 0 THEN 2*PI()-@abs_radians
>         END AS radians
>       FROM
>         tinyint_asc t1,
>         tinyint_asc t2,
>         (select @size := 15) sel_size,
>         (select @radius := (@size/2 - 1)) sel_radius
>       WHERE
>         t1.value < @size
>         AND t2.value < @size) select_combinations
>       GROUP BY row_number
>     ;
>     +-------------------------------------------------------------+
>     | circle                                                      |
>     +-------------------------------------------------------------+
>     | 135 131 126 120 113 106 98 90 82 74 67 60 54 49 45          |
>     | 139 135 130 124 117 108 99 90 81 72 63 56 50 45 41          |
>     | 144 140 135 129 121 112 101 90 79 68 59 51 45 40 36         |
>     | 150 146 141 135 127 117 104 90 76 63 53 45 39 34 30         |
>     | 157 153 149 143 135 124 108 90 72 56 45 37 31 27 23         |
>     | 164 162 158 153 146 135 117 90 63 45 34 27 22 18 16         |
>     | 172 171 169 166 162 153 135 90 45 27 18 14 11 9 8           |
>     | 180 180 180 180 180 180 180 90 0 0 0 0 0 0 0                |
>     | 188 189 191 194 198 207 225 270 315 333 342 346 349 351 352 |
>     | 196 198 202 207 214 225 243 270 297 315 326 333 338 342 344 |
>     | 203 207 211 217 225 236 252 270 288 304 315 323 329 333 337 |
>     | 210 214 219 225 233 243 256 270 284 297 307 315 321 326 330 |
>     | 216 220 225 231 239 248 259 270 281 292 301 309 315 320 324 |
>     | 221 225 230 236 243 252 261 270 279 288 297 304 310 315 319 |
>     | 225 229 234 240 247 254 262 270 278 286 293 300 306 311 315 |
>     +-------------------------------------------------------------+

The above needs some formattign to present well, but that’s not the purpose; I’m only showing the above to explain the steps taken.

#### Part 3: combining the two

Next step is probably the most significant one: we’re going to present a rough, square, weird looking pie chart using the original values:

>     SELECT
>       group_concat(
>         (SELECT name_order FROM
>           (
>           SELECT
>             name_order,
>             name_column,
>             value_column,
>             accumulating_value,
>             accumulating_value/@accumulating_value AS accumulating_value_ratio,
>             2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
>           FROM (
>             SELECT
>               name_column,
>               value_column,
>               @name_order := @name_order+1 AS name_order,
>               @accumulating_value := @accumulating_value+value_column AS accumulating_value,
>               @aggregated_name_column := CONCAT(@aggregated_name_column, name_column, ',') AS aggregated_name_column
>             FROM (
>               SELECT name AS name_column, value AS value_column FROM sample_values2
>               ) select_values,
>               (SELECT @name_order := 0) select_name_order,
>               (SELECT @accumulating_value := 0) select_accumulating_value,
>               (SELECT @aggregated_name_column := '') select_aggregated_name_column
>             ) select_accumulating_values
>           ) select_for_radians
>         WHERE accumulating_value_radians >= radians LIMIT 1
>         )
>         order by col_number separator ' ') as circle
>     FROM (
>       SELECT
>         t1.value AS col_number,
>         t2.value AS row_number,
>         @dx := (t1.value - (@size-1)/2) AS dx,
>         @dy := ((@size-1)/2 - t2.value) AS dy,
>         @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
>         CASE
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) >= 0 THEN @abs_radians
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) <= 0 THEN PI()-@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) <= 0 THEN PI()+@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) >= 0 THEN 2*PI()-@abs_radians
>         END AS radians
>       FROM
>         tinyint_asc t1,
>         tinyint_asc t2,
>         (select @size := 21) sel_size,
>         (select @radius := (@size/2 - 1)) sel_radius
>       WHERE
>         t1.value < @size
>         AND t2.value < @size) select_combinations
>       GROUP BY row_number
>     ;
>     +-------------------------------------------+
>     | circle                                    |
>     +-------------------------------------------+
>     | 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 |
>     | 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 |
>     | 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 |
>     | 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 |
>     | 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 |
>     | 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 |
>     | 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 1 1 1 1 1 |
>     | 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 1 1 1 1 1 1 |
>     | 3 3 3 3 3 3 3 3 3 3 2 2 2 1 1 1 1 1 1 1 1 |
>     | 3 3 3 3 3 3 3 3 3 3 2 2 1 1 1 1 1 1 1 1 1 |
>     | 3 3 3 3 3 3 3 3 3 3 2 1 1 1 1 1 1 1 1 1 1 |
>     | 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 3 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 3 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     | 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 |
>     +-------------------------------------------+

The interesting SQL trick is that everything goes within the GROUP_CONCAT clause. Instead of presenting the coordinate, we check on the enhanced values table, looking for the first row which has a greater angle (in radians) than the current pixel has. We then display 1, 2, etc. to denote the value.

The next step is actually very simple: instead of drawing the full square, limit to a circle!

>     SELECT
>       group_concat(
>         IF(round(sqrt(pow(col_number-(@size-1)/2, 2) + pow(row_number-(@size-1)/2, 2))) BETWEEN @radius/2 AND @radius,
>         (SELECT name_order FROM
>           (
>           SELECT
>             name_order,
>             name_column,
>             value_column,
>             accumulating_value,
>             accumulating_value/@accumulating_value AS accumulating_value_ratio,
>             2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
>           FROM (
>             SELECT
>               name_column,
>               value_column,
>               @name_order := @name_order+1 AS name_order,
>               @accumulating_value := @accumulating_value+value_column AS accumulating_value,
>               @aggregated_name_column := CONCAT(@aggregated_name_column, name_column, ',') AS aggregated_name_column
>             FROM (
>               SELECT name AS name_column, value AS value_column FROM sample_values2
>               ) select_values,
>               (SELECT @name_order := 0) select_name_order,
>               (SELECT @accumulating_value := 0) select_accumulating_value,
>               (SELECT @aggregated_name_column := '') select_aggregated_name_column
>             ) select_accumulating_values
>           ) select_for_radians
>         WHERE accumulating_value_radians >= radians LIMIT 1
>         ), '-')
>         order by col_number separator ' ') as circle
>     FROM (
>       SELECT
>         t1.value AS col_number,
>         t2.value AS row_number,
>         @dx := (t1.value - (@size-1)/2) AS dx,
>         @dy := ((@size-1)/2 - t2.value) AS dy,
>         @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
>         CASE
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) >= 0 THEN @abs_radians
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) <= 0 THEN PI()-@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) <= 0 THEN PI()+@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) >= 0 THEN 2*PI()-@abs_radians
>         END AS radians
>       FROM
>         tinyint_asc t1,
>         tinyint_asc t2,
>         (select @size := 21) sel_size,
>         (select @radius := 7) sel_radius
>       WHERE
>         t1.value < @size
>         AND t2.value < @size) select_combinations
>       GROUP BY row_number
>     ;
>     +-------------------------------------------+
>     | circle                                    |
>     +-------------------------------------------+
>     | - - - - - - - - - - - - - - - - - - - - - |
>     | - - - - - - - - - - - - - - - - - - - - - |
>     | - - - - - - - - - - - - - - - - - - - - - |
>     | - - - - - - - - 2 2 2 2 2 - - - - - - - - |
>     | - - - - - - 3 3 3 2 2 2 2 2 2 - - - - - - |
>     | - - - - - 3 3 3 3 2 2 2 2 2 2 2 - - - - - |
>     | - - - - 3 3 3 3 3 2 2 2 2 2 2 2 1 - - - - |
>     | - - - - 3 3 3 3 3 - - - 2 2 2 1 1 - - - - |
>     | - - - 3 3 3 3 3 - - - - - 1 1 1 1 1 - - - |
>     | - - - 3 3 3 3 - - - - - - - 1 1 1 1 - - - |
>     | - - - 3 3 3 3 - - - - - - - 1 1 1 1 - - - |
>     | - - - 3 3 3 3 - - - - - - - 4 4 4 4 - - - |
>     | - - - 3 3 3 3 3 - - - - - 4 4 4 4 4 - - - |
>     | - - - - 3 3 4 4 4 - - - 4 4 4 4 4 - - - - |
>     | - - - - 3 4 4 4 4 4 4 4 4 4 4 4 4 - - - - |
>     | - - - - - 4 4 4 4 4 4 4 4 4 4 4 - - - - - |
>     | - - - - - - 4 4 4 4 4 4 4 4 4 - - - - - - |
>     | - - - - - - - - 4 4 4 4 4 - - - - - - - - |
>     | - - - - - - - - - - - - - - - - - - - - - |
>     | - - - - - - - - - - - - - - - - - - - - - |
>     | - - - - - - - - - - - - - - - - - - - - - |
>     +-------------------------------------------+

That looks a lot more like a pie chart.

#### Part 4: doing the fancy work

We will now add (in one big step):

- Stretching along the X-axis.
- Condensing the spaces.
- Coloring for the pie parts.
- A legend.

The text in bold is the original query, and is the only thing you need to change in order to create your own pie charts.

>     SELECT
>       group_concat(
>         IF(round(sqrt(pow(col_number/@stretch-0.5-(@size-1)/2, 2) + pow(row_number-(@size-1)/2, 2))) BETWEEN @radius*2/3 AND @radius,
>         (SELECT SUBSTRING(@colors, name_order, 1) FROM
>           (
>           SELECT
>             name_order,
>             name_column,
>             value_column,
>             accumulating_value,
>             accumulating_value/@accumulating_value AS accumulating_value_ratio,
>             @aggregated_data := CONCAT(@aggregated_data, name_column, ': ', value_column, ' (', ROUND(100*value_column/@accumulating_value), '%)', '|') AS aggregated_name_column,
>             2*PI()*accumulating_value/@accumulating_value AS accumulating_value_radians
>           FROM (
>             SELECT
>               name_column,
>               value_column,
>               @name_order := @name_order+1 AS name_order,
>               @accumulating_value := @accumulating_value+value_column AS accumulating_value
>             FROM (
>               SELECT name AS name_column, value AS value_column FROM sample_values2 LIMIT 4
>               ) select_values,
>               (SELECT @name_order := 0) select_name_order,
>               (SELECT @accumulating_value := 0) select_accumulating_value,
>               (SELECT @aggregated_data := '') select_aggregated_name_column
>             ) select_accumulating_values
>           ) select_for_radians
>         WHERE accumulating_value_radians >= radians LIMIT 1
>         ), ' ')
>         order by col_number separator '') as pie
>     FROM (
>       SELECT
>         t1.value AS col_number,
>         t2.value AS row_number,
>         @dx := (t1.value/@stretch - (@size-1)/2) AS dx,
>         @dy := ((@size-1)/2 - t2.value) AS dy,
>         @abs_radians := IF(@dx = 0, PI()/2, (atan(abs(@dy/@dx)))) AS abs_radians,
>         CASE
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) >= 0 THEN @abs_radians
>           WHEN SIGN(@dy) >= 0 AND SIGN(@dx) <= 0 THEN PI()-@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) <= 0 THEN PI()+@abs_radians
>           WHEN SIGN(@dy) <= 0 AND SIGN(@dx) >= 0 THEN 2*PI()-@abs_radians
>         END AS radians
>       FROM
>         tinyint_asc t1,
>         tinyint_asc t2,
>         (select @size := 23) sel_size,
>         (select @radius := (@size/2 - 1)) sel_radius,
>         (select @stretch := 4) sel_stretch,
>         (select @colors := '#;o:X"@+-=123456789abcdef') sel_colors
>       WHERE
>         t1.value < @size*@stretch
>         AND t2.value < @size) select_combinations
>       GROUP BY row_number
>     UNION ALL
>       SELECT
>         CONCAT(
>           REPEAT(SUBSTRING(@colors, value, 1), 2),
>           '  ',
>           SUBSTRING_INDEX(SUBSTRING_INDEX(@aggregated_data, '|', value), '|', -1)
>         )
>       FROM
>         tinyint_asc
>       WHERE
>         value BETWEEN 1 AND @name_order
>     ;
>     +----------------------------------------------------------------------------------------------+
>     | pie                                                                                          |
>     +----------------------------------------------------------------------------------------------+
>     |                                                                                              |
>     |                                   ;;;;;;;;;;;;;;;;;;;;;;;;;                                  |
>     |                          oooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                         |
>     |                    ooooooooooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                   |
>     |                oooooooooooooooooooo;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;               |
>     |             oooooooooooooooooooooooo                     ;;;;;;;;;;;;;;;;;;;;;###            |
>     |           oooooooooooooooooooo                                 ;;;;;;;;;###########          |
>     |         oooooooooooooooooo                                         ##################        |
>     |       ooooooooooooooooo                                               #################      |
>     |      ooooooooooooooooo                                                 #################     |
>     |      oooooooooooooooo                                                   ################     |
>     |     oooooooooooooooo                                                     ################    |
>     |      oooooooooooooooo                                                   ::::::::::::::::     |
>     |      ooooooooooooooooo                                                 :::::::::::::::::     |
>     |       ooooooooooooooooo                                               :::::::::::::::::      |
>     |         oooooooooooooo::::                                         ::::::::::::::::::        |
>     |           ooooooo:::::::::::::                                 ::::::::::::::::::::          |
>     |             ::::::::::::::::::::::::                     ::::::::::::::::::::::::            |
>     |                :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::               |
>     |                    :::::::::::::::::::::::::::::::::::::::::::::::::::::::                   |
>     |                          :::::::::::::::::::::::::::::::::::::::::::                         |
>     |                                   :::::::::::::::::::::::::                                  |
>     |                                                                                              |
>     | ##  red: 1 (10%)                                                                             |
>     | ;;  blue: 2 (20%)                                                                            |
>     | oo  orange: 3 (30%)                                                                          |
>     | ::  white: 4 (40%)                                                                           |
>     +----------------------------------------------------------------------------------------------+

Making the legend is by itself an interesting hack: aggregating an unknown number of rows using a session user variable, then splitting it again. Of course, this works well if we only have a small number of rows (values), as we expect in our case.

#### Showcase

OK. Why? Why?

Here are three charts I hope will convince the skeptic reader:

Given the sakila database, list the 8 largest tables (size in KB):

>     +------------------------------------------------------------------------------+
>     | pie_chart                                                                    |
>     +------------------------------------------------------------------------------+
>     |                                                                              |
>     |                            #######################                           |
>     |                    #######################################                   |
>     |               #################################################              |
>     |            ##################                   ##################           |
>     |         ###############                               ###############        |
>     |        ;;###########                                     #############       |
>     |      ;;;;;;;;;;;;;                                         #############     |
>     |      ;;;;;;;;;;;;                                           ############     |
>     |     ;;;;;;;;;;;;;                                           #############    |
>     |      ;;;;;;;;;;;;                                           @@@@@@@@@@@@     |
>     |      ;;;;;;;;;;;;;                                         """""""""""""     |
>     |        ;;;;;;;;;;;;;                                     XX"""""""""""       |
>     |         ;;;;;;;;;;;;;;;                               XXXXXXXXXXXXX""        |
>     |            ;;;;;;;;;;;;;;;;;;                   ::::::::::XXXXXXXX           |
>     |               ;;;;;;;;;;;;;;;;;;;;;;;;;;oooooooooo::::::::::::X              |
>     |                    ;;;;;;;;;;;;;;;;;;;;;;ooooooooooo::::::                   |
>     |                            ;;;;;;;;;;;;;;ooooooooo                           |
>     |                                                                              |
>     | ##  rental: 2850816 (43%)                                                    |
>     | ;;  payment: 2228224 (34%)                                                   |
>     | oo  inventory: 376832 (6%)                                                   |
>     | ::  film_text: 325440 (5%)                                                   |
>     | XX  film: 278528 (4%)                                                        |
>     | ""  film_actor: 278528 (4%)                                                  |
>     | @@  customer: 131072 (2%)                                                    |
>     | ++  staff: 98304 (1%)                                                        |
>     +------------------------------------------------------------------------------+

How much disk space does each storage engine consume (sum table size per engine)?

>     +------------------------------------------------------------------------------+
>     | pie_chart                                                                    |
>     +------------------------------------------------------------------------------+
>     |                                                                              |
>     |                            #######################                           |
>     |                    #######################################                   |
>     |               #################################################              |
>     |            ##################                   ##################           |
>     |         ###############                               ###############        |
>     |        #############                                     #############       |
>     |      #############                                         #############     |
>     |      ############                                           ############     |
>     |     #############                                           #############    |
>     |      ############                                           oooooooooooo     |
>     |      #############                                         ;;;;;;;;;;;oo     |
>     |        #############                                     ;;;;;;;;;;;;;       |
>     |         ###############                               ;;;;;;;;;;;;;;;        |
>     |            ##################                   #;;;;;;;;;;;;;;;;;           |
>     |               #####################################;;;;;;;;;;;;              |
>     |                    ###################################;;;;                   |
>     |                            #######################                           |
>     |                                                                              |
>     | ##  InnoDB: 1908732 (84%)                                                    |
>     | ;;  MyISAM: 284074 (12%)                                                     |
>     | oo  ARCHIVE: 84276 (4%)                                                      |
>     +------------------------------------------------------------------------------+

What were the most popular DMLs during the last 10 seconds?

>     +------------------------------------------------------------------------------+
>     | pie_chart                                                                    |
>     +------------------------------------------------------------------------------+
>     |                                                                              |
>     |                            #######################                           |
>     |                    #######################################                   |
>     |               #################################################              |
>     |            ##################                   ##################           |
>     |         ###############                               ###############        |
>     |        #############                                     #############       |
>     |      #############                                         #############     |
>     |      ############                                           ############     |
>     |     #############                                           #############    |
>     |      ############                                           oooo::::::::     |
>     |      #############                                         ooooooooooooo     |
>     |        #############                                     ooooooooooooo       |
>     |         ###############                               ooooooooooooooo        |
>     |            #################;                   ;;;;;;;;;ooooooooo           |
>     |               #############;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oo              |
>     |                    ######;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;                   |
>     |                            ;;;;;;;;;;;;;;;;;;;;;;;                           |
>     |                                                                              |
>     | ##  com_select: 1876 (69%)                                                   |
>     | ;;  com_insert: 514 (19%)                                                    |
>     | oo  com_delete: 277 (10%)                                                    |
>     | ::  com_update: 63 (2%)                                                      |
>     +------------------------------------------------------------------------------+

#### Conclusion

ASCII graphics always look funny; some would say the same about top, wget, cal, etc. (should I even mention lynx?)

I think it is possible to do most common charting with SQL: I’ve already shown how to do horizontal graphs and pie charts. Multi-column bar charts can also be worked out. These are not meant as a permanent solution; but it’s good to be able to visualize some values without having to install Nagios (along with apache, php, drivers, etc.), or otherwise exporting table, copying to desktop machines, loading into OpenOffice impress, generating graphs.

Sometimes you just need an immediate overlook. This is where I find SQL charting to be useful.

Sure, there are Perl and Python solutions for that; that’s easily achieved as well. But doing it from with the MySQL client gives, in my opinion, a level of confidence: you’ll always be able to produce the graph; perl-DBD-MySQL or no perl-DBD-MySQL; Linux or Windows.

Besides, it was fun doing it.

- Twitter
- LinkedIn
- WhatsApp
- Email
- Facebook
- Print

###
80
Leave a Reply

80 Comment threads
0 Thread replies
0 Followers
Most reacted comment
Hottest comment thread
40 Comment authors
Recent comment authors

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe
Notify of
Guest
Mark Leith

This is awesome.

You’re crazy of course, but this is awesome none the less! 😛

Guest
Domas

Riiiight! 🙂 HTML5 canvas is not needed, when there’s

Guest
Domas

… there’s

George Entenman (ge) 's status on Wednesday, 12-Aug-09 11:48:18 UTC - Identi.ca

[…] human creativity never ceases to amaze: https://shlomi-noach.github.io/blog/mysql/sql-pie-chart […]

Author
shlomi

@Mark,
Thank you – the doctors released me this very morning. Said I wasn’t dangerous.

@Domas,
I’m sorry, I completely lost you; I’m not sure if you mean you like it or hate it 😀

Guest
robo47

Really awesome …
Nice idea and implementation.

Never thought that this would be possible 🙂

Guest
Jan Kneschke

Please, oh, please: implemented something like http://aa-project.sourceforge.net/bb/ on top of this.

You have the 2D-rendering, now just loop it at 25frames/s and add a some extra transformations 🙂

Daniel E. Renfer (duck1123) 's status on Wednesday, 12-Aug-09 14:38:09 UTC - Identi.ca

[…] art pie charts in #SQL. https://shlomi-noach.github.io/blog/mysql/sql-pie-chart So crazy its […]

Guest
Wes Hardaker

Ha! That makes everything I’ve ever done look paltry. Thanks for that. I think.

Guest
Gord J

Bent. Brilliant, but bent.

Author
shlomi

@Jan:
Seriously??

@Wes:
thank you too, I guess 😉

@Gord:
Guilty as charged!

Seems like crazy posts deserve crazy comments 😀

Guest
familiar

What does your family say 😉
i think of them 😉
Seriously, what do you eat?

Guest
Jay Pipes

Ha! Awesomeness.

SQL pie chart | code.openark.org « Netcrema - creme de la social news via digg + delicious + stumpleupon + reddit

[…] SQL pie chart | code.openark.orgcode.openark.org […]

Guest
Ben

What version of mysql do u use? I’m having trouble on 5.0.27

Guest
sss

You are crazy or unemployed, but Everyone likes it

Guest
math0ne

Very impressive!

But yeah, your crazy.

Guest
ASCII_Man

Great success!

Guest
Dave

Man!

Would the term “doughnut chart” be more appropriate?

Thanks!

Guest
Ronald Bradford

I need plenty of help in real practical pursuits, like improved MySQL monitoring and instrumentation and better benchmarking of real-life systems.

When you have more free time.

Author
shlomi

@Ben:
5.0. If you describe exactly what problems you are experiencing, it would help.

@Everyone:
I get the message. I’ll just say goodbye to my loving family and commit myself to a good hospital (I’m eating well).

@Ronald:
After all these comments, I can;t say for sure if you mean it or if you’re joking…?

Håvard Eide (eide) 's status on Thursday, 13-Aug-09 06:02:32 UTC - Identi.ca

[…] why use fancy graphics when you can have pie charts in sql: https://shlomi-noach.github.io/blog/mysql/sql-pie-chart […]

Guest
Nils

mhmmm…. pie.

Guest
Wikidkaka

I really did not read through the query of yours. (Dont believe many people would have done). But you kick ass!!

Pie Charts with MySQL at chris ramsay

[…] Look no further than here… Shlomi Noach is clearly a genius! […]

Guest
Raja Sekharan

This is the most wicked thing I’ve ever seen anyone do with SQL! awesome!

Guest
Chuck

You have too much time on your hands. 🙂

I am somewhat new to MySQL and learned a lot about it other than just how to create pie charts using pure sql from this article.

Guest
Chuck

BTW if I wanted a pie chart from data store in mysql, I think I’d still probably just use a spreadsheet program like excel or OOo.

Author
shlomi

@Chuck
And you would be right in doing so. I’m very glad you learned something – that would be the best thing anyone can tell me!

@All
Hmmm… Right… this actually isn’t a pie but a donut… Guess I’ll need to start all over again!

Guest
Rafu

http://rafudb.blogspot.com/2009/08/sql-donut.html […] Oracle version […]

Maurizio Napolitano (napo) 's status on Friday, 14-Aug-09 09:59:50 UTC - Identi.ca

[…] https://shlomi-noach.github.io/blog/mysql/sql-pie-chart […]

Log Buffer #158: a Carnival of the Vanities for DBAs | Pythian Group Blog

[…] don’t want to shard, but you might want a slice of SQL pie—Shlomi Noach’s SQL-generated pie chart, that […]

Guest
Doug

This is the greatest thing next to the wheel! Period

15-Aug-2009 | MohanArun.com

[…] SQL Pie Chart – Interesting – how would you create a Pie chart using SQL alone? (with suitable data of course) […]

tech: Four short links: 14 August 2009 | tech3bite

[…] SQL Pie Chart — an ASCII pie chart, drawn by SQL code. Horrifying and yet inspiring. Compare to PostgreSQL code to produce ASCII Mandelbrot set. (via jdub on Twitter and Simon Willison) […]

Gráficas circulares SQL « /var/log/jynus

[…] es el segundo post de creación de gráficos con SQL de Shlomi Noach. Puedes leer también el original en inglés en su blog y mi post […]

Guest
Gerg

As an experiment I translated this into Postgres (not sure how well this will come out in the comments, I can send you the file) \set width 80 \set height 25 \set radius 1.0 \set colours '''#;o:X"@+-=123456789abcdef''' with slices as ( select cast(row_number() over () as integer) as slice, name, value, 100.0 * value / sum(value) over () as percentage, 2*PI() * sum(value) over (rows unbounded preceding) / sum(value) over () as radians from (values ('red',1),('blue',2),('orange',3),('white',4)) as data(name,value)) (select array_to_string(array_agg(c),'') from ( select x, y, case when not (sqrt(pow(x, 2) + pow(y, 2)) BETWEEN :radius*1/10 AND :radius) then ' '… Read more »

Guest
Gerg

I think all that stuff with abs() and then handling each quadrants as separate cases is unnecessary. You can just use -x if you want to flip it around the way you did. Why did you want to flip it around anyways?

Author
shlomi

@Gerg,

Ha! I wasn’t even aware of ATAN2() which gets 2 arguments… I did the workaround based on the one argument ATAN()…

Thanks,
Shlomi

PS have formatted your code in comment

Dagbok för 15 August 2009 | En sur karamell

[…] Delade SQL pie chart | code.openark.org […]

links for 2009-08-16 | burningCat

[…] SQL pie chart My other half says I’m losing it. But I think that as an enthusiast kernel developer she doesn’t have the right to criticize people. (”I like user space better!” – she exclaims upon reading this). […]

Houďasovo » Blog Archive » Madness? This… is… SQLPARTA!

[…] https://shlomi-noach.github.io/blog/mysql/sql-pie-chart […]

Guest
Gerg

Do you mind if I put this on our gallery of sql snippets for Postgres?

Thibauld Favre (thibauld) 's status on Sunday, 16-Aug-09 17:10:04 UTC - Identi.ca

[…] https://shlomi-noach.github.io/blog/mysql/sql-pie-chart […]

Guest
Vinay Srivastava

This is the most wicked thing I’ve ever seen anyone do with SQL! awesome!

Andrii V. Mishkovskyi (mishok13) 's status on Monday, 17-Aug-09 09:44:51 UTC - Identi.ca

[…] crazy stuff to say the least https://shlomi-noach.github.io/blog/mysql/sql-pie-chart […]

meneame.net

Generar gráficas tipo tarta en SQL [eng]…

Se pueden crear gráficas tipo tarta (pie chart) con los datos de una base de datos. Todo hecho únicamente con sentencias SQL y desde consola. via: ocio.barrapunto.com/article.pl?sid=09/08/17/0712203…

Guest
TheVoo

Hi, very nice hack! Still, I found the ASCII representation a bit hard to read and wondered if it was possible to add color to the pie chart? And indeed it is, thanks to ANSI Terminal Control Escape Sequences. Below is a changed version that will output the pie in color to your ANSI terminal. Only drawback: The column headers will be messed up, but perhaps someone find a nice solution to fix that 🙂 Only a few lines needed to change: Lines 4, 29, 50, 58, 59. @shlomi: keep up with this creative use of the mysql cli! SELECT… Read more »

Guest
Martijn Lafeber

I have to agree that you’re completely crazy, but not dangerous.

Meanwhile, I’m working hard to create a 3D version of this.

Guest
Willie

Amazing is an understatment – it’s epic dude. Seriously epic!

1 2 Next »
wpDiscuz
Send to Email Address Your Name Your Email Address Cancel
Post was not sent - check your email addresses!
Email check failed, please try again
Sorry, your blog cannot share posts by email.