How to pivot a table in MySQL using case statements? - mysql

I am trying to pivot a table in MySQL using case statements. This question has been asked many times here, and I have studied all of those answers, but I am looking for a solution that:
1. Uses case statements. Not self joins, subqueries, or unions.
2. Uses just SQL. Not Excel or shell scripts.
3. Works on MySQL.
Here is the table:
create table client (
name varchar(10),
revenue int(11),
expense int(11)
);
insert into client (name, revenue, expense) values ("Joe", 100, 200);
insert into client (name, revenue, expense) values ("Bill", 300, 400);
insert into client (name, revenue, expense) values ("Tim", 500, 600);
mysql> select * from client;
+------+---------+---------+
| name | revenue | expense |
+------+---------+---------+
| Joe | 100 | 200 |
| Bill | 300 | 400 |
| Tim | 500 | 600 |
+------+---------+---------+
I would like to pivot the table to this:
+-----+------+-----+
| Joe | Bill | Tim |
| 100 | 300 | 500 |
| 200 | 400 | 600 |
+-----+------+-----+
How can I accomplish this?
I have already seen the solutions at artfulsoftware dot com and buysql dot com, but those solutions are not working for my table.

see fiddle demo here
select
sum(case when name='Joe' then revenue else 0 end) as JOE,
sum(case when name='Bill' then revenue else 0 end) as Bill,
sum(case when name='Tim' then revenue else 0 end) as TIM
from client
union
select
sum(case when name='Joe' then expense else 0 end) as JOE,
sum(case when name='Bill' then expense else 0 end) as Bill,
sum(case when name='Tim' then expense else 0 end) as TIM
from client

Related

MySql Combine two queries (subselect or join or union)

I have two working queries that I would like to combine to provide one lot of output for a wall display. I am NOT a DB person by any means but have managed to create these queries from scratch, albeit with a lot of info from here!
I have four tables with relevant columns:
Hosts[host, hostid]
Items[hostid, itemid, itemname]
History_unit[itemid, value]
History_log[itemid, value]
hostid and itemid are our identifiers
The history_xxx tables are just that, and entry for every record of that data.
After trying to combine these for too many hours I just don't understand enough to make it work.
Query 1
SELECT hosts.host,
max((case when items.name='RP_Dayend_OK' then history_uint.value end) *1000) as 'Day End',
max((case when items.name='RP_Sync_OK' then history_uint.value end) *1000) as 'Sync',
max((case when items.name='RP_Monthend_OK' then history_uint.value end) *1000) as 'Month End',
max(case when items.name='RP_Version' then history_uint.value end) as 'Version'
from hosts, items, history_uint
where hosts.hostid=items.hostid and items.itemid=history_uint.itemid and items.name like '%RP\_%'
group by hosts.host
Output:
Host | Day End | Sync | Month End | Version
Host 1 | date | date | date | 7xx
Host 2 | date | date | date | 7xx
Query 2
SELECT hosts.host,
max(case when items.name='RP_Cron' then history_log.value end) as 'cron'
from hosts, items, history_log
where hosts.hostid=items.hostid and items.itemid=history_log.itemid and items.name like '%RP\_%'
group by hosts.host
Output:
Host | Cron
Host 1 | string
Host 2 | string
What I would like is:
Host | Day End | Sync | Month End | Version | Cron
Host 1 | date | date | date | 7xx | string
Host 2 | date | date | date | 7xx | string
I did manage a sub select but I ended up with a different row for each host for each item, and no data for 'cron'. I also tried joins to no avail. It is simply my lack of understanding.
Thanks for any help!
You should abandon the use of implicit (comma separated) joins in favour of explicit joins. In your case LEFT (outer) joins are appropriate.
DROP TABLE IF EXISTS Hosts;
DROP TABLE IF EXISTS Items;
DROP TABLE IF EXISTS History_unit;
DROP TABLE IF EXISTS History_uint;
DROP TABLE IF EXISTS History_log;
CREATE TABLE Hosts(host VARCHAR(20), hostid INT);
CREATE TABLE Items(hostid INT, itemid INT, name VARCHAR(20));
CREATE TABLE History_uint(itemid INT, value INT);
CREATE TABLE History_log(itemid INT, value INT);
INSERT INTO HOSTS VALUES ('HOST1',1),('HOST2',2);
INSERT INTO ITEMS VALUES
(1,1,'RP_Dayend_OK'),
(1,2,'RP_Sync_OK'),
(1,3,'RP_Monthend_OK'),
(1,4,'RP_Version' ),
(2,1,'RP_Dayend_OK'),
(2,2,'RP_Sync_OK'),
(2,2,'RP_cron')
;
INSERT INTO HISTORY_uint VALUES
(1,10),(2,10),(3,10),(4,10),
(1,50),(3,60);
INSERT INTO HISTORY_log VALUES
(1,10),(2,10),(3,10),(4,10)
;
SELECT hosts.host,
max((case when items.name='RP_Dayend_OK' then history_uint.value end) *1000) as 'Day End',
max((case when items.name='RP_Sync_OK' then history_uint.value end) *1000) as 'Sync',
max((case when items.name='RP_Monthend_OK' then history_uint.value end) *1000) as 'Month End',
max(case when items.name='RP_Version' then history_uint.value end) as 'Version',
max(case when items.name='RP_Cron' then history_log.value end) as 'cron'
from hosts
left join items on items.hostid = hosts.hostid
left join history_uint on history_uint.itemid = items.itemid
left join history_log on history_log.itemid = items.itemid
where items.name like '%RP\_%'
group by hosts.host;
+-------+---------+-------+-----------+---------+------+
| host | Day End | Sync | Month End | Version | cron |
+-------+---------+-------+-----------+---------+------+
| HOST1 | 50000 | 10000 | 60000 | 10 | NULL |
| HOST2 | 50000 | 10000 | NULL | NULL | 10 |
+-------+---------+-------+-----------+---------+------+
2 rows in set (0.00 sec)
Note it's usually best if the OP provides the data.

