Mysql: Compare and filter time entries - mysql

I have an table with user ids and login dates.
id | customer | timestamp
1 | 1 | 2017-02-10 11:30:28
2 | 1 | 2017-02-11 11:30:28
3 | 2 | 2017-02-12 11:30:28
4 | 3 | 2017-02-13 11:30:28
5 | 1 | 2017-02-14 11:30:28
Now I want to get the count of the longest continuous streak of logins per customer.
I got to the point, where the difference is calculated correctly for one customer.
SELECT a.id aId,
b.id bId,
a.customer,
a.timestamp aTime,
b.timestamp bTime,
DATEDIFF(b.timestamp, a.timestamp) as diff
FROM logins a
INNER JOIN logins b
ON a.customer = b.customer AND a.id < b.id
WHERE b.customer = 7
GROUP BY a.id
How can I do this for the whole table and count the following logins with a difference under 1 day?
The wanted result for this example should be:
customer | days of continuous login
1 | 2
2 | 1
3 | 1

You can do this with a LEFT JOIN
Query
SELECT
logins.customer
, COUNT(*) as "longest continuous streak of logins"
FROM (
SELECT
login1.*
FROM
login login1
LEFT JOIN
login login2
ON
login1.timestamp < login2.timestamp
AND
# Only JOIN if date diff is less or equal then 1 day
DATEDIFF(login2.timestamp, login1.timestamp) <= 1
WHERE
login2.id IS NOT NULL
AND
login2.customer IS NOT NULL
AND
login2.timestamp IS NOT NULL
ORDER BY
login1.customer
)
AS logins
GROUP BY
logins.customer
Result
| customer | longest continuous streak of logins |
|----------|-------------------------------------|
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
see demo http://www.sqlfiddle.com/#!9/ad581/17

Related

Query based on dates

I've got the following table/data (example)
Users
user_id | email
1 | asd#asd.com
2 | asd2#asd.com
3 | asd3#asd.com
4 | asd4#asd.com
5 | asd5#asd.com
Scheduled_Jobs
job_id | user_id | date
1 | 1 | 05/09/2019
2 | 1 | 05/10/2019
3 | 1 | 05/11/2019
4 | 1 | 05/12/2019
5 | 2 | 07/10/2019
6 | 2 | 07/11/2019
7 | 2 | 07/12/2019
8 | 3 | 11/07/2019
9 | 4 | 13/10/2019
10 | 4 | 13/11/2019
11 | 5 | 10/10/2019
12 | 5 | 10/11/2019
13 | 5 | 10/12/2019
Last_Update
update_id | job_id
1 | 1
2 | 2
3 | 3
4 | 5
5 | 9
6 | 11
When a user is created a list of scheduled jobs is created too. When a user completes a job the Last_Update table is getting updated.
I'm trying to show a list of users which got unfinished jobs based on date. For example 1-30 days delay: x users, 31-60 days delay: y users etc
Based on the example above here would be the expected result:
Number of users with no delayed jobs: 2 (users 1 & 4)
1-30 days delay: 2 (users 2 & 5)
31-60 days delay: 0
Over 60 days delay: 1 (user 3)
I'm currently only showing the number of users that got no delayed jobs
SELECT u.user_id
FROM users u
LEFT JOIN (
SELECT j.user_id AS completed
FROM jobs j
LEFT JOIN last_update lu
ON lu.job_id = j.job_id
WHERE j.job_date <= CURDATE()
AND lu.update_id IS NULL
) AS cj
ON u.user_id = cj.completed
WHERE cj.completed IS NULL
You can first join the three tables, aggregate by user_id and compute, for each user
how many unfinished jobs they have
how many unfinished jobs they have within the last 30 days
how many unfinished jobs they have within the last 31-60 days
Then, you can add another level of aggreation and count how many users meet each criteria.
Query:
select
sum(cnt_jobs_unfinished = 0) cnt_users_no_unfinished_jobs,
sum(cnt_jobs_unfinished_30d > 0) cnt_users_unfinished_30d,
sum(cnt_jobs_unfinished_31_60d > 0) cnt_users_unfinished_31_60d
from (
select
u.user_id,
sum(l.job_id is null) cnt_jobs_unfinished,
sum(
l.job_id is null
and j.date >= curdate() - interval 30 day
) cnt_jobs_unfinished_30d,
sum(
l.job_id is null
and j.date < curdate() - interval 30 day
and j.date >= curdate() - interval 60 day
) cnt_jobs_unfinished_31_60d
from users u
inner join scheduled_jobs j
on j.date <= curdate()
and j.user_id = u.user_id
left join last_update l
on l.job_id = j.job_id
group by u.user_id
) t
Demo on DB Fiddle
cnt_users_no_unfinished_jobs | cnt_users_unfinished_30d | cnt_users_unfinished_31_60d
---------------------------: | -----------------------: | --------------------------:
2 | 2 | 1
Note: I had to modify your sample data so job 8, for user 3, has a date within 30-60 days, as it was not the case in your original data).
You can run the subquery independantly to see what it returns:
user_id | cnt_jobs_unfinished | cnt_jobs_unfinished_30d | cnt_jobs_unfinished_31_60d
------: | ------------------: | ----------------------: | -------------------------:
1 | 0 | 0 | 0
2 | 1 | 1 | 0
3 | 1 | 0 | 1
4 | 0 | 0 | 0
5 | 1 | 1 | 0

