Recursively calculate records within a time interval in mysql - mysql

I have a table where records will be getting inserted every 4 hours on a daily basis. If the record was not inserted for continuous 4 hours, I need to insert a log into another table. Below is the table schema.
Id DocPathid CreatedAt
1 1 2021-04-02 00:00:00
2 1 2021-04-02 04:00:00
3 1 2021-04-02 09:00:00
4 1 2021-04-02 12:00:00
5 1 2021-04-02 16:00:00
6 1 2021-04-02 20:00:00
7 1 2021-04-02 24:00:00
In the above case, there was no records inserted within a interval of 4hours (i.e. between 2021-04-02 04:00:00 & 2021-04-02 09:00:00). The query should return no. of failure count (in this case it is failed for 1 time).
Is there a way to achieve this in MySQL?

You can do something like this.
select count(1)
from (
select id, CreatedAt , timestampdiff(hour, CreatedAt
, lead(CreatedAt,1) over (partition by DocPathid order by CreatedAt) ) as hour
from Table1
) t
where hour >4
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=9b0c631145422dbccd2ea23f0a7d2011

Related

Getting average of count(column) grouped by date, but meeting certain conditions

Data sample:
dtime
id
2021-01-01 06:00:00
1
2021-01-01 06:00:00
2
2021-01-01 06:00:00
3
...
...
2021-01-01 12:00:00
1
2021-01-01 12:00:00
2
2021-01-01 12:00:00
3
...
...
...
...
2021-01-12 20:00:00
1
2021-01-12 20:00:00
2
2021-01-12 20:00:00
3
In the real dataset, ids are between 1 and 9999, dtime are every 5 minutes, 24h/day, and I'd like to sample only at certain times (eg 06, 12, 16, 20h).
The expected output is the average of count(id) values, grouped by DATE(dtime), but:
Only certain TIME(dtime) should be sampled (eg 06, 12, 16, 20h);
count(id) should ignore id that are not between 10 and 500;
count(id) should be discarded (and not considered for the average) if <3.
Output sample:
DATE(dtime)
AVG(count(id))
2021-01-01
31
2021-01-02
29
So far I've got:
SELECT dtime,count(id)
FROM cron5min
WHERE (TIME(dtime) = '06:00:00' OR TIME(dtime) = '12:00:00' OR TIME(dtime) = '16:00:00' OR TIME(dtime) = '20:00:00') AND id BETWEEN 10 AND 500 AND estado = 1
GROUP BY dtime
and then I'm using PHP to do the average and discard data according to 3.
I'm now trying to do this with a MySQL statement only, no PHP.
You need 2 levels of aggregation:
SELECT DATE(dtime) date, AVG(counter) avg_count
FROM (
SELECT dtime, COUNT(id) counter
FROM cron5min
WHERE TIME(dtime) IN ('06:00:00', '12:00:00', '16:00:00', '20:00:00')
AND id BETWEEN 10 AND 500
AND estado = 1
GROUP BY dtime
HAVING counter >= 3
) t
GROUP BY date

Counting club memberships in SQL

EDIT: I have added the primary key, following the comment by #Strawberry
The aim is to return the number of current members, and also the number of past memberships, on any particular date/time.
For example, suppose we have
msid id start cancelled
1 1 2020-01-01 09:00:00 null
2 2 2020-01-01 09:00:00 2020-12-31 09:00:00
3 2 2021-01-01 09:00:00 null
4 3 2020-01-01 09:00:00 2020-06-30 09:00:00
5 3 2020-02-01 09:00:00 2020-06-30 09:00:00
6 3 2020-07-01 09:00:00 null
and we want to calculate the number of members at various times, which should return as follows
Datetime Current Past <Notes - not to be returned by the query>
2020-01-01 12:00:00 3 0 -- all 3 IDs have joined earlier on this date
2020-02-01 12:00:00 3 0 -- new membership for existing member (ID 3) is not counted
2020-06-30 12:00:00 2 1 -- ID 3 has cancelled earlier on this day
2020-07-01 12:00:00 3 0 -- ID 3 has re-joined earlier on this day
2020-12-31 12:00:00 2 1 -- ID 2 has cancelled earlier on this day
2021-01-01 12:00:00 3 0 -- ID 2 has re-joined earlier on this day
An ID may either be current or past, but never both. That is, if a past member re-joins, as in the case of ID 2 and 3 above, they become current members, and are no longer past members.
Also, a member may have multiple current memberships, but they can only be counted as a current member once, as in the case of ID 3 above.
How can this be achieved in MySQL ?
Here is a db<>fiddle with the above data
Test this:
WITH
cte1 AS ( SELECT start `timestamp` FROM dt
UNION
SELECT cancelled FROM dt WHERE cancelled IS NOT NULL ),
cte2 AS ( SELECT DISTINCT id
FROM dt )
SELECT cte1.`timestamp`, COUNT(DISTINCT dt.id) current, SUM(dt.id IS NULL) past
FROM cte1
CROSS JOIN cte2
LEFT JOIN dt ON cte1.`timestamp` >= dt.start
AND (cte1.`timestamp` < dt.cancelled OR dt.cancelled IS NULL)
AND cte2.id = dt.id
GROUP BY cte1.`timestamp`
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=942e4c97951ed0929e178134ef67ce69

