SQL pivot column names and organize by date - mysql

I have a table as such:
date | page
----- |-----
2018-01-01 | good
2018-01-01 | good
2018-01-01 | good
2018-01-01 | bad
2018-01-02 | good
How do I organize by the values in the page column by date as such:
date | good | bad
----- |------|----
2018-01-01 | 3 | 1
2018-01-02 | 1 | 0

You need conditional aggregation :
select date,
sum(case when page = 'good' then 1 else 0 end) as good,
sum(case when page = 'bad' then 1 else 0 end) as bad
from table t
group by date;
However, MySQL has shorthand for this :
select date,
sum( page = 'good') as good,
sum( page = 'bad' ) as bad
from table t
group by date;

This can work just add a case inside sum function:
select date,
sum( case when page = 'good' then 1 else 0 end) as good,
sum( case when page = 'bad' then 1 else 0 end ) as bad
from table t
group by date

Using pivot table as,
create table #pivot(date date,page varchar(20))
insert into #pivot values
('2018-01-01','good')
,('2018-01-01','good')
,('2018-01-01','good')
,('2018-01-01','bad')
,('2018-01-02','good')
select date,good,bad from(
select date, page from #pivot
) f
pivot
( count(page) for page in (good,bad)
) p

I'm not sure what platform you are using but if it is in Oracle this will do the same.
select date, sum(decode(page,'good',1,0)) as good, sum(decode(page,'bad',1,0)) as bad
from table t
group by date;

Related

Mysql Group by 3 fields with count

See the attached image to see the data I have.
I am trying to get a result like this:
SessionNumber | Event Date| Critical Care Count | Pulmonary Circulation
G1 | 5/19/2018 | 2 | 3
G1 | 5/20/2018 | 5 | 1
PCC1 | 5/19/2018 | 4 | 5
I'm trying to count the various primaryAssembly, topic, reg per SessionNumber and EventDate.
This is the query I am using:
select SessionNumber, EventDate, count(distinct BadgeID) as CriticalCareCount
from beacon
where primaryAssembly="Critical Care"
group by SessionNumber, EventDate
order by EventDate;
But I would rather not have to use the 'Where' clause. I'd like grouping on the term itself.
Here's a screen shot:
A pivot query can help:
SELECT SessionNumber,Event_Date,
count( case when primaryAssembly = 'Critical Care' then 1 end )
As Critical_Care_Count,
count( case when primaryAssembly = 'Pulmonary Circulation' then 1 end )
As Pulmonary_Circulation_Count,
count( case when primaryAssembly = 'Some other value' then 1 end )
As Some_other_value_Count,
......
......
count( case when some_other_logical_condition then 1 end )
As some_other_condition_count
......
......
SUM( case when primaryAssembly = 'Critical Care' then Duration else 0 end )
As sum_of_duration_for_critical_care
......
......
count(*) As total_count
FROM table
GROUP BY SessionNumber,Event_Date

How can I make an SQL query that returns time differences between checkins and checkouts?

