Fetch data with open status but no closed status in mysql - mysql

I have a logs table which consists of data in which user has opened (1) or closed (2) status. My problem is I need to get the user with registration_id that has open status but no close status.
Logs Table
id | registration_id | user_id | status | created_at
1 | 1 | 1 | 1 | 2021-02-22 8:00:00
2 | 1 | 1 | 2 | 2021-02-22 8:30:00
3 | 2 | 1 | 1 | 2021-02-22 8:30:00
4 | 2 | 1 | 2 | 2021-02-22 9:00:00
5 | 3 | 1 | 1 | 2021-02-22 9:00:00
6 | 4 | 2 | 1 | 2021-02-22 8:00:00
7 | 4 | 2 | 2 | 2021-02-22 8:30:00
Expected Output
id | registration_id | user_id | status | created_at
5 | 3 | 1 | 1 | 2021-02-22 9:00:00
Since the registration_id = 3 with user_id = 1 don't have a closed status. Also, there's a lot of logs between open and closed, I just simplified it in my question so, if you're planning to just count the registration_id if it's equals to 1. It doesn't work.
What I've tried is subtracting the closed created_at - open created_at and if the total is less than or equal 0, it doesn't have a close status but I know there's a better way to get what I wanted because my current query is very slow.
SELECT
user_id,
registration_id,
date,
SUM(timestampdiff(minute, openTime, closedTime)) AS total
FROM (
SELECT
user_id,
date(created_at) as `date`,
created_at as openTime,
registration_id,
coalesce(
(
SELECT created_at FROM logs t2
WHERE t1.registration_id = t2.registration_id
AND t1.created_at < t2.created_at
AND t1.user_id = t2.user_id
AND status = 2
ORDER BY t1.created_at
LIMIT 1
),
date_add(t1.created_at, interval -1 minute)
) AS closedTime
FROM logs t1
WHERE status = 1
) a
GROUP BY a.user_id, registration_id
HAVING total <= 0;

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

Get return for the latest day

