Select records based on independent conditions in MySQL - mysql

please find below the sample database:
CREATE TABLE ipay
(Ticket int(11) Primary Key,Login int(11), Profit double, opentime datetime);
INSERT INTO ipay
(Ticket,Login,Profit,opentime)
VALUES
(1,100,100,'2020-01-01 00:00:00'),
(2,100,100,'2020-02-01 00:00:00'),
(3,100,-200,'2019-01-01 00:00:00'),
(4,100,-50,'2020-01-02 00:00:00'),
(5,101,200,'2020-02-02 00:00:00'),
(6,101,200,'2020-03-02 00:00:00'),
(7,101,-10,'2020-04-02 00:00:00'),
(8,101,-200,'2020-05-02 00:00:00')
When Profit> 0, you can think the record as a deposit; when Profit<0, you can think the record as a withdrawal.
I need to get all withdrawals that happened after the first deposit, for each individual login. So that the expected output would be:
Ticket
Login
Profit
opentime
4
100
-50
2020-01-02 00:00:00
7
101
-10
2020-04-02 00:00:00
8
101
-200
2020-05-02 00:00:00
(For login 100, Ticket 3 is filtered out as it was made before Ticket 1;
For login 101, both Ticket 7 and 8 are included, since both were made after Ticket 5)
I have managed to identify the time when the first deposit was made:
SELECT LOGIN, TICKET, PROFIT, SUM(PROFIT), MIN(OPENTIME)
FROM ipay
WHERE PROFIT>0
GROUP BY LOGIN
I am stuck as there are more than one MIN(opentime).
I'm currently using MySQL version 5.7.34. Please do not hesitate to let me know if any clarification is needed. Any ideas would be much appreciated!

please try this
select * from ipay as a left join
(select login,min(opentime) as firsttime
from ipay where profit>0
group by login
order by opentime) as b
on a.login=b.login
where a.opentime>firsttime and profit<0
see in action here :sqlfiddle

Related

SQL: Creating time intervals between rows