MySQL Sum operation based on other column

I have a table like this:
Date | Name | Pick | Amount
-----------+-------+-------+-------
2018-01-01 | Alice | Apple | 2
2018-01-01 | Alice | Grape | 3
2018-01-01 | Bob | Apple | 4
2018-01-02 | Alice | Apple | 5
2018-01-02 | Bob | Grape | 6
What is the SQL statement that produce result like below?
Name | Apple | Grape | Total
------+-------+-------+------
Alice | 7 | 3 | 10
Bob | 4 | 6 | 10
Using CASE condition, it will be possible.
Try this:
select name
,sum(case when pick = 'Apple' then amount end)Apple
,sum(case when pick = 'Grape' then amount end)Grape
,sum(case when pick = 'Apple' then amount end)
+sum(case when pick = 'Grape' then amount end)Total
from your_table
group by name
You want conditional aggregation :
select name,
sum(case when pick = 'Apple' then amount else 0 end) Apple,
sum(case when pick = 'Grape' then amount else 0 end) Grape,
sum(case when pick in ('Apple', 'Grape') then amount else 0 end) Total
from table t
group by name;
the is also a way to calculate your result, using aggregate function and or
select name,
sum(case when pick = 'Apple' then amount else 0 end) Apple,
sum(case when pick = 'Grape' then amount else 0 end) Grape,
sum(case when pick in('Apple','Grape') then amount else 0 end) Total
from tableA
group by name
http://sqlfiddle.com/#!9/4c56c/4
SELECT Name,
sum(if(Pick='Apple', Amount, 0)) as Apple,
sum(if(Pick='Grape', amount, 0)) as Grape
FROM table_name
GROUP BY Name;
You can use nested queries. One to calculate the grapes and the apples and one to calculate the total. The advantage is that you have the logic for calculation of each column on a single place and the calculation of the total is a bit more visible
SELECT
Name,
Apple,
Grape,
Apple + Grape as Total
FROM (
SELECT
`name` as Name,
SUM(IF(pick = 'Apple', amount, 0)) as Apple,
SUM(IF(pick = 'Grape', amount, 0)) as Grape
FROM test
GROUP BY name
) AS t1
db-fiddle

