Convert frequency table into a schedule view in mySQL - mysql

I have a mySQL table with the next contents:
ID START FREQUENCY REPETITIONS RESOURCE
--------------------------------------------------
1 24/02/2014 daily 5 10
2 24/02/2014 yearly 2 11
Is there any easy way to transform this into a view such as:
ID DATE RESOURCE
-------------------------
1 24/02/2014 10
1 25/02/2014 10
1 26/02/2014 10
1 27/02/2014 10
1 28/02/2014 10
2 24/02/2014 11
2 24/02/2015 11
Thanks

If you can have a limited number of repetitions, you can create a Numbers table like this:
CREATE TABLE numbers (
num INT,
i INT
);
INSERT INTO numbers VALUES
(1,1),
(2,1),
(2,2),
(3,1),
(3,2),
(3,3),
(4,1),
(4,2),
(4,3),
(4,4),
...
then you can use a JOIN:
SELECT
ID,
`START` + INTERVAL CASE WHEN FREQUENCY='daily' THEN i-1 ELSE 0 END DAY
+ INTERVAL CASE WHEN FREQUENCY='yearly' THEN i-1 ELSE 0 END YEAR
as `Date`,
RESOURCE
FROM
yourtable INNER JOIN numbers
ON yourtable.REPETITIONS = numbers.num
ORDER BY
ID, numbers.i
Please see fiddle here.

Related

Delete all SQL rows except one for a Group

I have a table like this:
Schema (MySQL v5.7)
CREATE TABLE likethis
(`id` int, `userid` int, `date` DATE)
;
INSERT INTO likethis
(`id`, `userid`, `date`)
VALUES
(1, 1, "2021-11-15"),
(2, 2, "2021-11-15"),
(3, 1, "2021-11-13"),
(4, 3, "2021-10-13"),
(5, 3, "2021-09-13"),
(6, 2, "2021-09-13");
id
userid
date
1
1
2021-11-15
2
2
2021-11-15
3
1
2021-11-13
4
3
2021-10-13
5
3
2021-09-13
6
2
2021-09-13
View on DB Fiddle
I want to delete all records which are older than 14 days, EXCEPT if the user only has records which are older - than keep the "newest" (biggest "id") row for this user.
Desired target after that action shall be:
id
userid
date
1
1
2021-11-15
2
2
2021-11-15
3
1
2021-11-13
4
3
2021-10-13
i.e.: User ID 1 only has records within the last 14 days: Keep all of them. User ID has a record within the last 14 days, so delete ALL his records which are older than 14 days. User ID 3 has only "old" records, i.e. older than 14 days - so keep only the one newest of those records, even though it's older than 14 days.
I thought of something like a self join with a subquery where I group by user-id ... but can't really get to it ...
This query could work
DELETE b
FROM likethis a
JOIN likethis b ON a.`userid` = b.`userid` AND a.`date` > b.`date`
WHERE b.`date` < NOW() - INTERVAL 14 DAY
I believe you can use case function in MySql
For Example -
SELECT TableID, TableCol,
CASE
WHEN Date > 30 THEN "Delete statement"
ELSE "Dont Delete (Record is not 30"
END
FROM TableName;
Suggested link:
https://www.w3schools.com/sql/func_mysql_case.asp
https://dev.mysql.com/doc/refman/5.7/en/case.html
Hope this helps...

Count maximum number of overlapping date ranges in MySQL 5.6