I'm rather stuck on how to transform my table (using MySQL). Let me start by describing my table.
ID, ObservationDate(DATETIME), Total score, whole bunch of parameters that determine the total score
The ID's stand for patients, each ID may occur multiple times on different observation dates. I want to add 3 new columns: t_start, t_end, t_total
T_start: would be 0 for the very first observation or the previous observations' ObservationDate
T_end: is the current observation date
T_Total: Total time elapsed since start until last observation date.
These columns would have to be in LONG format, so preferably in hours.
Any idea on how to do this?
Kind regards
As I'm getting downvoted and this is being requested:
Edit: Getting downvoted and people seem to require more info. Here we go:
Table:
CREATE TABLE `dataset_origineel` (
PatientId` int(11) NOT NULL,
`ObservationDate` varchar(255) DEFAULT NULL,
`EWS_Total` int(11) DEFAULT NULL,)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Data set:
INSERT INTO `dataset_origineel` (`PatientId`, `ObservationDate`, `EWS_Total`) VALUES
(30, '2019-05-01 13:27:50.0000000',0)
(30, '2019-05-01 15:27:44.0000000',5)
(30, '2019-05-01 15:54:27.0000000',4)
(30, '2019-05-01 16:07:27.0000000',2)
(31, '2019-05-01 17:03:16.0000000',1)
(31, '2019-05-01 18:02:29.0000000',0)
(31, '2019-05-01 19:23:49.0000000',0)
(32, '2019-05-01 21:07:36.0000000',0)
(32, '2019-05-01 21:08:05.0000000',4)
(32, '2019-05-01 21:12:11.0000000',3)
(32, '2019-05-01 21:13:21.0000000',2)
(32, '2019-05-01 23:12:50.0000000',0)
(32, '2019-05-02 00:28:57.0000000',3)
What I want is:
PatientId, ObservationDate, t_start, t_end, t_total
30 2019-05-01 13:27:50 0 120 170
30 2019-05-01 15:27:44 120 147 170
30 2019-05-01 15:54:27 147 170 170
30 2019-05-01 16:07:27 170 170
And so on. Hope this is more clear.
Also: Thanks to people helping with the formatting, rather new to SO as a whole )
You seem to want lag():
select t.*,
lag(observationdate) over(partition by id order by observationdate) as t_start,
observationdate as t_end,
timestampdiff(
second,
lag(observationdate) over(partition by id order by observationdate),
observationdate
) / 60 / 60 as t_total
from mytable t
This window function is available in MySQL 8.0.
Note that I computed the time difference in seconds, then translated it to hours. This gives you a decimal count of hours, which is more accurate that giving argument hour to timestampdif() (this function just counts the number of unit boundaries that where crossed between the two timestamps).

mySql calculate sum of date difference

I am developing a employee login system in which user check in and checkout timings are recorder. I have the following mySql table schema from which I would like to query the total working hours of an employee of a particular month.
AttendanceId UserId Operation CreatedDate
24 4 1 2016-03-20 23:18:59
25 4 2 2016-03-20 23:19:50
26 4 1 2016-03-20 23:20:28
27 4 2 2016-03-20 23:20:31
Operation 1 is for check in and operation 2 is for checkout. Can any one help me to build this query?
A pleasingly complicated question, thanks. My query deals with:
Attendances that aren't precisely measured in hours. The number of seconds is totalled and divided by 3600 at the end of the calculation.
Attendances that span the month boundary at either end (thanks strawberry)
Attendances in the current month that have started (there is an entry with operation "1") but not yet finished (there is no corresponding operation "2").
I used the following data for testing:
INSERT INTO Attendance(UserId, Operation, CreatedDate) VALUES
(4, 1, '2016-01-01 15:00:00'),
(4, 2, '2016-01-01 19:00:00'),
(4, 1, '2016-01-31 23:00:00'),
(4, 2, '2016-02-01 01:00:00'),
(4, 1, '2016-02-20 23:18:59'),
(4, 2, '2016-02-20 23:19:50'),
(4, 1, '2016-02-20 23:20:28'),
(4, 2, '2016-02-20 23:20:31'),
(4, 1, '2016-02-29 23:00:00'),
(4, 2, '2016-03-01 01:00:00'),
(4, 1, '2016-03-02 15:00:00'),
(4, 2, '2016-03-02 18:00:00'),
(4, 1, '2016-03-22 10:00:00');
The query selects all users' hours for a specific month. Selecting results for more than one month in one query is more complicated because of the possibility that attendances span month boundaries and if required it might be simplest to iterate over the months and run the query repeatedly, adjusting the four dates in the SQL appropriately.
The innermost query selects all arrival times and the corresponding departure time for all users. The outer query then restricts them to the current month, calculates the difference between the two times, and sums them by user.
SELECT UserId, SUM(TIMESTAMPDIFF(
SECOND,
GREATEST(TimeIn, '2016-02-01'),
LEAST(COALESCE(TimeOut, NOW()), '2016-03-01'))) / 3600 HoursInMonth
FROM (SELECT TimeIn.UserId, TimeIn.CreatedDate TimeIn, MIN(TimeOut.CreatedDate) TimeOut
FROM Attendance TimeIn
LEFT JOIN Attendance TimeOut ON TimeOut.UserId = TimeIn.UserId
AND TimeOut.Operation = 2
AND TimeOut.CreatedDate > TimeIn.CreatedDate
WHERE TimeIn.operation = 1
GROUP BY TimeIn.AttendanceId
ORDER BY TimeIn.CreatedDate) TimeInOut
WHERE DATE_FORMAT(TimeIn, '%Y-%m') = '2016-02'
OR DATE_FORMAT(TimeOut, '%Y-%m') = '2016-02'
OR (DATE_FORMAT(TimeIn, '%Y-%m') < '2016-02' AND TimeOut IS NULL)
GROUP BY UserId;

Read user registration with count per day

I have a table with user data and I would like to do an efficient query to SELECT the user Count (userId e.g.) PER DAY.
So the output will be:
20/07/2015 - 50 new users
21/07/2015 - 23 new users
22/07/2015 - 19 new users
How can I do it with one query? A while() loop and then doing a query per day each seems very inefficient for me.
You could have a schema which has the following structure(I'm using MySql's syntax):
create table users(
user_id int,
create_date date
);
Say it's populated with following data:
insert into users values (1, STR_TO_DATE('1-01-2012', '%d-%m-%Y'));
insert into users values (2, STR_TO_DATE('1-01-2012', '%d-%m-%Y'));
insert into users values (3, STR_TO_DATE('1-01-2012', '%d-%m-%Y'));
insert into users values (4, STR_TO_DATE('2-01-2012', '%d-%m-%Y'));
If you select the user id's and group them by create_date, you'll have the required response using single query:
select count(user_id) as count,create_date from users group by create_date;
count create_date
3 January, 01 2012 00:00:00
1 January, 02 2012 00:00:00

Mysql Select Only Staff with Specified Number of Consecutive Free Time Slots

Each staff already has a table of avail time slots in AvailSlots like this:
Staff_ID Avail_Slots_Datetime
1 2015-1-1 09:00:00
1 2015-1-1 10:00:00
1 2015-1-1 11:00:00
2 2015-1-1 09:00:00
2 2015-1-1 10:00:00
2 2015-1-1 11:00:00
3 2015-1-1 09:00:00
3 2015-1-1 12:00:00
3 2015-1-1 15:00:00
I need to find out which staff has, for example, 2 (or 3, 4, etc) CONSECUTIVE avail time slots at each time slot. As a novice, the INNER JOIN codes below is all I know to write if the query is for 2 consecutive time slots.
SELECT a.start_time, a.person
FROM a_free a, a_free b
WHERE (b.start_time = addtime( a.start_time, '01:00:00' )) and (a.person = b.person)
But, obviously, doing it that way, I would have to add more INNER JOIN codes - for each case - depending on whether the query is for 3, or 4, or 5 , etc consecutive available time slots at a given date/hour. Therefore, I want to learn a more efficient and flexible way to do the same. Specifically, the query code I need (in natural language) would be this:
For each time slot in AvailSlots, list one staff that has X (where X can
be any number I specify per query, from 1 to 24) consecutive datetime
slot starting from that datetime. In case more than one staff can meet
that criteria, the tie break is their "rank" which is kept in a
separate table below:
Ranking Table (lower number = higher rank)
Staff_ID Rank
1 3
2 1
3 2
If the answer is to use things like "mysql variables", "views", etc, please kindly explain how those things work. Again, as a total mysql novice, "select", "join", "where", "group by" are all I know so far. I am eager to learn more but have trouble understanding more advanced mysql concepts so far. Many thanks in advance.
Using a bit more data than you posted, I found a query that might do what you need. It does use the variables as you predicted :) but I hope it's pretty self-explanatory. Let's start with the table:
CREATE TABLE a_free
(`Staff_ID` int, `Avail_Slots_Datetime` datetime)
;
INSERT INTO a_free
(`Staff_ID`, `Avail_Slots_Datetime`)
VALUES
(1, '2015-01-01 09:00:00'),
(1, '2015-01-01 10:00:00'),
(1, '2015-01-01 11:00:00'),
(1, '2015-01-01 13:00:00'),
(2, '2015-01-01 09:00:00'),
(2, '2015-01-01 10:00:00'),
(2, '2015-01-01 11:00:00'),
(3, '2015-01-01 09:00:00'),
(3, '2015-01-01 12:00:00'),
(3, '2015-01-01 15:00:00'),
(3, '2015-01-01 16:00:00'),
(3, '2015-01-01 17:00:00'),
(3, '2015-01-01 18:00:00')
;
Then there's a query to find the consecutive slots. It lists start times of each pair, and marks each group of consecutive slots with a unique number. The case expression is where the magic happens, see the comments:
select
Staff_ID,
Avail_Slots_Datetime as slot_start,
case
when #slot_group is null then #slot_group:=0 -- initalize the variable
when #prev_end <> Avail_Slots_Datetime then #slot_group:=#slot_group+1 -- iterate if previous slot end does not match current one's start
else #slot_group -- otherwise just just keep the value
end as slot_group,
#prev_end:= Avail_Slots_Datetime + interval 1 hour as slot_end -- store the current slot end to compare with next row
from a_free
order by Staff_ID, Avail_Slots_Datetime asc;
Having the list with slot groups identified, we can wrap the query above in another one to get the lengths of each slot group. The results of the first query are treated as any other table:
select
Staff_ID,
slot_group,
min(slot_start) as group_start,
max(slot_end) as group_end,
count(*) as group_length
from (
select
Staff_ID,
Avail_Slots_Datetime as slot_start,
case
when #slot_group is null then #slot_group:=0
when #prev_end <> Avail_Slots_Datetime then #slot_group:=#slot_group+1
else #slot_group
end as slot_group,
#prev_end:= Avail_Slots_Datetime + interval 1 hour as slot_end
from a_free
order by Staff_ID, Avail_Slots_Datetime asc
) groups
group by Staff_ID, slot_group;
Note: if you use the same DB connection to execute the query again, the variables would not be reset, so the slot_groups numbering will continue to grow. This normally should not be a problem, but to be on the safe side, you need to execute something like this before or after:
select #prev_end:=null;
Play with the fiddle if you like: http://sqlfiddle.com/#!2/0446c8/15

Order by day_of_week in MySQL

How can I order the mysql result by varchar column that contains day of week name?
Note that MONDAY should goes first, not SUNDAY.
Either redesign the column as suggested by Williham Totland, or do some string parsing to get a date representation.
If the column only contains the day of week, then you could do this:
ORDER BY FIELD(<fieldname>, 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY');
Why not this?
ORDER BY (
CASE DAYOFWEEK(dateField)
WHEN 1 THEN 7 ELSE DAYOFWEEK(dateField)
END
)
I believe this orders Monday to Sunday...
I'm thinking that short of redesigning the column to use an enum instead, there's not a lot to be done for it, apart from sorting the results after you've gotten them out.
Edit: A dirty hack is of course to add another table with id:weekday pairs and using joins or select in selects to fake an enum.
... ORDER BY date_format(order_date, '%w') = 0, date_format(order_date, '%w') ;
This looks messy but still works and seems more generic:
select day,
case day
when 'monday' then 1
when 'tuesday' then 2
when 'wednesday' then 3
when 'thursday' then 4
when 'friday' then 5
when 'saturday' then 6
when 'sunday' then 7
end as day_nr from test order by day_nr;
Using if is even more generic and messier:
select id, day,
if(day = 'monday',1,
if(day = 'tuesday',2,
if(day = 'wednesday',3,
if(day = 'thursday',4,
if(day = 'friday',5,
if(day = 'saturday',6,7)
)
)
)
)
) as day_nr from test order by day_nr;
You can also hide the details of conversion from name to int in stored procedure.
I realise that this is an old thread, but as it comes to the top of google for certain search times I will use it to share my approach.
I wanted the same result as the original question, but in addition I wanted the ordering of the results starting from the current day of the week and then progressing through the rest of the days.
I created a separate table, in which the days were listed over a fortnight, so that no matter which day you started from you could run through a sequence of 7 days.
CREATE TABLE IF NOT EXISTS `Weekdays` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=15 ;
INSERT INTO `Weekdays` (`id`, `name`) VALUES
(1, 'Monday'),
(2, 'Tuesday'),
(3, 'Wednesday'),
(4, 'Thursday'),
(5, 'Friday'),
(6, 'Saturday'),
(7, 'Sunday'),
(8, 'Monday'),
(9, 'Tuesday'),
(10, 'Wednesday'),
(11, 'Thursday'),
(12, 'Friday'),
(13, 'Saturday'),
(14, 'Sunday');
I then ran the query with a variable that determined the start point in sequence and used a join to get the order number for the days. For example to start the listing at Wednesday, I do the following:
SELECT #startnum := MIN(id) FROM Weekdays WHERE name='Wednesday';
SELECT * FROM Events INNER JOIN ( SELECT id as weekdaynum, name as dayname FROM Weekdays WHERE id>(#startnum-1) AND id<(#startnum+7) ) AS s2 ON s2.dayname=Events.day ORDER BY weekdaynum;
I hope this helps someone who stumbles onto this post.
Found another way, your can reverse order bye week
ORDER BY date_format(date_name, '%w') DESC;
Another way would be to create another table with those days and an int to order them by, join that table when searching, and order by it. Of course, joining on a varchar is not recommended.
Table DaysOfWeek
id | day
--------------------
1 | Monday
2 | Tuesday
3 | Wednesday
4 | Thursday
5 | Friday
6 | Saturday
SELECT * FROM WhateverTable
LEFT JOIN DaysOFWeek on DaysOFWeek.day = WhateverTable.dayColumn
ORDER BY DaysOfWeek.id
(Apologies if that's not correct; I've been stuck with SQL server recently)
Again, this is NOT recommended, but if you cannot alter the data you've already got... This will also work if there are non-standard values in the dayColumn field.
Found another way that works for me:
SELECT LAST_NAME, HIRE_DATE, TO_CHAR(HIRE_DATE, 'fmDAY') as 'Day' FROM EMPLOYEES
ORDER BY TO_CHAR(HIRE_DATE, 'd');
Hope it helps
In my case, since the days can be registered in several languages, to get the correct order I do like this according to Glen Solsberry:
....
....
ORDER BY
FIELD(<fieldname>, 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'),
FIELD(<fieldname>, 'LUNDI', 'MARDI', 'MERCREDI', 'JEUDI', 'VENDREDI', 'SAMEDI', 'DIMANCHE'),
FIELD(<fieldname>, 'LUNES', 'MARTES', 'MIERCOLES', 'JUEVES', 'VIERNES', 'SABADO', 'DOMINGO'),
FIELD(<fieldname>, 'MONTAGE', 'DIENSTAG', 'MITTWOCH', 'DENNERSTAG', 'FREITAG', 'SAMSTAG', 'SONNTAG')
;
Do not forget that, <fieldname> is the name of the date column in question in your case.
I saw that ...WHEN 1 THEN 7... was posted but it should be WHEN 1 THEN 8.
So...
ORDER BY (
CASE DATEPART(DW, yourdatefield)
WHEN 1 THEN 8 ELSE DATEPART(DW, yourdatefield)
END
)
Otherwise Sunday may come before Saturday because both Sunday and Saturday would equal 7. By setting Sunday to 8, it ensures it comes after Saturday.
If you try this, it should work:
SELECT ename, TO_CHAR(hiredate, 'fmDay') as "Day"
FROM my_table
ORDER BY MOD(TO_CHAR(hiredate, 'D') + 5, 7)