Select from db and limit result to where datetime is the same

I have a datetime field in my db. Now i want to select upcoming dates from NOW() based on the dates. If two datetimes is the same I want to select them. And if it's only one I want to select that date. Limit is that I never want to select more than two rows.
So if NOW() is 2021-06-18 12:00:00 row number 1 should be selected.
If NOW() is 2021-06-20 18:15:00 row number 3 and 4 should be selected.
1 2021-06-19 18:00:00
2 2021-06-20 18:00:00
3 2021-06-21 15:00:00
4 2021-06-21 15:00:00
5 2021-06-21 18:00:00
6 2021-06-21 18:00:00
I've tried
SELECT gamedate
FROM games
WHERE gamedate > NOW()
ORDER BY gamedate LIMIT 0 , 1
but that doesn't make any sense or what I want to do.
Use a subquery to get the next gamedate. Then use that in the main query to select at most two rows with that date.
SELECT id, gamedate
FROM games AS g
WHERE gamemedate = (SELECT MIN(gamedate) FROM games WHERE gamedate > NOW())
ORDER BY id
LIMIT 2

Calculate MRR Kpi without monthly invoices

I am trying to calculate mrr for each month.
The DB table 'boxes' looks like this:
project_id
product_id
payment_method_id
price
interval
booked_at
canceled_at
1
1
3
19.00
1
2020-12-01 00:00:00
NULL
1
2
3
39.00
1
2020-05-01 00:00:00
2020-11-05 19:10:27
4
1
3
39.00
12
2020-05-01 00:00:00
2020-11-05 19:10:27
Payment-Interval is in months. I need to show in KPI dashboard the mrr.
Currently I have this query:
SELECT DATE_FORMAT(booked_at, '%Y-%m') AS period, sum(price)
FROM
project_boxes
GROUP BY period;
The problem with the above query is that it doesn't show between months MRR and doesn't work with canceled boxes.
What am I missing? Any help is appreciated.

query to show items that are NOT in the list