selecting only newest row with specific value

Table:
person | borrow_date | is_borrowed | SN | date | id
1 | 2019-01-10...| 1 | 20 |2019-01-10...| 6
3 | 2019-01-09...| 3 | 10 |2019-01-09...| 5
1 | 2019-01-08...| 1 | 10 |2019-01-08...| 4
2 | 2019-01-08...| 1 | 10 |2019-01-08...| 3
1 | NULL | 2 | 20 |2019-01-07...| 2
1 | NULL | 2 | 10 |2019-01-07...| 1
My wanted output is to select newest rows where "is_borrowed" equals 1 and grouped by SN, so that when the query is executed with person=2 or person=3 then it would retrieve empty set. Whereas for person=1 it would give back two rows.
Wanted output (where person=1):
person | borrow_date | is_borrowed | SN | date |id
1 | 2019-01-10...| 1 | 20 | 2019-01-10...|6
1 | 2019-01-08...| 1 | 10 | 2019-01-08...|4
Wanted output (where person=2):
EMPTY SET
Wanted output (where person=3):
EMPTY SET
This is my current query and it sadly doesn't work.
SELECT a.SN, a.is_borrowed,a.max(date) as date, a.person
FROM table a
INNER JOIN (SELECT SN, MAX(date) as date, osoba from table where person like
"2" group by SN) as b
ON a.SN=b.SN and a.date=b.date
WHERE a.person like "2" and a.is_borrowed=1
If I correctly understood you from the question and the comment you made under it, here's one way to do it without specifying the person:
select *
from TableName as p
inner join (select max(borrow_date) as borrow_date,
SN
FROM TableName
where is_borrowed = 1
group by SN) as p2
on p.borrow_date = p2.borrow_date and p.SN = p2.SN
This should give you the result you're looking for. Here's a demo.
Note that I had to change the borrowed_date values in the table since yours contain hours and minutes while I didn't add those.
You can always specify it for each person by adding a where clause after the join.
select p.person,
p.borrow_date,
p.is_borrowed,
p.SN,
p.date,
p.id
from TableName as p
inner join (select max(borrow_date) as borrow_date,
SN
FROM TableName
where is_borrowed = 1
group by SN) as p2
on p.borrow_date = p2.borrow_date and p.SN = p2.SN
where p.person = '1'
Output:
person | borrow_date | is_borrowed | SN | date | id
1 | 2019-01-10 | 1 | 20 | 2019-01-10 | 6
1 | 2019-01-08 | 1 | 10 | 2019-01-08 | 4
While where p.person = '2' and where p.person = '3' will return empty sets.

How can I get a history like query on MySQL?

I'd like a little help here.
I'm building a database in MySQL where I will have a bunch of different activities. Each activity is part of a list.
So, I have the following tables on my database.
List
id
name
Activity
id
name
idList (FK to List)
I also want to know when each activity is finished (you can finish the same activity many times). To accomplish that, I have another table:
History
date
idActivity (FK to activity)
When the user finishes an activity, I add the id of this activity and the current time the activity was finished, to the History table.
I want to get the entire list with the date it was finished. When an activity has not been finished, I want it to show the date as null.
But, getting the list just once is easy. A simple Left Outer Join will do the trick. My issue here is that I want to get the ENTIRE list everytime a date appears on the history table.
This is what I'm looking for:
List:
id | name
1 | list1
Activity:
id | name | idList
1 | Activity1 | 1
2 | Activity2 | 1
3 | Activity3 | 1
4 | Activity4 | 1
5 | Activity5 | 1
6 | Activity6 | 1
History:
date | idActivity
17/07/14 | 1
17/07/14 | 3
17/07/14 | 4
17/07/14 | 6
16/07/14 | 2
16/07/14 | 3
16/07/14 | 5
Expected Result:
idActivity | idList | activityName | date
1 | 1 | Activity1 | 17/07/14
2 | 1 | Activity2 | NULL
3 | 1 | Activity3 | 17/07/14
4 | 1 | Activity4 | 17/07/14
5 | 1 | Activity5 | NULL
6 | 1 | Activity6 | 17/07/14
1 | 1 | Activity1 | NULL
2 | 1 | Activity2 | 16/07/14
3 | 1 | Activity3 | 16/07/14
4 | 1 | Activity4 | NULL
5 | 1 | Activity5 | 16/07/14
6 | 1 | Activity6 | NULL
The "trick" is to use a CROSS JOIN (or semi-cross join) operation with a distinct list of dates from the history table, to produce the set of rows you want to return.
Then a LEFT JOIN (outer join) to the history table to find the matching history rows.
Something like this:
SELECT a.id AS idActivity
, a.idList AS idList
, a.name AS activityName
, h.date AS `date`
FROM activity a
CROSS
JOIN ( SELECT s.date
FROM history s
GROUP BY s.date
) r
LEFT
JOIN history h
ON h.idActivity = a.id
AND h.date = r.date
ORDER
BY r.date
, a.id
That query gets the six rows from activity, and two rows (distinct values of date) from history (inline view aliased as r). The CROSS JOIN operation matches each of the six rows with each of the two rows, to produce a Cartesian product of 12 rows.
To get the rows returned in the specified order, we order by date, and then by activity.id.

