SQL counting days from periods - mysql

My problem is that I want to sum periods of date from only may, but as you can see below some of events starts before first day of may and some end after last may day.
There is my code:
SELECT * FROM rooms p, bookings r WHERE p.id_room = r.id_room group by
r.id_room having
case
WHEN (month(r.start_date) = 5 AND month(r.end_date) = 5) THEN
sum(datediff(r.end_date, r.start_date)) < 21
WHEN (month(r.start_date) < 5 AND month(r.end_date) = 5) THEN
sum(datediff(r.end_date, '2022-05-01 12:00:00')) < 21
WHEN (month(r.start_date) = 5 AND month(r.end_date) > 5) THEN
sum(datediff('2022-05-31 12:00:00', r.start_date)) < 21
END;
Edit 1
I will only talk about example on bottom.
E.g.
date_Start - June 3
date_end - June 15
GREATEST(date_start, '2022-05-1') returns June 3
LEAST(date_end, '2022-05-31') retruns may 31
DATEDIFF(date_end, date_start) returns -3 and it is still counted as day from may while it should be skipped

Simplify the HAVING clause by using the functions LEAST() and GREATEST():
SELECT r.id_room
FROM rooms r LEFT JOIN bookings b
ON b.id_room = r.id_room
AND b.end_date > '2022-05-01 12:00:00' AND b.start_date < '2022-05-31 12:00:00'
GROUP BY r.id_room
HAVING SUM(COALESCE(DATEDIFF(
LEAST(b.end_date, '2022-05-31 12:00:00'),
GREATEST(b.start_date, '2022-05-01 12:00:00')
), 0)) < 21;
Also, use a proper join.

Related

MySQL get different SUMs based on dates

I'm trying to create a Sql query where I can sums for 4 different date range.
SELECT t.TenantID, t.TenantFName, t.TenantLName, u.UnitName,
TotalDebit, HousingDebit, TotalCredit, HousingCredit
FROM Tenants t
JOIN Units u ON t.UnitID = u.UnitID
LEFT JOIN (
Select
TenantID,
SUM(CASE WHEN TransactionTypeID = 1 AND ChargeTypeID != 6 THEN TransactionAmount ELSE 0 END) AS TotalDebit,
From TenantTransactions
Where TenantTransactionDate BETWEEN /* Here is my issue */
Group By TenantID
) sums ON sums.TenantID = t.TenantID
Where t.Prospect = 2
AND t.PropertyID = 10
I am trying to return 4 sums:
For the last 30 days
For last 30-60 days
For last 60-90 days
Greater than 90 days ago
Does it make sense?
Thanks
You're looking for totals based on the age of the transaction. We can get that by subtracting the transaction date from today to get a result in days:
datediff(now(), TenantTransactionDate)
You want to group that into period by 30 days, so we use integer division to get a period:
datediff(now(), TenantTransactionDate) DIV 30
And lastly, you want everything over 90 days grouped together:
if(datediff(now(), TenantTransactionDate)<90, datediff(now(), TenantTransactionDate) DIV 30, 3)
This last expression returns a value of 0, 1, 2 or 3. We can use that to group the transactions, and total them:
SELECT TenantID,
sum(TransactionAmount) as totalDebit,
if(datediff(now(), TenantTransactionDate)<90, datediff(now(), TenantTransactionDate) DIV 30, 3) as period
FROM TenantTransactions
where TransactionTypeID=1 and chargeTypeID != 6
group by tenantID, period
order by tenantID, period
Now you should be able to plug that back into your original query, and sort them:
SELECT t.TenantID, t.TenantFName, t.TenantLName, u.UnitName,
TotalDebit, HousingDebit, TotalCredit, HousingCredit
FROM Tenants t
JOIN Units u ON t.UnitID = u.UnitID
LEFT JOIN (
SELECT TenantID,
sum(TransactionAmount) as totalDebit,
if(datediff(now(), TenantTransactionDate)<90, datediff(now(), TenantTransactionDate) DIV 30, 3) as period
FROM TenantTransactions
where TransactionTypeID=1 and chargeTypeID != 6
group by tenantID, period
) sums ON sums.TenantID = t.TenantID
Where t.Prospect = 2
AND t.PropertyID = 10
order by t.tenantID, sums.period

How can I create this query?

