Reputation: 55
I have a table of meter readings which holds a meterNo, readingValue and readingDate.
I want to show the total sum of readingValue per meterNo per year.
For example:
SELECT meterNo, SUM(readingValue) '2009' FROM readings
WHERE readingDate >= '2009-01-01' AND readingDate <= '2009-12-31'
GROUP BY meterNo;
Will display the table:
meterNo 2009
--------------
4 50
5 5
12 30
13 63
18 18
26 484
27 21
28 510
29 28
Which is what I want, it shows the total reading value for each meter in 2009. However, I need the result table to display a column for each year. So it would be something like this:
meterNo 2009 2010 2011 2012
--------------------------------
1 20 35 50
2 45 35
3 50 60
4 50 35 20 30
5 5 10 10 15
6 30
And so on...
I can reuse the where part of the query:
WHERE readingDate >= '2009-01-01' AND readingDate <= '2009-12-31'
And just change the date for each year, butt how do I display each WHERE result as its own column in the one result table?
Upvotes: 1
Views: 8364
Reputation: 247840
MySQL does not have a PIVOT function but you can convert the rows of data into columns using an aggregate function with a CASE
expression.
If you have a limited number of years
, then you can hard-code the query:
select meterNo,
sum(case when year(readingDate) = 2009 then readingValue else 0 end) `2009`,
sum(case when year(readingDate) = 2010 then readingValue else 0 end) `2010`,
sum(case when year(readingDate) = 2011 then readingValue else 0 end) `2011`,
sum(case when year(readingDate) = 2012 then readingValue else 0 end) `2012`,
sum(case when year(readingDate) = 2013 then readingValue else 0 end) `2013`
from readings
group by meterno;
But if you are going to have an unknown number of values or what the query to adjust as new years are added to the database, then you can use a prepared statement to generate dynamic SQL:
SET @sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(CASE WHEN year(readingDate) = ',
year(readingDate),
' THEN readingValue else 0 END) AS `',
year(readingDate), '`'
)
) INTO @sql
FROM readings;
SET @sql
= CONCAT('SELECT meterno, ', @sql, '
from readings
group by meterno');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
See SQL Fiddle with Demo. Both give the result:
| METERNO | 2009 | 2010 | 2012 | 2013 | 2011 |
----------------------------------------------
| 1 | 90 | 180 | 0 | 90 | 90 |
| 2 | 50 | 0 | 90 | 0 | 0 |
| 3 | 80 | 40 | 90 | 90 | 0 |
As a side note, if you want null
to display in the rows without values instead of the zeros, then you can remove the else 0
(see Demo)
Upvotes: 7
Reputation: 12870
You can use CASE WHEN to do this. It allows you to sum multiple values based on multiple conditions. In your example, you could do something like this (I've simplified my example because I don't have access to your actual data):
SELECT meterNo,
sum(CASE WHEN readingDate = 2009 THEN readingValue END) as '2009',
sum(CASE WHEN readingDate = 2010 THEN readingValue END) as '2010',
sum(CASE WHEN readingDate = 2011 THEN readingValue END) as '2011',
sum(CASE WHEN readingDate = 2012 THEN readingValue END) as '2012'
FROM readings
GROUP BY meterNo
You need to switch up the conditions with your own--sum(CASE WHEN readingDate >= '2009-01-01' AND readingDate <= '2009-12-31' THEN readingValue END)
, etc--but it should work. Here's a working example that I did on sqlFiddle: http://sqlfiddle.com/#!2/6990e/29. Output is five columns with sums of each meterNo.
Upvotes: 2
Reputation: 6086
You could do something like this:
SELECT
meterNo, SUM(readingValue) AS `total`, YEAR(readingDate)
FROM
readings
GROUP BY
meterNo, YEAR(readingDate);
result:
METERNO TOTAL YEAR(READINGDATE)
1 90 2009
1 90 2010
2 50 2009
3 80 2009
3 40 2010
Also note how you can use YEAR
function instead of the where clause you have.
Upvotes: 0