This query returns me the list of room #077 that is occupied on specific day; how do I reverse this query and show only times that are NOT in the database between 07:00:00 and 22:00:00? (30 minutes intervals) and each class should take only 1 hour and 30 minutes
select
*
from
(select
rooms.id, rooms.number, rooms.building, rooms.capacity
from
rooms) R1,
(select
exam_schedules.room_id,
exam_schedules.day,
exam_schedules.start_time,
exam_schedules.end_time
from
exam_schedules) R2
where
R2.room_id = R1.id and R2.day = 'tuesday' and R1.number = '077'
This is the result:
ID number Bulding Capacity room_id day start_time end_time
1 077 ACT 12 1 tuesday 10:30:00 12:00:00
But I need the result that is shown below (which is pretty much shows the AVAILABLE times that are NOT occupied by exams AND could no be occupied by other exam since it might cause scheduling issues: for example if exam is already scheduled at 10.30, only 9.00 (not 9.30) should be shown since if 9.30 is shown - it will cause the conflict: 9.30+1.30 = 11.00 - but I already have the 10.30-12.00 scheduled for this room)
1 077 ACT 12 1 tuesday 07:00:00
1 077 ACT 12 1 tuesday 07:30:00
1 077 ACT 12 1 tuesday 08:00:00
1 077 ACT 12 1 tuesday 08:30:00
1 077 ACT 12 1 tuesday 09:00:00
//note that time frame from 9.30-10.30 is not available since there is a class at 10.30 scheduled already
//note that time period 10.30-<12.00 not shown since class is already scheduled for this timeframe 10.30-12.00
1 077 ACT 12 1 tuesday 12:00:00
1 077 ACT 12 1 tuesday 12:30:00
1 077 ACT 12 1 tuesday 13:00:00
1 077 ACT 12 1 tuesday 13:30:00
1 077 ACT 12 1 tuesday 14:00:00
1 077 ACT 12 1 tuesday 14:30:00
1 077 ACT 12 1 tuesday 15:00:00
1 077 ACT 12 1 tuesday 15:30:00
1 077 ACT 12 1 tuesday 16:00:00
1 077 ACT 12 1 tuesday 16:30:00
1 077 ACT 12 1 tuesday 17:00:00
1 077 ACT 12 1 tuesday 17:30:00
1 077 ACT 12 1 tuesday 18:00:00
1 077 ACT 12 1 tuesday 18:30:00
1 077 ACT 12 1 tuesday 19:00:00
1 077 ACT 12 1 tuesday 19:30:00
1 077 ACT 12 1 tuesday 20:00:00
1 077 ACT 12 1 tuesday 20:30:00
1 077 ACT 12 1 tuesday 21:00:00
1 077 ACT 12 1 tuesday 21:30:00
1 077 ACT 12 1 tuesday 22:00:00
The table with all possible timeframes does not exist in the database. Can I maybe hadcode it into the query?
You need to create the list of all possible values. You can do this with a query like:
select *
from (select '2012-01-01 7:00:00' from dual union all
select '2012-01-01 7:30:00' from dual union all . . .
) times
That is, you can use union all to put the data together. I find it easiest to use Excel to put together the SQL statements needed to make this work.
That said, a calendar/calendar-time table is something you should consider. Having a table with each possible time slot it in will probably help you in the future.
Take for example this query:
SELECT * FROM Persons WHERE Year='1965'
this sql query will return a data set containing all the rows from the 'Persons' table where all the 'Year' fields equal to '1965'. As you said, you wanted to reverse your sql query so it returns the opposite data set. This is made possible in the WHERE clause, where you can use the not equal to operator: <> or != in some versions of SQL
SELECT * FROM Persons WHERE Year<>'1965'
or
SELECT * FROM Persons WHERE Year!='1965'
will return a data set containing rows from the 'Persons' table where its field does not equal to '1965'.
Hope this helps in your sql query. you simply need to implement the <> or != operator where needed to return the opposite data set from your database this is assuming you wanted the opposite data from the data you're already having returned with your current query.
If this is not what you wanted please update your question so that you clearly explain what output you expect it to have - an example - so that a query can be made to retrieve the desired results.
Let me give you what I think is the answer first:
I think what you are looking for is Between ... And.... Or better to say Not(Between .. And..). Between ... And... is a range operator. So x Between 1 And 10 = 1<x<10 and Not x Between 1 And 10 = x<1 || x>10.
There are certain part of the query that are plain wrong to me:
Why do you have the select * anyway? You could just join the to tables like:
select
rooms.id, rooms.number, rooms.building, rooms.capacity,exam_schedules.room_id,
exam_schedules.day,
exam_schedules.start_time,
exam_schedules.end_time
from
rooms,exam_schedules
where
exam_schedules.room_id = rooms.id and exam_schedules.day = 'tuesday' and rooms.number = '077'
I generally do this with a left join and include only rows that are not in the right table.
SELECT
*
FROM
(select
rooms.id, rooms.number, rooms.building, rooms.capacity
from
rooms) R1
LEFT JOIN (select
exam_schedules.room_id,
exam_schedules.day,
exam_schedules.start_time,
exam_schedules.end_time
from
exam_schedules) R2
ON
R2.room_id = R1.id and R2.day = R1.day and (R2.start BETWEEN R1.start AND R1.end OR R2.end BETWEEN R1.start AND R1.end OR R1.start BETWEEN R2.start AND R2.end OR R1.end BETWEEN R2.start AND R2.end)
WHERE R2.room_id IS NULL;