I have this MySQL Query here:
SELECT
COUNT(*) ReleasePerMonth,
d.name as DevGroup_REGION
FROM
release_summary r
inner join
gti_server_info g
on r.gti_server_id = g.gti_server_id
inner join
dev_group d
on d.dev_group_id = g.dev_group_id
WHERE
r.testingFinishedOn_timestamp >= '2020-05-01 00:00:00'
AND r.testingFinishedOn_timestamp <= '2020-05-31 00:00:00'
AND r.test_type != 14
GROUP BY
d.name ;
Now, I want this to run for every month. That is,
r.testingFinishedOn_timestamp >= '2019-01-01 00:00:00'
AND r.testingFinishedOn_timestamp <= '2019-01-31 00:00:00'
and
r.testingFinishedOn_timestamp >= '2019-05-02 00:00:00'
AND r.testingFinishedOn_timestamp <= '2019-02-31 00:00:00'
Till end of year. Currently, I am doing this manually. Is there any way I can do it in an automated manner?
Clarification:
I'd want 12 seperate tables for each of the 12 months.
Try the following:
SELECT
YEAR(r.testingFinishedOn_timestamp) year,
MONTH(r.testingFinishedOn_timestamp) month,
COUNT(*) ReleasePerMonth,
d.name as DevGroup_REGION
FROM
release_summary r
inner join
gti_server_info g
on r.gti_server_id = g.gti_server_id
inner join
dev_group d
on d.dev_group_id = g.dev_group_id
WHERE
r.test_type != 14
GROUP BY
YEAR(r.testingFinishedOn_timestamp),
MONTH(r.testingFinishedOn_timestamp),
d.name
ORDER BY year, month;
A possible solution is to query data for the entire year and group by MONTH(testingFinishedOn_timestamp).
I added the query below but it's not tested:
SELECT
MONTH(r.testingFinishedOn_timestamp) ReleaseMonth,
COUNT(*) ReleasePerMonth,
d.name as DevGroup_REGION
FROM
release_summary r
inner join
gti_server_info g
on r.gti_server_id = g.gti_server_id
inner join
dev_group d
on d.dev_group_id = g.dev_group_id
WHERE
r.testingFinishedOn_timestamp >= '2020-01-01 00:00:00'
AND r.testingFinishedOn_timestamp < '2021-01-01 00:00:00'
AND r.test_type != 14
GROUP BY
d.name, MONTH(r.testingFinishedOn_timestamp);
ORDER BY
MONTH(r.testingFinishedOn_timestamp), d.name
Based on the documentation available, MONTH() function returns the number of the month, for instance for January returns 1.
If you want to have the name of the month you case use MONTHNAME() function instead of Month().
You can try the below query - using last_day()
SELECT year(r.testingFinishedOn_timestamp),month(r.testingFinishedOn_timestamp),
COUNT(*) ReleasePerMonth
FROM
release_summary r
inner join
gti_server_info g
on r.gti_server_id = g.gti_server_id
inner join
dev_group d
on d.dev_group_id = g.dev_group_id
WHERE
r.testingFinishedOn_timestamp >= date_add(date_add(LAST_DAY(now()),interval 1 DAY),interval -12 MONTH)
AND r.testingFinishedOn_timestamp <= LAST_DAY(now())
AND r.test_type != 14
GROUP BY
year(r.testingFinishedOn_timestamp),month(r.testingFinishedOn_timestamp) ;

If statement in mysql query with inner join

I'm currently showing users that got unfinished jobs and based on the results I run a while loop and a switch case statement to come with the final results. I'm wondering if it is possible to move that statement in the mysql 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,
sum(cnt_jobs_unfinished_61_90d > 0) cnt_users_unfinished_61_90d,
sum(cnt_jobs_unfinished_90d_more > 0) cnt_users_unfinished_90d_more
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,
sum(
l.job_id is null
and j.date < curdate() - interval 60 day
and j.date >= curdate() - interval 90 day
) cnt_jobs_unfinished_61_90d,
sum(
l.job_id is null
and j.date < curdate() - interval 90 day
) cnt_jobs_unfinished_90d_more
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
Here is the dbfiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=d2f217e074a391d8b5f769e08b1d2c87
As you can see because a user got both an unfinished job between 61-90 days and 90+ days the final table shows both results which is a mistake. The correct one would be 61-90 days: 0 users and 90+: 1 user.
This is what I came up with (fiddle):
SELECT
sum(IF(t.nb_days IS NULL OR t.nb_days <= 0, 1, 0)) cnt_users_no_unfinished_jobs,
sum(IF(t.nb_days > 0 AND t.nb_days <= 30, 1, 0)) cnt_users_unfinished_30d,
sum(IF(t.nb_days > 30 AND t.nb_days <= 60, 1, 0)) cnt_users_unfinished_31_60d,
sum(IF(t.nb_days > 60 AND t.nb_days <= 90, 1, 0)) cnt_users_unfinished_61_90d,
sum(IF(t.nb_days > 90, 1, 0)) cnt_users_unfinished_90d_more
FROM users u
LEFT JOIN (
SELECT j.user_id, DATEDIFF(curdate(), MIN(j.date)) AS nb_days
FROM scheduled_jobs j
LEFT JOIN last_update l
ON l.job_id = j.job_id
WHERE l.job_id IS NULL
GROUP BY j.user_id
) AS t
ON u.user_id = t.user_id
Instead of counting how many jobs are in each range for each user, I'm only looking at their oldest unfinished job, and extracting the number of days since the deadline: DATEDIFF(curdate(), MIN(j.date)) AS nb_days).
Then this result is LEFT JOIN-ed to users (that way, in case a user doesn't have any unfinished jobs, he'll still show up in the cnt_users_no_unfinished_jobs column by checking for NULL values)
Finally, it's just a matter of SELECT-ing how many nb_days are in each range.

