I am trying to insert rows that do not exist in the table that I am pulling from, it is sequenced by the days_to_cancel column and starts back at zero with a new Year/Month entry. I am using the following query:
CREATE TABLE reporting.tbl_exec_retention_curve_cumulative (days_to_cancel int, cancels int, cumulative_cancels int, enroll_dt varchar(50));
SELECT rc.days_to_cancel,
rc.cancels,
(#run_total := CASE WHEN rc.days_to_cancel <> 0 THEN #run_total +
rc.cancels ELSE rc.cancels END) AS cumulative_cancels,
LEFT(rc.client_enroll_dt,7) AS client_enroll_dt
FROM (SELECT rc.days_to_cancel,
SUM(CASE WHEN rc.client_status = 'CAN' THEN 1 ELSE 0 END) AS cancels,
LEFT(rc.client_enroll_dt,7) AS client_enroll_dt
FROM t1.table rc
GROUP BY 1,3
ORDER BY 3,1
) rc
JOIN (SELECT #run_total := 0) r
GROUP BY 1,4
ORDER BY 4,1;
I get a sample result of the following
days_to_cancel | cancels | cumulative_cancels | client_enroll_dt
---------------+---------+--------------------+------------------
42 | 2 | 376 | 2019-02
47 | 0 | 376 | 2019-02
0 | 0 | 0 | 2019-03
10 | 4 | 4 | 2019-03
11 | 9 | 13 | 2019-03
So my goal is to input the missing days and use the previous cumulative_cancels and enroll_dt values and 0 for the cancels column when I input that new row for those missing days. I've tried multiple ways including using variables, but I'm at a loss of what to do. I don't know if it's possible considering the rows never existed in the first place. If it matters I am on version 10.0.35 of MariaDB.
I just imagine that you fetched data from t1.table and want to insert your result into the same table.
My solution is putting your result into a temporary table then find the missing days by left join.
SELECT rc.days_to_cancel,
rc.cancels,
(#run_total := CASE WHEN rc.days_to_cancel <> 0 THEN #run_total +
rc.cancels ELSE rc.cancels END) AS cumulative_cancels,
LEFT(rc.client_enroll_dt,7) AS client_enroll_dt
--put your result into a temp table
into #temp
FROM (SELECT rc.days_to_cancel,
SUM(CASE WHEN rc.client_status = 'CAN' THEN 1 ELSE 0 END) AS cancels,
LEFT(rc.client_enroll_dt,7) AS client_enroll_dt
FROM t1.table rc
GROUP BY 1,3
ORDER BY 3,1
) rc
JOIN (SELECT #run_total := 0) r
GROUP BY 1,4
ORDER BY 4,1;
insert into t1.table (days_to_cancel, cancels,cumulative_cancels, client_enroll_dt)
select tm.days_to_cancel, tm.cancels, tm.cumulative_cancels, tm.client_enroll_d from #temp tm
left join t1.table rc on rc.days_to_cancel = tm.days_to_cancel
where rc.days_to_cancel is null --find out missing days
drop table #temp
Related
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
Consider:
SELECT(count(c.id),
case when(count(c.id) = 0)
then 'loser'
when(count(c.id) BETWEEN 1 AND 4)
then 'almostaloser'
when(count(c.id) >= 5)
then 'notaloser'
end as status,
...
When all is said and done, the query as a whole produces a set of results that look similar to this:
Count | status
--------|-------------
2 | almostaloser //total count is between 2 and 4
--------|-------------
0 | loser // loser because total count = 0
--------|-------------
3 | almostaloser //again, total count between 2 and 4
--------|-------------
What I would like to achieve:
a method to reatain the information from the above table, but add a third column that will give a total count of each status, something like
select count(c.id)
case when(count(c.id) = 0 )
then loser as status AND count how many of the total count does this apply to
results would look similar to:
Count | status |total_of each status |
--------|-------------|---------------------|
2 | almostaloser| 2 |
--------|-------------|---------------------|
0 | loser | 1 |
--------|-------------|---------------------|
3 | almostaloser| 2 |
--------|-------------|----------------------
I've been told this could be achieved using a derived table, but i've not yet been able to get them both, only one or the other.
This can be achieved with this query (you must place your original query as subquery in two places):
SELECT t1.*, t2.total_of_each_status
FROM (
-- put here your query --
) t1
INNER JOIN (
SELECT status, count(*) AS total_of_each_status
FROM (
-- put here your query --
) t2
GROUP BY status
) t2 ON t2.status = t1.status
I'm trying to get a list of all words in my dictionary ordered by the amount of "yes" votes it received.
Here is an example of the tables dictionary and vote:
Table dictionary:
dictionary_id dictionary_word
1 Dog
2 Cat
3 Pig
Table vote:
vote_id dictionary_id vote_path
1 3 yes
2 3 yes
3 1 yes
4 2 no
5 1 no
6 1 no
Here is the sql statement I've started making, the problem is I need to order it by the COUNT of votes that are yes, not just the number of votes tied to one dictionary_id
SELECT *
FROM dictionary
LEFT JOIN vote ON dictionary.dictionary_id = vote.dicionary_id
GROUP BY dictionary.dictionary_id
ORDER BY COUNT(vote_id) DESC
untested
SELECT *
FROM dictionary d
LEFT JOIN vote v ON d.dictionary_id = v.dictionary_id
GROUP BY d.dictionary_id
ORDER BY (
SELECT COUNT(*)
FROM vote AS vo1
WHERE vo1.dictionary_id=d.dictionary_id AND vo1.vote_path="yes"
) DESC
try using a conditional order like this.
NOTE: (with one exception) LEFT JOIN x WHERE x is the same as INNER JOIN x WHERE x .. in this case you only need a JOIN
SELECT
*
FROM dictionary d
JOIN vote v ON d.dictionary_id = v.dictionary_id
GROUP BY d.dictionary_id
ORDER BY SUM(CASE WHEN v.vote_path = 'yes' THEN 1 ELSE 0 END) DESC
DEMO
OUTPUT:
+--------------+---------------+-------+----------+-----------------+
|DICTIONARY_ID |DICTIONARY_WORD|VOTE_ID|VOTE_PATH | NUMBER_OF_YESSES|
+--------------+---------------+-------+----------+-----------------+
| 3 | Pig | 1 | yes | 2 |
| 1 | Dog | 3 | yes | 1 |
| 2 | Cat | 4 | no | 0 |
+--------------+---------------+-------+----------+-----------------+
if you want you can just do the SUM(CASE....) only once in the select and reference it in the ORDER BY for even faster speeds like so
SELECT
*,
SUM(CASE WHEN v.vote_path = 'yes' THEN 1 ELSE 0 END) as number_of_yesses
FROM dictionary d
JOIN vote v ON d.dictionary_id = v.dictionary_id
GROUP BY d.dictionary_id
ORDER BY number_of_yesses DESC
DEMO
Try:
SELECT
*
, (
SELECT COUNT(*)
FROM vote
WHERE dictionary.dictionary_id = vote.dicionary_id
AND vote_path = 'yes'
) AS yes_cnt
FROM dictionary
ORDER BY yes_cnt DESC
suppose you have this
create table dic (id int, name varchar(255));
create table votes (vid int, id int, vote int);
insert into dic values (1,'dog');
insert into dic values (2,'cat');
insert into dic values (3,'mouse');
insert into votes values (1,1,1);
insert into votes values (2,1,1);
insert into votes values (3,1,0);
insert into votes values (4,1,0);
insert into votes values (5,2,1);
insert into votes values (6,2,1);
insert into votes values (7,3,0);
then if you do this
select d.name, count(v.vid) as c
from dic d, votes v
where d.id = v.id
and v.vote = 1
group by d.id
order by c
you'll have this
NAME C
dog 2
cat 2
I have a table like this :
Type | Time
1 | 234234
2 | 234235
1 | 234238
3 | 234239
4 | 234240
1 | 234242
2 | 234245
I want to count number of all those rows where type=1 and next row's type=2.
For ex : The result here is 2.
I don't know how to put where clause on next row.
You should be able to implement user defined variables to get the total:
select count(*) Total
from
(
select type,
#row:=(case when #prev=1 and type=2 then 'Y' else 'N' end) as Seq,
#prev:=type
from yourtable, (SELECT #row:=null, #prev:=null) r
order by time, type
) src
where Seq = 'Y'
See SQL Fiddle with Demo
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`