I am running a mysql - 10.1.39-MariaDB - mariadb.org binary- database.
I am having the following table:
| id | date | product_name | close |
|----|---------------------|--------------|-------|
| 1 | 2019-08-07 00:00:00 | Product 1 | 806 |
| 2 | 2019-08-06 00:00:00 | Product 1 | 982 |
| 3 | 2019-08-05 00:00:00 | Product 1 | 64 |
| 4 | 2019-08-07 00:00:00 | Product 2 | 874 |
| 5 | 2019-08-06 00:00:00 | Product 2 | 739 |
| 6 | 2019-08-05 00:00:00 | Product 2 | 555 |
| 7 | 2019-08-07 00:00:00 | Product 3 | 762 |
| 8 | 2019-08-06 00:00:00 | Product 3 | 955 |
| 9 | 2019-08-05 00:00:00 | Product 3 | 573 |
I want to get the following output:
| id | date | product_name | close | daily_return |
|----|---------------------|--------------|-------|--------------|
| 4 | 2019-08-07 00:00:00 | Product 2 | 874 | 0,182679296 |
| 1 | 2019-08-07 00:00:00 | Product 1 | 806 | -0,179226069 |
Basically I want ot get the TOP 2 products with the highest return. Whereas return is calculated by (close_currentDay - close_previousDay)/close_previousDay for each product.
I tried the following:
SELECT
*,
(
CLOSE -(
SELECT
(t2.close)
FROM
prices t2
WHERE
t2.date < t1.date
ORDER BY
t2.date
DESC
LIMIT 1
)
) /(
SELECT
(t2.close)
FROM
prices t2
WHERE
t2.date < t1.date
ORDER BY
t2.date
DESC
LIMIT 1
) AS daily_return
FROM
prices t1
WHERE DATE >= DATE(NOW()) - INTERVAL 1 DAY
Which gives me the return for each product_name.
How to get the last product_name and sort this by the highest daily_return?
Problem Statement: Find the top 2 products with the highest returns on the latest date i.e. max date in the table.
Solution:
If you have an index on date field, it would be super fast.
Scans table only once and also uses date filter(index would allow MySQL to only process rows of given date range only.
A user-defined variable #old_close is used to find the return. Note here we need sorted data based on product and date.
SELECT *
FROM (
SELECT
prices.*,
CAST((`close` - #old_close) / #old_close AS DECIMAL(20, 10)) AS daily_return, -- Use #old_case, currently it has value of old row, next column will set it to current close value.
#old_close:= `close` -- Set #old_close to close value of this row, so it can be used in next row
FROM prices
INNER JOIN (
SELECT
DATE(MAX(`date`)) - INTERVAL 1 DAY AS date_from, -- if you're not sure whether you have date before latest date or not, can keep date before 1/2/3 day.
#old_close:= 0 as o_c
FROM prices
) AS t ON prices.date >= t.date_from
ORDER BY product_name, `date` ASC
) AS tt
ORDER BY `date` DESC, daily_return DESC
LIMIT 2;
Another version which doesn't depend on this date parameter.
SELECT *
FROM (
SELECT
prices.*,
CAST((`close` - #old_close) / #old_close AS DECIMAL(20, 10)) AS daily_return, -- Use #old_case, currently it has value of old row, next column will set it to current close value.
#old_close:= `close` -- Set #old_close to close value of this row, so it can be used in next row
FROM prices,
(SELECT #old_close:= 0 as o_c) AS t
ORDER BY product_name, `date` ASC
) AS tt
ORDER BY `date` DESC, daily_return DESC
LIMIT 2
You can do it with a self join:
select
p.*,
cast((p.close - pp.close) / pp.close as decimal(20, 10)) as daily_return
from prices p left join prices pp
on p.product_name = pp.product_name
and pp.date = date_add(p.date, interval -1 day)
order by p.date desc, daily_return desc, p.product_name
limit 2
See the demo.
Results:
| id | date | product_name | close | daily_return |
| --- | ------------------- | ------------ | ----- | ------------ |
| 4 | 2019-08-07 00:00:00 | Product 2 | 874 | 0.182679296 |
| 1 | 2019-08-07 00:00:00 | Product 1 | 806 | -0.179226069 |

How can I count the number of messages to different people per day?

I have a table like this:
// messages
+----+----------------+-----------+-------------+-------------+
| id | content | sender_id | receiver_id | date_time |
+----+----------------+-----------+-------------+-------------+
| 1 | whatever1 | 1 | 3 | 1521097240 |
| 2 | whatever2 | 3 | 1 | 1521097241 |
| 3 | whatever3 | 1 | 3 | 1521097242 |
| 4 | whatever4 | 1 | 4 | 1521097243 |
| 5 | whatever5 | 1 | 5 | 1521097244 |
| 6 | whatever6 | 5 | 1 | 1521097245 |
+----+----------------+-----------+-------------+-------------+
Now I need to count the number of messages that user sender_id = 1 to different people. So assuming all those row are in the past day, then the result should be 3. Because sender_id = 1 have sent messages to receiver_ids = 3,4,5. How can I do that count in Mysql?
Here is what I've tried:
SELECT count(1) as sent_messages_num
FROM messages
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
So all I need it adding a grouping. But not sure how should I do that?
You need to use distinct in COUNT
SELECT count(distinct receiver_id) as sent_messages_num
FROM users
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
Try using the following query:
SELECT count(*) as sent_messages_num, receiver_id
FROM users
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))
GROUP BY receiver_id
This will give you the number of messages per receiver. After I read the answer again, I think the following might suit better:
SELECT count(DISTINCT receiver_id) as sent_messages_num
FROM users
WHERE sender_id = 1
AND date_time > UNIX_TIMESTAMP(DATE_SUB(now(), INTERVAL 1 DAY))

MySQL Select three results for active, expired and unactive by comparing date

I have this table
card_id | activated | expiry_date
---------------------------------------------
1 | 0 | 19-01-2015 19:00
2 | 1 | 20-10-2014 08:00
3 | 1 | 04-12-2014 22:30
4 | 0 | 12-11-2015 10:00
5 | 1 | 01-02-2016 03:30
6 | 0 | 16-08-2015 16:00
7 | 1 | 13-06-2015 12:00
8 | 1 | 03-02-2015 01:30
9 | 0 | 18-02-2016 15:00
10 | 1 | 10-05-2015 12:30
I have activated and not-activated cards, then I also have their expiry dates.
I wish to select these results into three categories (unactivated, active and expired) depending on the status of the activated field and compare the expiry date with the present time THEN LIMIT 3 PER CATEGORY.
The expected outcome for this case will be:
Unactivated
card_id | activated | expiry_date
---------------------------------------------
1 | 0 | 19-01-2015 19:00
4 | 0 | 12-11-2015 10:00
6 | 0 | 16-08-2015 16:00
Active
card_id | activated | expiry_date
---------------------------------------------
5 | 1 | 01-02-2016 03:30
7 | 1 | 13-06-2015 12:00
8 | 1 | 03-02-2015 01:30
Expired
card_id | activated | expiry_date
---------------------------------------------
2 | 1 | 20-10-2014 08:00
3 | 1 | 04-12-2014 22:30
How can I do this preferably using just one MySQL query?
Unactivated:
SELECT * FROM your_table WHERE activated = 0 LIMIT 3;
Note though, that a LIMIT without an ORDER BY is meaningless, as there's no order in a relational database, unless you specify it.
Active:
SELECT * FROM your_table WHERE activated = 1 AND expiry_date > CURDATE() LIMIT 3;
Expired:
SELECT * FROM your_table WHERE activated = 1 AND expiry_date < CURDATE() LIMIT 3;
If you don't have to limit each query, I'd recommend to do it all in one query:
SELECT
CASE WHEN activated = 0 THEN 'unactivated'
WHEN active = 1 AND expiry_date > CURDATE() THEN 'active'
WHEN active = 1 AND expiry_date < CURDATE() THEN 'expired'
END AS status,
yt.* FROM your_table yt;
You can also limit each status and do it in one query, but this would be not trivial (you'd have to use variables) and it would result in a full table scan anyway. So you don't gain anything. Therefore I'd recommend to do it in three queries. Also the above query will do a full table scan. I just posted it to give you an idea...

Getting UNIX TIME with number

MySQL Query:
SELECT c.day,
COUNT(site_id)
FROM calendar c
LEFT JOIN
(
SELECT *
FROM visitors
WHERE site_id = 16
) d ON DAYOFMONTH(d.created) = c.day
WHERE c.day BETWEEN DAYOFMONTH('2012-10-01') AND DAYOFMONTH('2012-10-31')
GROUP BY c.day
ORDER BY c.day
My Tables
Calendar
id | day
---------
1 | 1
2 | 2
3 | 3
...
31 | 31
Visitors
id | site_id | created
-----------------------------------
1 | 16 | 2012-10-18 11:14:39
2 | 16 | 2012-10-18 11:15:17
3 | 11 | 2012-10-18 11:49:14
4 | 11 | 2012-10-18 11:49:43
5 | 16 | 2012-10-19 11:54:37
6 | 1 | 2012-10-19 05:56:31
7 | 2 | 2012-10-19 05:57:56
I used the above query to retrieve a daily result of visits to a site. The query solved my question here.
Results:
day | COUNT(*)
-------------
1 | 0
2 | 0
3 | 0
....
18 | 2
19 | 1
...
31 | 0
Although, now, I am having problems retrieving UNIX_TIMESTAMP from the day which I need for graphing purposes.
How do I retrieve it from the c.day in the query?
Edited:
SELECT
UNIX_TIMESTAMP('2012-10-01' + INTERVAL c.day - 1 DAY) unix_ts_day,
COUNT(v.site_id)
FROM
calendar c
LEFT JOIN (
SELECT * FROM visitors
WHERE site_id = 16 AND DATE(created) BETWEEN '2012-10-01' AND '2012-10-31'
) v
ON DAYOFMONTH(v.created) = c.day
GROUP BY
unix_ts_day