Return all results where the purchase made date is within 7 days of the registration date

I have so far produced the below code to try and pull the relevant data together.
However with the '+ 7' function it is producing the below issue.
Registration date = '2018-01-01'
It is pulling back purchase dates of 2018-04-08, i.e as long as the day of the day is 7 days greater then it is being deemed acceptable. When in reality what I ma looking for is any all purchases within 7 days of the registration date.
Any advice/help would be greatly appreciated.
select *
from purchases b
inner join registrations r
on r.customer_id = b.customer_id
and day(b.purchase_date) between d(r.account_opening_date) and day(r.account_opening_date) + 7
and r.account_opening_date >= '2018-01-01 00:00:00.0'
Use DATE_ADD instead, DAY() will not work well when dates to compare are in different months.
AND b.purchase_date >= r.account_opening_date AND
b.purchase_date <= DATE_ADD(r.account_opening_date INTERVAL 7 Day)
you can use window function
select *
partition by (order by account_opening_date rows 6 preceding)
from purchases p, registrations r
where p.customer_id = r.customer_id
and r.account_opening_date >= '2018-01-01 00:00:00.0'
You can simply use +:
select *
from purchases b inner join
registrations r
on r.customer_id = b.customer_id and
b.purchase_date >= r.account_opening_date and
b.purchase_date < r.account_opening_date + interval 7 day
where r.account_opening_date >= '2018-01-01';
If you want to do this without the time component, then use the date() function:
on r.customer_id = b.customer_id and
b.purchase_date >= r.account_opening_date and
b.purchase_date <= date(r.account_opening_date) + interval 7 day
where r.account_opening_date >= '2018-01-01'
Whether you use < or <= depends on the exactly interpretation of "within 7 days". This version assumes that you really want 7-8 days.
It sounds like you need to use DATE_ADD and DATE MySQL functions.
select *
from purchases b
inner join registrations r
on r.customer_id = b.customer_id
and date(b.purchase_date) between date(r.account_opening_date) and date_add(r.account_opening_date interval 7 day)
and r.account_opening_date >= '2018-01-01 00:00:00.0'

MySQL : Different behaviors depending on WHERE result

I'm modifying an existing project. I want to group 3 mysql request in one.
These 3 request have the same selected data, only the WHERE change.
here's one of the request for exemple :
SELECT COUNT(seg.my_seg1) FROM (
SELECT COUNT(DISTINCT cp.conference_id) as my_seg1 FROM A.Account a
INNER JOIN A.ConferenceParticipant cp ON a.account_id = cp.user_id
INNER JOIN A.Conference cf ON cf.id = cp.conference_id
WHERE cf.`status` = 0
AND DATE_SUB(CURDATE(), INTERVAL 30 DAY) <= cf.creation_timestamp
GROUP BY a.account_id) as seg
WHERE seg.my_seg1 >= 30
The 2 other requests are exactly the same except :
WHERE seg.my_seg1 >= 11 AND seg.my_seg1 <= 30;
and :
WHERE seg.my_seg1 >= 30;
So my question is how can I get 3 different values depending on the WHERE result in the same request ?
Like this you'll have 3 virtual columns:
SELECT
COUNT(IF(seg.my_seg1 >= 30, 1, 0)) AS res1,
COUNT(IF(seg.my_seg1 >= 11 AND seg.my_seg1 < 30, 1, 0)) AS res2
FROM (
SELECT COUNT(DISTINCT cp.conference_id) as my_seg1
FROM A.Account a
JOIN A.ConferenceParticipant cp ON a.account_id = cp.user_id
JOIN A.Conference cf ON cf.id = cp.conference_id
WHERE
cf.`status` = 0
AND DATE_SUB(CURDATE(), INTERVAL 30 DAY) <= cf.creation_timestamp
GROUP BY a.account_id
) AS seg
But you have to revise your filters, you talk about 3 but I only see 2 different ones.