I am creating a vehicle rental application. I was trying find overlapping booking in given dates. I come across a similar question Count maximum number of overlapping date ranges in MySQL but this only answered for MySQL 8.0.
I modified above question for my problem.
I need solution for MySQL 5.6 without window function.
create table if not exists BOOKING
(
start datetime null,
end datetime null,
vehicle_id varchar(255),
id int auto_increment
primary key
);
INSERT INTO BOOKING (start, end, vehicle_id)
VALUES
('2020-02-06 10:33:55', '2020-02-07 10:34:41', 111),
('2020-02-08 10:33:14', '2020-02-10 10:33:57', 111),
('2020-02-06 10:32:55', '2020-02-07 10:33:32', 222),
('2020-08-06 10:33:03', '2020-02-11 10:33:12', 111),
('2020-02-12 10:31:38', '2020-02-15 10:32:41', 111),
('2020-02-09 09:48:44', '2020-02-10 09:50:37', 222);
Suppose If I give start as 2020-02-05 and end as 2020-02-11, this should return 2, as maximum usage of vehicle 111, is 2 from 2020-02-06 to 2020-02-10
5 6 7 8 9 10 11
<--> <------>
<----------------> (Vehicle Id 111, ANSWER should be 2)
for vehicle id 222, (For same query)
5 6 7 8 9 10 11
<--> <---> (Vehicle Id 222, ANSWER should be 1)
So overall output I am expecting for input start(2020-02-05) and end(2020-02-11)
+---------+-------+
| vehicle | usage |
+---------+-------+
| 111 | 2 |
| 222 | 1 |
+---------+-------+
I need solution which covers followings
on passing start_date and end_date my query will return data only for that range
If no data found should return vehicle_id 0
The maximum number of overlaps occurs when a rental starts (although it might persist for a period of time, this is all you care about).
You can calculate this for each start using:
SELECT b.vehicle_id, b.start, COUNT(*)
FROM booking b JOIN
booking b2
ON b2.vehicle_id = b.vehicle_id AND
b2.start <= b.start AND
b2.end > b.start
WHERE b.start <= $end and b.end >= $start
GROUP BY b.vehicle_id, b.start;
Then for the maximum:
SELECT vehicle_id, MAX(overlaps)
FROM (SELECT b.vehicle_id, b.start, COUNT(*) as overlaps
FROM booking b JOIN
booking b2
ON b2.vehicle_id = b.vehicle_id AND
b2.start <= b.start AND b2.end > b.start
GROUP BY b.vehicle_id, b.start
) b
GROUP BY vehicle_id;
Here is a db<>fiddle.
Performance on this type of query is never going to be as good as using window functions. However, an index on (vehicle_id, start, end) would help.
SELECT vehicle_id vehicle, MAX(cnt) `usage`
FROM ( SELECT booking.vehicle_id, timepoints.dt, COUNT(*) cnt
FROM booking
JOIN ( SELECT start dt FROM booking
UNION ALL
SELECT `end` FROM booking ) timepoints ON timepoints.dt BETWEEN booking.start AND booking.`end`
GROUP BY booking.vehicle_id, timepoints.dt ) subquery
GROUP BY vehicle_id;
fiddle
PS. Misprint in 4th row is corrected.

Show all hours even when no data

I am looking to write a query that shows all hours even when no data is present. I have seen some posts where the suggestion is to create a temporary table that has all the hours listed but I am not sure how to do that. Here is my current query:
select DATE_FORMAT(t_stamp, "%h %p") as Hour, count(*) as Count
from cyclehistory
where DATE(t_stamp) = CURRENT_DATE()
group by hour(t_stamp)
This works returns the following
Hour | Count
09 AM | 6
10 AM | 11
1 PM | 5
But I would like it to return
Hour | Count
.
.
.
09 AM | 6
10 AM | 11
11 AM | 0
12 PM | 0
1 PM | 5
.
.
You do need some kind of table of numbers to do this. Here, you just need 24 numbers, from 0 to 23. Say the numbers table is called numbers with column num:
select maketime(n.num, 0, 0) as t_time, count(c.t_stamp) cnt
from numbers n
left join cyclehistory c
on hour(c.tstamp) = n.num
and c.t_stamp >= current_date and c.t_stamp < current_date + interval 1 day
group by n.num
There are multiple ways to build the number table. You can create a table and insert records manually:
create table numbers (num int primary key);
insert into numbers values (0), (1), ..., (23);
For a one-time task, you can create a derived table directly in the query:
select ...
from (
select 0 num
union all select 1
...
union all select 23
) numbers
left join ...
In MySQL 8.0, you can use a recursive query:
with recursive numbers (num) as (
select 0
union all select num + 1 from cte where num < 23
)
select ...
from numbers
left join ...

MySQL - Date Difference and Flags

