I have a table that looks like this
userid | eventid | description | date | starttime | endtime
1 1 Event 1 2016-02-02 09:30:00 11:00:00
1 2 Event 2 2016-02-02 13:30:00 15:00:00
1 3 Event 3 2016-02-02 17:30:00 21:00:00
2 4 Event 4 2016-02-03 13:00:00 14:00:00
2 5 Event 5 2016-02-03 15:00:00 16:00:00
I need to find what is the sum of time between the events on the same day by the user.
Like this:
userid | timeBetween
1 05:00:00
2 01:00:00
I should also assume that there may be overlapping times for example event1 starts at 11:00 ends 13:00 and event2 starts 12:00 and ends 14:00 by the same user on the same day. These cases are rare and I believe returning 00:00 here is the appropriate answer.
I solved a similar problem, finding the sum of the length of all events per day.
SELECT *,
SEC_TO_TIME( SUM( TIME_TO_SEC(TIMEDIFF(`endtime`,`starttime`)))) as sumtime
FROM `events`
group by userid, date
order by sumtime desc
Given this sample data:
CREATE TABLE t
(`userid` int, `eventid` int, `description` varchar(7), `date` date, `starttime` time, `endtime` time)
;
INSERT INTO t
(`userid`, `eventid`, `description`, `date`, `starttime`, `endtime`)
VALUES
(1, 1, 'Event 1', '2016-02-02', '09:30:00', '11:00:00'),
(1, 2, 'Event 2', '2016-02-02', '13:30:00', '15:00:00'),
(1, 3, 'Event 3', '2016-02-02', '17:30:00', '21:00:00'),
(2, 4, 'Event 4', '2016-02-03', '13:00:00', '14:00:00'),
(2, 5, 'Event 5', '2016-02-03', '15:00:00', '16:00:00')
;
this query
SELECT userid, SEC_TO_TIME(SUM(TIME_TO_SEC(diff))) AS time_between
FROM (
SELECT
TIMEDIFF(starttime, COALESCE(IF(userid != #prev_userid, NULL, #prev_endtime), starttime)) AS diff,
#prev_endtime := endtime,
#prev_userid := userid AS userid
FROM
t
, (SELECT #prev_endtime := NULL, #prev_userid := NULL) var_init_subquery
ORDER BY userid
) sq
GROUP BY userid;
will return
+--------+--------------+
| userid | time_between |
+--------+--------------+
| 1 | 05:00:00 |
| 2 | 01:00:00 |
+--------+--------------+
Explanation:
In this part
, (SELECT #prev_endtime := NULL, #prev_userid := NULL) var_init_subquery
ORDER BY userid
we initialize our variables. The ORDER BY is very important, since there's no order in a relational database unless you specify it. It is so important, because the SELECT clause processes the rows in this order.
In the SELECT clause the order is also very important. Here
#prev_endtime := endtime,
#prev_userid := userid AS userid
we assign the values of the current row to the variables. Since this happens after this line
TIMEDIFF(starttime, COALESCE(IF(userid != #prev_userid, NULL, #prev_endtime), starttime)) AS diff,
the variables still hold the values of the previous row in the timediff() function. Therefore we also have to use COALESCE(), because in the very first row and when the userid changes, there is no value to calculate the diff from. To get a diff of 0 there, COALESCE() exchanges the NULL value with the starttime.
The last part is obviously to simply sum the seconds of the "between times".
Here's one way you can get the timeBetween value in SECONDS
SELECT
firsttable.userid,
SEC_TO_TIME(SUM(TIME_TO_SEC(secondtable.starttime) - TIME_TO_SEC(firsttable.endtime))) timeBetween
FROM
(
SELECT
*,
IF(#prev = userid, #rn1 := #rn1 + 1, #rn1 := 1) rank,
#prev := userid
FROM eventtable,(SELECT #prev := 0,#rn1 := 1) var
ORDER BY userid,starttime DESC
) firsttable
INNER JOIN
(
SELECT
*,
IF(#prev2 = userid, #rn2 := #rn2 + 1, #rn2 := 1) rank,
#prev2 := userid
FROM eventtable,(SELECT #prev2 := 0,#rn2 := 1) var
ORDER BY userid,endtime DESC
) secondTable
ON firsttable.userid = secondtable.userid AND firsttable.rank = secondtable.rank + 1 AND
firsttable.date = secondtable.date
GROUP BY firsttable.userid;
TEST:
Unable to add a fiddle.
So here's test data with schema:
DROP TABLE IF EXISTS `eventtable`;
CREATE TABLE `eventtable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) NOT NULL,
`eventid` int(11) NOT NULL,
`description` varchar(100) CHARACTER SET utf8 NOT NULL,
`date` date NOT NULL,
`starttime` time NOT NULL,
`endtime` time NOT NULL,
PRIMARY KEY (`id`)
) ;
INSERT INTO `eventtable` VALUES ('1', '1', '1', 'Event 1', '2016-02-02', '09:30:00', '11:00:00');
INSERT INTO `eventtable` VALUES ('2', '1', '2', 'Event 2', '2016-02-02', '13:30:00', '15:00:00');
INSERT INTO `eventtable` VALUES ('3', '1', '3', 'Event 3', '2016-02-02', '17:30:00', '21:00:00');
INSERT INTO `eventtable` VALUES ('4', '2', '4', 'Event 4', '2016-02-03', '13:00:00', '14:00:00');
INSERT INTO `eventtable` VALUES ('5', '2', '5', 'Event 5', '2016-02-03', '15:00:00', '16:00:00');
Result:
Executing the above query on the given test data you will get output like below:
userid timeBetween
1 05:00:00
2 01:00:00
Note:
For overlapping events the above query will give you negative timeBetween value.
You can replace the the SEC_TO_TIME...line by the following:
SEC_TO_TIME(IF(SUM(TIME_TO_SEC(secondtable.starttime) - TIME_TO_SEC(firsttable.endtime)) < 0, 0,SUM(TIME_TO_SEC(secondtable.starttime) - TIME_TO_SEC(firsttable.endtime)))) timeBetween
If you take the TIMEDIFF of the MIN(starttime) and MAX(endtime) for each user/day and then subtract the sum of events as calculated earlier, this will give you the times in between.
try this on
select TIMEDIFF('start_time','end_time') from your table
hope this one help you
Related
I'm running MySQL 5.6.
I have two related tables:
CREATE TABLE Cars (
id INT NOT NULL AUTO_INCREMENT,
plate VARCHAR(16) NOT NULL,
flag TINYINT,
PRIMARY KEY(id)
)
and:
CREATE TABLE Rents (
id INT NOT NULL AUTO_INCREMENT,
out_date DATE NOT NULL,
in_date DATE,
car_id INT,
FOREIGN KEY (car_id) REFERENCES Cars(id),
PRIMARY KEY(id)
)
I can have multiple rents for each car (0 to many).
I need to select all vehicles in table Cars (with flag = 1) along with their status i.e. I need to know if each car is currently unavailable (only out_date is filled) or availabe (out_date and in_date filled) of course also vehicles without any rents are to be considered available.
The result set need to include out_date and in_date values [Update 17/07/2022].
I tought to use something like:
SELECT
*,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rent.Car_id WHERE Cars.Flag = 1
but this of course will just return all the rows with positive flag match and a status evaluation (0 unavailable, 1 available):
id | plate | flag | id | out_date | in_date | car_id | status
---------------------------------------------------------------------
'1', 'FA787MX', '1', '1', '2022-07-14', '2022-07-15', '1', '1'
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
While I need to have this (edited 17/07/2022):
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
i.e. only the second row of FA787MX car should be mantained since it's the most recent out_date value (no matter if it's id is higher or lower).
For the sake of completeness: There is no guarantee that rental ids will be kept consistent with their rental history. In other words you cannot be sure that for a given car the rental where in_date = NULL is the correct one but you should compare them by out_date value.
Data sample:
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (1, 'FA787MX', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (2, 'EX431YY', 0);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (3, 'AB124DF', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (4, 'CC666VC', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (5, 'GG435ED', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (1, '2022-07-14', '2022-07-15', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (2, '2022-07-16', NULL, 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (3, '2022-07-16', NULL, 2);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (4, '2022-07-13', '2022-07-14', 3);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (5, '2022-07-16', NULL, 5);
One option is to join to find only those rentals that are still outstanding (in_date IS NULL). That will drop the old rentals having in_date not null.
Based on the updated requirements, there are a few ways to do it. One is a simple outer join to find the most recent rental per car to obtain the corresponding in_date as well...
MySQL 5.6 fiddle
SELECT Cars.*
, Rents.out_date
, Rents.in_date
, Rents.id IS NULL OR Rents.in_date IS NOT NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
LEFT JOIN Rents AS r2
ON Rents.out_date < r2.out_date
AND Rents.Car_id = r2.Car_id
WHERE Cars.Flag = 1
AND r2.Car_id IS NULL
ORDER BY Cars.id
;
The result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
2022-07-13
2022-07-14
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
Based on the original requirements: Try this (fiddle):
SELECT Cars.*
, Rents.in_date
, CASE WHEN in_date IS NOT NULL OR Rents.id IS NULL THEN 1 ELSE 0 END AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
and if the results contain only those with in_date IS NULL, this reduces to:
SELECT Cars.*
, out_date
, Rents.in_date
, Rents.id IS NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
Result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
If your version of MySql is 8.0+ use ROW_NUMBER() window function to pick the latest row for each car in Rents:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT *, ROW_NUMBER() OVER (PARTITION BY car_id ORDER BY out_date DESC) rn
FROM Rents
) r ON r.car_id = c.id AND r.rn = 1
WHERE c.flag = 1;
For previous versions use NOT EXISTS:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT r1.*
FROM Rents r1
WHERE NOT EXISTS (
SELECT *
FROM Rents r2
WHERE r2.car_id = r1.car_id AND r2.out_date > r1.out_date
)
) r ON r.car_id = c.id
WHERE c.flag = 1;
See the demo.
If you imagine the result of your query as a table, you can easily write a query that would give you what you need (the subquery is just yours with the select spelled out to give a unique column name to the second id column, as it seemed useful - the only way to uniquely identify a row):
SELECT MAX(rent_id) FROM (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
) as rental_status
WHERE status = 0
GROUP BY car_id;
Which tells you which rows are interesting:
+--------------+
| MAX(rent_id) |
+--------------+
| 2 |
| 5 |
+--------------+
Now you can use a join to return the results of your initial query only for the interesting rows. To avoid having to spell out that query all over again, MySQL 8 has a way to stash the results of your core query and use it like a table:
WITH
status_data AS (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
)
SELECT * from status_data
JOIN (
SELECT MAX(rent_id) as rent_id FROM status_data
WHERE status = 0
GROUP BY car_id
) as ids using(rent_id);
Giving the result:
+---------+----+---------+------+------------+---------+--------+--------+
| rent_id | id | plate | flag | out_date | in_date | car_id | status |
+---------+----+---------+------+------------+---------+--------+--------+
| 2 | 1 | FA787MX | 1 | 2022-07-16 | NULL | 1 | 0 |
| 5 | 5 | GG435ED | 1 | 2022-07-16 | NULL | 5 | 0 |
+---------+----+---------+------+------------+---------+--------+--------+
I'm working on an old DB and I have to create new columns based on old ones. This is a log table, each lines is an event. So uid is a User ID and a user can make several validation requests.
Here's my table
event_id uid event
1 1 REQUEST
2 1 VALIDATION
3 2 REQUEST
4 3 REQUEST
5 3 VALIDATION
6 2 VALIDATION
7 1 REQUEST
8 1 VALIDATION
Here's what I'd like to have
event_id uid event last_event_id request_nb
1 1 REQUEST 8 1
2 1 VALIDATION 8 1
3 2 REQUEST 6 1
4 3 REQUEST 5 1
5 3 VALIDATION 5 1
6 2 VALIDATION 6 1
7 1 REQUEST 8 2
8 1 VALIDATION 8 2
I assume that I need a group by on uid, a sum like 1 + SUM(CASE WHEN EVENT = 'VALIDATION' THEN 1 ELSE 0 END) AS request_nb. And a MAX somewhere for last_event_id. But I'm not familiar with these kinds of join.
Here's my sample data set:
CREATE TABLE IF NOT EXISTS `docs` (
`event_id` int(6) unsigned NOT NULL,
`uid` int(3) unsigned NOT NULL,
`event` varchar(200) NOT NULL,
PRIMARY KEY (`event_id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `docs` (`event_id`, `uid`, `event`) VALUES
('1', '1', 'REQUEST'),
('2', '1', 'VALIDATION'),
('3', '2', 'REQUEST'),
('4', '3', 'REQUEST'),
('5', '3', 'VALIDATION'),
('6', '2', 'VALIDATION'),
('7', '1', 'REQUEST'),
('8', '1', 'VALIDATION');
and fiddle of same: http://sqlfiddle.com/#!9/404dcf/1
Does anyone have an idea ?
Thanks!
E.g.:
SELECT a.event_id
, a.uid
, a.event
, a.i
, b.last_event_id
FROM
( SELECT x.*
, CASE WHEN #prev_uid = uid THEN CASE WHEN #prev_event = event THEN #i:=#i+1 ELSE #i:=1 END ELSE #i:=1 END i
, #prev_uid := uid prev_uid
, #prev_event := event prev_event FROM docs x
, (SELECT #prev_uid := null, #prev_event := null, #i:=1) vars
ORDER
BY uid
, event
, event_id
) a
JOIN
( SELECT uid, MAX(event_id) last_event_id FROM docs GROUP BY uid ) b
ON b.uid = a.uid
ORDER
BY a.event_id;
You can use ANSI-standard window functions:
select t.*,
max(event_id) over (partition by uid) as last_event_id,
row_number() over (partition by uid, event order by event_id) as request_nb
from t;
If for example I have:
CREATE TABLE application (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`month` VARCHAR(255) NOT NULL,
`amount` DECIMAL(9,2) NOT NULL)
;
INSERT INTO application
(`id`, `month`, `amount`)
VALUES
(1, 'january', 2000.00),
(2, 'february', 1000.00),
(3, 'january', 3000.00),
(4, 'january', 5000.00)
;
And then I run the query:
SELECT SUM(`amount`) as sum FROM application WHERE month IN ('january', 'february', 'march') GROUP BY `month`;
I get the result:
month sum
___________________
january | 10000.00
february | 1000.00
which is what the query was supposed to do however I'm looking for this result:
month sum
___________________
january | 10000.00
february | 1000.00
march | 0.00
how can I achieve this?
if anyone needs clarity don't vote down just ask and I will be more precise if i can.
cheers
SELECT m.mname, SUM(ISNULL(a.`amount`,0)) as sum
FROM
(
select 'january' as mname union all
select 'february' as mname union all
select 'march' as mname
) m LEFT JOIN application a on a.`month` = m.mname
GROUP BY a.`month`
This is my table:
placeID | name | time
My goal is to remove the oldest records if the placeID has more than 3 record in the table.
It's pretty hard to understand, so example will make it clear:
1 | "Some Name" | *fresher timestamp*
1 | "Some Name" | *fresher timestamp*
1 | "Some Name" | *older timestamp* -- > Delete this result since there are more than 3 records, and this is the older than the rest
1 | "Some Name" | *older timestamp* -- > Delete this result since there are more than 3 records, and this is older than the rest
1 | "Some Name" | *fresher timestamp*
2 | "Some Name" | *fresher timestamp*
2 | "Some Name" | *fresher timestamp*
My trouble: How do I loop for each placeID that has more than 3 record in my table?
Removing the oldest results will not be a problem.
What I tried:
SELECT placeID,COUNT(placeID) FROM place_fbStatus WHERE count(placeID) > 3
Error: #1111 - Invalid use of group function
Another Try:
SELECT id, placeID
FROM place_fbStatus
HAVING COUNT( placeID ) >4
LIMIT 0 , 30
Problem: Show only one placeID
Here my Answer:
First thing is to create a PRIMARY KEY on your Table. So it is easy to DELETE a ROW with this.
Create the Table:
CREATE TABLE `mytable` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`placeID` INT(11) DEFAULT NULL,
`name` VARCHAR(32) DEFAULT NULL,
`time` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `placeID` (`placeID`,`time`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Insert some stuff:
INSERT INTO `mytable` (`id`, `placeID`, `name`, `time`)
VALUES
(1, 1, 'Some Name', '2015-01-01 00:00:00'),
(2, 1, 'Some Name', '2015-01-02 00:00:00'),
(3, 1, 'Some Name', '2015-01-10 00:00:00'),
(4, 1, 'Some Name', '2015-01-09 00:00:00'),
(5, 2, 'Some Name', '2015-02-01 00:00:00'),
(6, 2, 'Some Name', '2015-01-30 00:00:00'),
(7, 1, 'Some Name', '2015-01-08 00:00:00');
i add rid ( ROW ID per Place ) and the Rows are in the right order
SELECT #rid:=IF( #pid = m.placeID, #rid+1 , 1) rid, m.*, #pid:=m.placeID AS pid
FROM mytable m, (SELECT #rid:=0, #pid:=-1) AS tmp
ORDER BY placeID,TIME ASC
And at last the Query to DELETE the ROWs:
DELETE mt FROM mytable mt
INNER JOIN (
SELECT * FROM (
SELECT #rid:=IF( #pid = m.placeID, #rid+1 , 1) rid, m.*, #pid:=m.placeID AS pid
FROM mytable m, (SELECT #rid:=0, #pid:=-1) AS tmp
ORDER BY placeID,TIME ASC
) AS x
WHERE x.rid >3
) AS dt ON mt.id = dt.id ;
You almost nailed it. What you need is to combine GROUP BY and HAVING
SELECT placeID, count(placeID) AS cnt
FROM place_fbStatus
GROUP BY placeID HAVING cnt>3
DELETE FROM Table WHERE ID NOT IN (SELECT TOP 3 ID FROM Table)
Refer to this thread
Although, it seems after trying it myself, it seems some versions of mysql dont yet support limit clauses in subqueries (This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'). To workaround this, try using an alternate subquery that doesnt involve limits.
This is the database schema:
CREATE TABLE `bookings` (
`id` int(2) NOT NULL,
`start` time NOT NULL,
`end` time NOT NULL
)
INSERT INTO `bookings` VALUES(1, '13:00:00', '14:30:00');
INSERT INTO `bookings` VALUES(2, '15:00:00', '16:00:00');
I tried to run the following query, to find the free times:
SELECT free_from, free_until
FROM (SELECT a.end AS free_from,
(SELECT MIN(c.start)
FROM bookings c
WHERE c.start>a.end
) as free_until
FROM bookings a
WHERE NOT EXISTS (SELECT 1
FROM bookings b
WHERE b.start BETWEEN a.end AND a.end + INTERVAL your_duration HOURS
) AND
a.end BETWEEN '10:00:00' AND '18:00:00'
) t
The output generated is:
free_from free_until
14:30:00 15:00:00
16:00:00 NULL
Why is there a NULL in the end? Please help me solve this problem. Expected output should be:
free_from free_until
10:00:00 13:00:00
14:30:00 15:00:00
16:00:00 18:00:00
try this:
first create table and insert records
CREATE TABLE bookings (
id int NOT NULL,
start time NOT NULL,
endtime time NOT NULL
)
INSERT INTO bookings VALUES(1, '13:00:00', '14:30:00');
INSERT INTO bookings VALUES(2, '15:00:00', '16:00:00');
then run this select query
SELECT free_from, free_until
FROM (SELECT a.endtime AS free_from,
(SELECT MIN(c.start)
FROM bookings c
WHERE c.start>a.endtime
) as free_until
FROM bookings a
WHERE a.endtime BETWEEN '10:00:00' AND '18:00:00'
) t where free_until IS NOT NULL;
Note: I changed your column end to endtime
The first row of your output corresponds to the following data set:
a:
1, '13:00:00', '14:30:00'
c:
1, '13:00:00', '14:30:00'
2, '15:00:00', '16:00:00'
for "free_until", c.start must be higher than a.end, which is satisfied for this data set.(for 2nd row of C)
The second row of your output corresponds to the following data set:
a:
2, '15:00:00', '16:00:00'
c:
1, '13:00:00', '14:30:00'
2, '15:00:00', '16:00:00'
for "free_until", c.start>a.end but as you can see, a.end is 16:00 here, and no c.start is higher than this.
that's why null
Modify a.end of the second row to 14:00 and you will not get null value. :D
modify the data in the table:
Delete everything from bookings, then
INSERT INTO `bookings` VALUES(1, '13:00:00', '14:30:00');
INSERT INTO `bookings` VALUES(2, '15:00:00', '14:00:00');