SQL Count on Multiple Fields

I'm not even sure if this is possible or whether I just need to write multiple queries.
I have a database table as follows:-
USER
university_id
gender
The output I'd like to achieve is as follows
| Uni | Total Users | Male | Female
| Uni1 | 30 | 15 | 15
| Uni2 | 40 | 25 | 15
I would be grateful if you could let me know if this is possible to achieve in a single queries, or whether I should just use different queries for each result.
Thank you in advance.
Phill
Here's how you can do it in a single query:
select
UniversityId,
count(*) as `Total Users`,
sum(case when gender = 'male' then 1 else 0 end) as Male,
sum(case when gender = 'female' then 1 else 0 end) as Female
from User
group by UniversityId
Demo: http://www.sqlfiddle.com/#!2/e7d16a/4

Create a Summary View in MySQL by pivoting row into dynamic number of columns

I have a table in MySQL with the following fields:
id, company_name, year, state
There are multiple rows for the same customer and year, here is an example of the data:
id | company_name | year | state
----------------------------------------
1 | companyA | 2008 | 1
2 | companyB | 2009 | 2
3 | companyC | 2010 | 3
4 | companyB | 2009 | 1
5 | companyC | NULL | 3
I am trying to create a view from this table to show one company per row (i.e. GROUP BY pubco_name) where the state is the highest for a given year.
Here is an example of the view I am trying to create:
id | cuompany_name | NULL | 2008 | 2009 | 2010
--------------------------------------------------
1 | companyA | NULL | 1 | NULL | NULL
2 | companyB | NULL | 2 | NULL | NULL
3 | companyC | 3 | NULL | NULL | 3
There is a lot more data than this, but you can see what I am trying to accomplish.
I don't know how to select the max state for each year and group by pubco_name.
Here is the SQL I have thus far (I think we need to use CASE and/or sub-selects here):
SELECT
id,
company_name,
SUM(CASE WHEN year = 2008 THEN max(state) ELSE 0 END) AS 2008,
SUM(CASE WHEN year = 2009 THEN max(state) ELSE 0 END) AS 2009,
SUM(CASE WHEN year = 2010 THEN max(state) ELSE 0 END) AS 2010,
SUM(CASE WHEN year = 2011 THEN max(state) ELSE 0 END) AS 2011,
SUM(CASE WHEN year = 2012 THEN max(state) ELSE 0 END) AS 2012,
SUM(CASE WHEN year = 2013 THEN max(state) ELSE 0 END) AS 2013
FROM tbl
GROUP BY company_name
ORDER BY id DESC
Appreciate your help and thanks in advance.
You need to pivot the table but mysql does not have any such functionality of pivot
so we need to replicate its functionality
EDITED
Select
group_concat(
DISTINCT
if(year is null,
CONCAT('max(if (year is null, state, 0)) as ''NULL'' '),
CONCAT('max(if (year=''', year, ''', state, 0)) as ''',year, ''' '))
) into #sql from tbl join (SELECT #sql:='')a;
set #sql = concat('select company_name, ', #sql, 'from tbl group by company_name;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
Result
| COMPANY_NAME | 2008 | 2009 | 2010 | NULL |
--------------------------------------------
| companyA | 1 | 0 | 0 | 0 |
| companyB | 0 | 2 | 0 | 0 |
| companyC | 0 | 0 | 3 | 3 |
SQL FIDDLE
There are 2 approaches to solve your problem
1. create case for each year, which is not possible in your case as we are dealing with year
2. generate the query dynamically so that we get proper columns as per your need.
I have given solution according to the second solution where I am generating the query and storing it in #sql variable. In the fiddle I have printed the contents of #sql before executing it.
select company_name, max(if (year='2008', state, 0)) as '2008' ,max(if (year='2009', state, 0)) as '2009' ,max(if (year='2010', state, 0)) as '2010' ,max(if (year is null, state, 0)) as 'NULL' from tbl group by company_name;
For more information regarding group_concat() go through the link
GROUP_CONCAT and
USER DEFINED VARIABLE
Hope this helps..
Please see the page linked in the answer to this question.
Note that when you do this, you must specify ahead of time how many columns you want in your output.
In response to the comment below, here is a simple/ basic implementation that reproduces the result table above (except for the ID column; having it makes no sense, as each row in the result can summarize more than one row in the input table)
SELECT
`company_name`,
NULLIF(SUM(CASE WHEN `t3`.`year` IS NULL THEN `t3`.`state` ELSE 0 END), 0) AS `null`,
NULLIF(SUM(CASE WHEN `t3`.`year` = 2008 THEN `t3`.`state` ELSE 0 END), 0) AS `2008`,
NULLIF(SUM(CASE WHEN `t3`.`year` = 2009 THEN `t3`.`state` ELSE 0 END), 0) AS `2009`,
NULLIF(SUM(CASE WHEN `t3`.`year` = 2010 THEN `t3`.`state` ELSE 0 END), 0) AS `2010`
FROM
(
SELECT
`t1`.`id`,
`t1`.`company_name`,
`t1`.`year`,
`t1`.`state`
FROM `tbl` `t1`
WHERE `t1`.`state` = (
SELECT MAX(`state`)
FROM `tbl` `t2`
WHERE `t2`.`company_name` = `t1`.`company_name`
AND (`t2`.`year` IS NULL AND `t1`.`year` IS NULL OR `t2`.`year` = `t1`.`year`)
)
) `t3`
GROUP BY `t3`.`company_name`;
This uses nested queries: the inner ones (with the t1 and t2 aliases) find the row with the maximum state for each year and company (and which will break unless you can be sure that this is unique!), and the outer one t3 does the pivot.
I would test this thoroughly to ensure performance is acceptable on real data.

MySQL : How to convert multiple row to single row? in mysql

I want to convert multiple rows to a single row, based on week. It should look like the following. Can any one help me?
id | Weight | Created |
1 | 120 | 02-04-2012 |
2 | 110 | 09-04-2012 |
1 | 100 | 16-04-2012 |
1 | 130 | 23-04-2012 |
2 | 140 | 30-04-2012 |
3 | 150 | 07-05-2012 |
Result should look like this:
id | Weight_week1 | Weight_week2 | weight_week3 | weight_week4 |
1 | 120 | 100 | 130 | |
2 | 110 | 140 | | |
3 | 150 | | | |
Thanks in advance.
if this a single table then
SELECT GROUP_CONCAT(weight) as Weight,
WEEK(Created) as Week
Group by Week(Created)
This will give you a row each having week id and comma seperated whights
You could do it like this:
SELECT
t.id,
SUM(CASE WHEN WeekNbr=1 THEN Table1.Weight ELSE 0 END) AS Weight_week1,
SUM(CASE WHEN WeekNbr=2 THEN Table1.Weight ELSE 0 END) AS Weight_week2,
SUM(CASE WHEN WeekNbr=3 THEN Table1.Weight ELSE 0 END) AS Weight_week3,
SUM(CASE WHEN WeekNbr=4 THEN Table1.Weight ELSE 0 END) AS Weight_week4
FROM
(
SELECT
(
WEEK(Created, 5) -
WEEK(DATE_SUB(Created, INTERVAL DAYOFMONTH(Created) - 1 DAY), 5) + 1
)as WeekNbr,
Table1.id,
Table1.Weight,
Table1.Created
FROM
Table1
) AS t
GROUP BY
t.id
I don't know if you want a AVG,SUM,MAX or MIN but you can change the aggregate to what you want.
Useful references:
Function for week of the month in mysql
you cannot create fields on the fly like that but you can group them.
use GROUP_CONCAT to deliver results with a delimiter that you can separate on later.
You could also do this:
SELECT id, created, weight, (
SELECT MIN( created ) FROM weights WHERE w.id = weights.id
) AS `min` , round( DATEDIFF( created, (
SELECT MIN( created )
FROM weights
WHERE w.id = weights.id ) ) /7) AS diff
FROM weights AS w
ORDER BY id, diff
This code does not do pivot table. You should add some additional code to convert the data to your needs. You may run into trouble if you use WEEK() because of the years.