How to update foreign key with the most recent associated record's id in MySQL

What is the most performant way to generate the latest_entry_id on checks table from the entries with the same user_id, with the newest start_date that is prior to create_date of the check?
Before:
checks table
id | user_id | create_date | latest_entry_id
------------------------------------------------------
1 | 1 | 2012-01-01 | NULL
2 | 2 | 2012-01-01 | NULL
entries table
id | user_id | start_date
-------------------------------------
1 | 1 | 2012-02-01
2 | 1 | 2011-01-01
3 | 2 | 2011-09-01
4 | 2 | 2011-10-01
After:
checks table
id | user_id | create_date | latest_entry_id
------------------------------------------------------
1 | 1 | 2012-01-01 | 2
2 | 2 | 2012-01-01 | 4
I think this can make it work:
UPDATE checks c
INNER JOIN (
SELECT e.user_id,
MAX(e.id) AS ID
FROM entries e
INNER JOIN checks c1 ON e.user_id = c1.user_id
AND e.start_date < c1.create_date
GROUP BY e.user_id
) a ON a.user_id = c.user_id
SET c.latest_entry_id = a.id;
sqlfiddle demo
p.s. Your second row in your expected results is not consistent with your requirements. latest_entry_id should be 4, not 3.
The best query I came up with is this
Update checks INNER JOIN
(
SELECT checks.id AS c_id, MAX(entries.start_date) AS max_start_date
FROM checks LEFT OUTER JOIN entries ON checks.user_id = entries.user_id
WHERE entries.start_date < checks.create_date
GROUP BY checks.id
) AS tmp
ON checks.id = tmp.c_id
LEFT JOIN entries
ON tmp.max_start_date = entries.start_date AND checks.user_id = entries.user_id
SET checks.latest_entry_id = entries.id
This query is performant and avoid running one subquery per check. If you know of another performant way to do this update to get the same results, I would like to hear your way.

MySQL pro rata counting of distinct values in last period

This one is quite tricky i've been scratching my head all day.
I have a table containing billing periods
ID | DATEBEGIN | DATEEND | CUSTOMER_ID
=======+===========+==========+=================
1 | 1/1/2011 | 30/1/2011 | 1
I have a table containing 'sub customers'
ID | NAME
=======+===========
1 | JOHN
2 | JACK
3 | Elwood
I have a table containing items purchased on a subscription (wholesale account
ID | DATE | SUBCUSTOMER_ID | CUSTOMER ID
=======+===========+================+==============
1 | 15/1/2011 | 1 | 1
2 | 18/1/2011 | 1 | 1
3 | 25/1/2011 | 2 | 1
4 | 28/1/2011 | 3 | 1
So I want to count 'credits' to deduct from their account. So the subscription is per 'sub customer'.
So at the end of the billing period (30/1/2011 from first table). I need to count the distinct sub customers (there are 3). They are charged pro-rata from the first purchase they make during the billing period.
Days Having Subscription | SUBCUSTOMER_ID | Pro Rata Rate | CUSTOMER_ID
==========================+===================+==================+==============
3 | 3 | 3/30 | 1
6 | 2 | 6/30 | 1
16 | 1 | 16/30 | 1
The output should therefore be
CUSTOMER_ID | BILLING CREDITS
============+========================
1 | 25/30
I have to count it pro rata, previously it would be unfair to bill a full period even if they purchase an item 1 day prior to the billing date
SELECT customerId, SUM(DATEDIFF(dateend, fp) + 1) / (DATEDIFF(dateend, datestart) + 1)
FROM (
SELECT b.*, MIN(date) AS fp
FROM billing b
JOIN purchase p
ON p.customerId = b.customerId
AND p.date >= b.datebegin
AND p.date < b.dateend + INTERVAL 1 DAY
GROUP BY
b.id, p.subcustomerId
) q
GROUP BY
customerId
SELECT customer_id, sum(pro_rated_date) / max(days_in_billing_cycle)
FROM (SELECT min(date),
subcustomer_id,
datebegin,
dateend,
items.customer_id,
datediff(dateend, datebegin) + 1 AS days_in_billing_cycle,
datediff(dateend, min(date)) AS pro_rated_date
FROM items
JOIN
billing_period
ON items.date BETWEEN billing_period.datebegin
AND billing_period.dateend
AND items.customer_id = billing_period.customer_id
GROUP BY subcustomer_id) AS billing
GROUP BY customer_id