I'm using mysql and I've got a table similar to this one:
id | user | task | time | checkout
----+-------+------+-----------------------+---------
1 | 1 | 1 | 2014-11-25 17:00:00 | 0
2 | 2 | 2 | 2014-11-25 17:00:00 | 0
3 | 1 | 1 | 2014-11-25 18:00:00 | 1
4 | 1 | 2 | 2014-11-25 19:00:00 | 0
5 | 2 | 2 | 2014-11-25 20:00:00 | 1
6 | 1 | 2 | 2014-11-25 21:00:00 | 1
7 | 1 | 1 | 2014-11-25 21:00:00 | 0
8 | 1 | 1 | 2014-11-25 22:00:00 | 1
id is just an autogenerated primary key, and checkout is 0 if that row registered a user checking in and 1 if the user was checking out from the task.
I would like to know how to make a query that returns how much time has a user spent at each task, that is to say, I want to know the sum of the time differences between the checkout=0 time and the nearest checkout=1 time for each user and task.
Edit: to make things clearer, the results I'd expect from my query would be:
user | task | SUM(timedifference)
------+------+-----------------
1 | 1 | 02:00:00
1 | 2 | 02:00:00
2 | 2 | 03:00:00
I have tried using SUM(UNIX_TIMESTAMP(time) - UNIX_TIMESTAMP(time)), while grouping by user and task to figure out how much time had elapsed, but I don't know how to make the query only sum the differences between the particular times I want instead of all of them.
Can anybody help? Is this at all possible?
As all comments tell you, your current table structure is not ideal. However it's still prossible to pair checkins with checkouts. This is a SQL server implementation but i am sure you can translate it to MySql:
SELECT id
, user_id
, task
, minutes_per_each_task_instance = DATEDIFF(minute, time, (
SELECT TOP 1 time
FROM test AS checkout
WHERE checkin.user_id = checkout.user_id
AND checkin.task = checkout.task
AND checkin.id < checkout.id
AND checkout.checkout = 1
))
FROM test AS checkin
WHERE checkin.checkout = 0
Above code works but will become slower and slower as your table starts to grow. After a couple of hundred thousands it will become noticable
I suggest renaming time column to checkin and instead of having checkout boolean field make it datetime, and update record when user checkouts. That way you will have half the number of records and no complex logic for reading or querying
You can determine with a ranking method what are the matching check in/ check out records, and calculate time differences between them
In my example new_table is the name of your table
SELECT n.user, n.task,n.time, n.checkout ,
CASE WHEN #prev_user = n.user
AND #prev_task = n.task
AND #prev_checkout = 0
AND n.checkout = 1
AND #prev_time IS NOT NULL
THEN HOUR(TIMEDIFF(n.time, #prev_time)) END AS timediff,
#prev_time := n.time,
#prev_user := n.user,
#prev_task := n.task,
#prev_checkout := n.checkout
FROM new_table n,
(SELECT #prev_user = 0, #prev_task = 0, #prev_checkout = 0, #prev_time = NULL) a
ORDER BY user, task, `time`
Then sum the time differences (timediff) by wrapping it in another select
SELECT x.user, x.task, sum(x.timediff) as total
FROM (
SELECT n.user, n.task,n.time, n.checkout ,
CASE WHEN #prev_user = n.user
AND #prev_task = n.task
AND #prev_checkout = 0
AND n.checkout = 1
AND #prev_time IS NOT NULL
THEN HOUR(TIMEDIFF(n.time, #prev_time)) END AS timediff,
#prev_time := n.time,
#prev_user := n.user,
#prev_task := n.task,
#prev_checkout := n.checkout
FROM new_table n,
(#prev_user = 0, #prev_task = 0, #prev_checkout = 0, #prev_time = NULL) a
ORDER BY user, task, `time`
) x
GROUP BY x.user, x.task
It would probably be easier to understand by changing the table structure though. If that is at all possible. Then the SQL wouldn't have to be so complicated and would be more efficient. But to answer your question it is possible. :)
In the above examples, names prefixed with '#' are MySQL variables, you can use the ':=' to set a variable to a value. Cool stuff ay?
Select MAX of checkouts and checkins independently, map them based on user and task and calculate the time difference
select user, task,
SUM(UNIX_TIMESTAMP(checkin.time) - UNIX_TIMESTAMP(checkout.time)) from (
(select user, task, MAX(time) as time
from checkouts
where checkout = 0
group by user, task) checkout
inner join
(select user, task, MAX(time) as time
from checkouts
where checkout = 1
group by user, task) checkin
on (checkin.time > checkout.time
and checkin.user = checkout.user
and checkin.task = checkout.task)) c
This should work. Join on the tables and select the minimum times
SELECT
`user`,
`task`,
SUM(
UNIX_TIMESTAMP(checkout) - UNIX_TIMESTAMP(checkin)
)
FROM
(SELECT
so1.`user`,
so1.`task`,
MIN(so1.`time`) AS checkin,
MIN(so2.`time`) AS checkout
FROM
so so1
INNER JOIN so so2
ON (
so1.`id` = so2.`id`
AND so1.`user` = so2.`user`
AND so1.`task` = so2.`task`
AND so1.`checkout` = 0
AND so2.`checkout` = 1
AND so1.`time` < so2.`time`
)
GROUP BY `user`,
`task`,
so1.`time`) a
GROUP BY `user`,
`task` ;
As others have suggested though, This will not scale too well as it is, you would need to adjust it if it starts handling more data

Table join to return results in one row.

I have two tables and need to create a mysql view that gives the results in one row.
Currently I use a join but that gives me records as rows rather than columns. I tried the pivot but cannot get it to work. I need the hours for paint, hours for plumb and Other (everything else is in other) per job in one row.
The table structure is here:
This is basically a PIVOT, unfortunately MySQL does not have a PIVOT function, but you can use an aggregate function with a CASE statement:
select jobnum,
sum(case when tasktype = 'paint' then hrs else 0 end) Paint,
sum(case when tasktype = 'plumb' then hrs else 0 end) plumb,
sum(case when tasktype not in ('paint', 'Plumb') then hrs else 0 end) Other
from tablea a
left join tableb b
on a.id = b.tbla_id
group by jobnum
See SQL Fiddle with Demo
Result:
| JOBNUM | PAINT | PLUMB | OTHER |
----------------------------------
| 1 | 10 | 10 | 20 |
| 2 | 25 | 0 | 0 |
SELECT
a.`JobNum`,
SUM(IF(a.`TaskType`='Paint',b.`Hrs`,0)) AS 'Paint',
SUM(IF(a.`TaskType`='Plumb',b.`Hrs`,0)) AS 'Plumb',
SUM(IF(a.`TaskType` IN('Paint','Plumb'),0,b.`Hrs`)) AS 'Other'
FROM `tableA` a
INNER JOIN `tableB` b
ON b.`tblAid`=a.`id`
GROUP BY a.`JobNum`

something like "group by" for columns?

I have table like this:
+----+---------+---------+--------+
| id | value_x | created | amount |
+----+---------+---------+--------+
value_x is set of six strings, lets say "one", "two", "three", etc.
I need to create report like this:
+--------------+-------------------------+-------------------+----------------------+
| day_of_month | "one" | "two" | [etc.] |
+--------------+-------------------------+-------------------+----------------------+
| 01-01-2011 | "sum(amount) where value_x = colum name" for this specific day |
+--------------+-------------------------+-------------------+----------------------+
Most obvious solution is:
SELECT SUM(amount), DATE(created) FROM `table_name` WHERE value_x=$some_variable GROUP BY DATE(created)
And loop this query six times with another value for $some_variable in every iteration, but I'm courious if is it possible to do this in single query?
What you're asking is called a "pivot table" and is typically achieved as below. The idea is for each potential value of value_x you either produce a 1 or 0 per row and sum 1's and 0's to get the sum for each value.
SELECT
DATE(created),
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'one',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'two',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'three',
etc...
FROM table_name
GROUP BY YEAR(created), MONTH(created), DAY(created)
This will come close:
SELECT
s.day_of_month
,GROUP_CONCAT(CONCAT(s.value_x,':',s.amount) ORDER BY s.value_x ASC) as output
FROM (
SELECT DATE(created) as day_of_month
,value_x
,SUM(amount) as amount
FROM table1
GROUP BY day_of_month, value_x
) s
GROUP BY s.day_of_month
You will need to read the output and look for the value_x prior to the : to place the items in the proper column.
The benefit of this approach over #Michael's approach is that you do not need to know the possible values of field value_x beforehand.

sql query help on multiple count columns and group by

i have the following table Students:
id | status | school | name
----------------------------
0 | fail | skool1 | dan
1 | fail | skool1 | steve
2 | pass | skool2 | joe
3 | fail | skool2 | aaron
i want a result that gives me
school | fail | pass
---------------------
skool1 | 2 | 0
skool2 | 1 | 1
I have this but it's slow,
SELECT s.school, (
SELECT COUNT( * )
FROM school
WHERE name = s.name
AND status = 'fail'
) AS fail, (
SELECT COUNT( * )
FROM school
WHERE name = s.name
AND status = 'pass'
) AS pass,
FROM Students s
GROUP BY s.school
suggestions?
Something like this should work:
SELECT
school,
SUM(CASE WHEN status = 'fail' THEN 1 ELSE 0 END) as [fail],
SUM(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) as [pass]
FROM Students
GROUP BY school
ORDER BY school
EDIT
Almost forgot, but you could also write the query this way:
SELECT
school,
COUNT(CASE WHEN status = 'fail' THEN 1 END) as [fail],
COUNT(CASE WHEN status = 'pass' THEN 1 END) as [pass]
FROM Students
GROUP BY school
ORDER BY school
I'm not sure if there's any performance benefit with second query. My guess would be if there is it's probably very small. I tend to use the first query because I think it's more clear but both should work. Also, I don't have a MySql instance handy to test with, but according to #Johan the ORDER BY clauses are unnecessary.
SELECT q.school, q.fail, q.failpass-q.fail as pass
FROM
(
SELECT s.school, sum(if(status = 'fail',1,0)) as fail, count(*) as failpass
FROM students s
GROUP BY s.school
) q
This way you save one conditional sum.
In MySQL a GROUP BY already orders the results, so a separate ORDER BY is not needed.