Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display Percent of value mysql

Tags:

mysql

I have the following data table and data:

mysql> describe school_data_sets_numeric_data;
+--------------+----------------+------+-----+---------+----------------+
| Field        | Type           | Null | Key | Default | Extra          |
+--------------+----------------+------+-----+---------+----------------+
| id           | int(11)        | NO   | PRI | NULL    | auto_increment |
| data_set_nid | int(11)        | NO   |     | NULL    |                |
| school_nid   | int(11)        | NO   |     | NULL    |                |
| year         | int(11)        | NO   |     | NULL    |                |
| description  | varchar(255)   | NO   |     | NULL    |                |
| value        | decimal(18,12) | NO   |     | NULL    |                |
+--------------+----------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

+----+--------------+------------+------+-------------------+------------------+
| id | data_set_nid | school_nid | year | description       | value            |
+----+--------------+------------+------+-------------------+------------------+
| 54 |        19951 |      19944 | 2008 | Asian             |  75.000000000000 |
| 51 |        19951 |      19944 | 2008 | White             | 200.000000000000 |
| 52 |        19951 |      19944 | 2008 | African American  | 100.000000000000 |
| 53 |        19951 |      19944 | 2008 | Hispanic          |  50.000000000000 |
| 55 |        19951 |      19944 | 2008 | Native American   |   9.000000000000 |

I want to display an average of the data instead of raw numbers. I want to do this in raw sql.

Example:

+----+--------------+------------+------+-------------------+------------------+
| id | data_set_nid | school_nid | year | description       | average          |
+----+--------------+------------+------+-------------------+------------------+
| 54 |        19951 |      19944 | 2008 | Asian             |  17.28%          |
| 51 |        19951 |      19944 | 2008 | White             | 46.06%           |
| 52 |        19951 |      19944 | 2008 | African American  | 23.04%           |
| 53 |        19951 |      19944 | 2008 | Hispanic          |  11.52%          |
| 55 |        19951 |      19944 | 2008 | Native American   |  2.07%           |

What is the best way to do this? Keep in mind there will be multiple data sets and years.

like image 346
Chris Muench Avatar asked Aug 10 '12 17:08

Chris Muench


1 Answers

One way to do this is to write a query that gets the "total value", which will be the denominator for your average.

SELECT SUM(value) AS total_value FROM school_data_sets_numeric_data

Join the results of that query with each row of the table, so you have the value and total available, which you use to calculate the average.

SELECT v.id
     , v.data_set_nid
     , v.school_nid
     , v.year
     , v.description
--   , v.value
--   , t.total_value
     , CONCAT(FORMAT(IF(t.total_value=0,0,(v.value*100.0)/t.total_value),2),'%')
       AS `average`
  FROM (SELECT SUM(value) AS total_value FROM school_data_sets_numeric_data) t
 CROSS
  JOIN school_data_sets_numeric_data v

The keyword CROSS is optional, but it serves as documentation to later reviewers, and lets them know that we intended to create a Cartesian product, and that we've purposefully left out (and not accidentally omitted) an ON clause for the JOIN.

The expression that returns the "average", displays the value as you show it... rounded to two decimal places with a trailing percent sign.

Calculating the "average" is straightforward... value / total_value * 100.

(Beware of the "integer" division if both numerator and demoninator are integer types, not an issue in your case, but we make sure numerator is decimal by multiplying by decimal literal, which insures we have decimal type value.)

The rest of the expression deals with avoiding possible "divide by zero" exception, rounding to two decimal places, formatting two fixed decimal places, and appending the '%'.

If you want those % signs to line up, you can pad the left side of that string with spaces, to a specific length, e.g. LPAD(expr,7,' '). (Actually, I would probably do that before the CONCAT that adds the %', but it doesn't really matter.)


If you intend to do an average on a subset, for example, for a particular year and a particular dataset, instead of the entire return set, then we get the "subtotal" for each of those groups of data.

Here, we're calculating the average from a particular data_set_nid and year:

SELECT v.id
     , v.data_set_nid
     , v.school_nid
     , v.year
     , v.description
--   , v.value
--   , t.total_value
     , CONCAT(FORMAT(IF(t.total_value=0,0,(v.value*100.0)/t.total_value),2),'%')
       AS `average`
  FROM (SELECT u.data_set_nid
             , u.year
             , SUM(u.value) AS total_value 
          FROM school_data_sets_numeric_data u
         GROUP
            BY u.data_set_nid
             , u.year
       ) t
  JOIN school_data_sets_numeric_data v
    ON v.data_set_nid = t.data_set_nid
     , v.year = t.year
like image 146
spencer7593 Avatar answered Sep 19 '22 12:09

spencer7593