I am very new to MySQL and currently working on a table with three columns: trx_id, user_id, last_activity. (Churn Analysis)
tbl_activity:
The table capture activity of users. I am finding it difficulty in performing two tasks.
1) I would like to see two new columns through SQL query
date difference between subsequent transactions.
flag based on condition > 30 days.
Desired table:
2) One of the objectives of this study is to identify when (date) a customer churned. Ideally in my case it would be the 31st day since last activity. Any way to arrive at this date?
I am new to SQL learning and finding it difficult to address SQL queries for the above tasks.
Try this:
For SQL Server:
CREATE TABLE #tbl_activity(Trx_ID INT, User_Id INT, Last_Activity DATETIME)
INSERT INTO #tbl_activity VALUES(1,1100,'2015-06-08')
INSERT INTO #tbl_activity VALUES(2,1100,'2015-06-10')
INSERT INTO #tbl_activity VALUES(3,1100,'2015-06-10')
INSERT INTO #tbl_activity VALUES(4,1100,'2015-06-12')
INSERT INTO #tbl_activity VALUES(5,1100,'2015-06-13')
INSERT INTO #tbl_activity VALUES(6,1100,'2015-06-14')
INSERT INTO #tbl_activity VALUES(7,1100,'2015-09-25')
SELECT T1.Trx_ID, T1.User_Id, T1.Last_Activity
,DATEDIFF(DAY, T1.Last_Activity, T2.Last_Activity) days_Diff
,CASE WHEN DATEDIFF(DAY, T1.Last_Activity, T2.Last_Activity) >30 THEN 1 ELSE 0 END Flag
FROM #tbl_activity T1
LEFT JOIN #tbl_activity T2 ON T1.Trx_ID = T2.Trx_ID-1
DROP TABLE #tbl_activity
For MySQL:
CREATE TABLE tbl_activity(Trx_ID INT, User_Id INT, Last_Activity DATETIME)
INSERT INTO tbl_activity VALUES(1,1100,'2015-06-08')
INSERT INTO tbl_activity VALUES(2,1100,'2015-06-10')
INSERT INTO tbl_activity VALUES(3,1100,'2015-06-10')
INSERT INTO tbl_activity VALUES(4,1100,'2015-06-12')
INSERT INTO tbl_activity VALUES(5,1100,'2015-06-13')
INSERT INTO tbl_activity VALUES(6,1100,'2015-06-14')
INSERT INTO tbl_activity VALUES(7,1100,'2015-09-25')
SELECT T1.Trx_ID, T1.User_Id, T1.Last_Activity
,DATEDIFF(T2.Last_Activity, T1.Last_Activity) days_Diff
,CASE WHEN DATEDIFF(T2.Last_Activity, T1.Last_Activity) >30 THEN 1 ELSE 0 END Flag
FROM tbl_activity T1
LEFT JOIN tbl_activity T2 ON T1.Trx_ID = T2.Trx_ID-1
DROP TABLE tbl_activity
Try this in #SQL Fiddle
Output:
Trx_ID User_Id Last_Activity days_Diff Flag
1 1100 2015-06-08 00:00:00.000 2 0
2 1100 2015-06-10 00:00:00.000 0 0
3 1100 2015-06-10 00:00:00.000 2 0
4 1100 2015-06-12 00:00:00.000 1 0
5 1100 2015-06-13 00:00:00.000 1 0
6 1100 2015-06-14 00:00:00.000 103 1
7 1100 2015-09-25 00:00:00.000 NULL 0

mysql select rows by consecutive date

I have a table of available date blocks (7 days in my case) which may or may not be consecutive:
start_date end_date booked id room_id
2012-07-14 2012-07-21 0 1 6
2012-07-21 2012-07-28 0 2 6
2012-07-28 2012-08-04 1 3 6
2012-08-04 2012-08-11 0 4 6
What I'd like to do is be able to get a result set that gives me one row per X weeks of consecutive unbooked dates, within a date range.
So, for 2 week blocks starting on the 14th of July and using the above table data, I would expect the following:
start_date end_date booked
2012-07-14 2012-07-28 0
The second block of 2 weeks would not be returned as one of the component weeks is booked.
Here are a few ideas I've tried already:
SELECT
MIN(start_date) AS start_date_min,
MAX(end_date) AS end_date_max,
CAST(GROUP_CONCAT(id) AS CHAR) AS ids,
SUM(booked) AS booked
FROM
available_dates
WHERE
(start_date>=20120714 AND end_date<=DATE_ADD(20120714, INTERVAL 14 DAY))
GROUP BY
room_id
HAVING
end_date_max=DATE_ADD(20120714, INTERVAL 14 DAY)
This gets me part of the way, however doesn't get me the consecutive results - that is the important part. It also only returns a single result (probably because of the HAVING clause) when I widen the test data.
Can anyone point me in the right direction?
If you have a calendar or a numbers table:
CREATE TABLE num
( i INT NOT NULL
, PRIMARY KEY (i)
) ;
INSERT INTO num
(i)
VALUES
(0), (1), (2), ..., (1000) ;
You could use something like this:
SELECT
avail.room_id,
MIN(avail.start_date) AS start_date_min,
MAX(avail.end_date) AS end_date_max,
CAST(GROUP_CONCAT(avail.id) AS CHAR) AS ids,
SUM(avail.booked) AS booked
FROM
available_dates AS avail
CROSS JOIN
( SELECT DATE('2012-07-14') AS start_date_check
, 52 AS max_week_check
) AS param
JOIN
num
ON avail.start_date = param.start_date_check + INTERVAL num.i WEEK
AND num.i < param.max_week_check
WHERE
avail.booked = 0
GROUP BY
avail.room_id,
( num.i / 2 )
HAVING
COUNT(*) = 2
You could also have this:
WHERE
1 =1 --- no WHERE condition
GROUP BY
avail.room_id,
( num.i / 2 )
HAVING --- and optionally
SUM(avail.booked) = 0 --- this