Enum type and MySQL - mysql

i have simple task and i got stucked on it.
I have table login_history
`login_history_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`login_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`login_action` enum('login','logout') NOT NULL,
`user_id` int(11) unsigned NOT NULL, (this one is foreign key)
TASK: Write a query which will find a user who had most logouts on Wednesdays in September 2012.
As you can see i have login_action which is enum type and i need to find which user had most logouts on some specific day.. This is what i done so far i just need little push in right direction, someone to tell me where i am wrong here..
SELECT fullname FROM user WHERE user_id = (
SELECT user_id FROM login_history WHERE (user_id,login_action) = (
SELECT user_id, COUNT(login_action) FROM login_history WHERE login_action = 'logout' AND login_time = (
SELECT login_time FROM login_history WHERE YEAR(login_time) = 2012 AND MONTH(login_time) = 9 AND DAYOFWEEK(login_time) = 3)));

Try this:
select u.fullname from (select count(*) n,user_id
from login_history where
login_time between '2012-09-01' and '2012-10-01' and dayofweek(login_time) = 3 and login_action = 'logout'
group by user_id order by n desc limit 1) a, user u where a.user_id = u.user_id
For good performance, make sure you have a key on login_time column.

Related

mysql how to check time interval based on the last time the user visited the place?

I have a client table that displays if thats the first time the client has visited the place and the client individual id, the point is i need to know if a user took longer than 6(360 minutes) hours to return.
Check the code to see how far i managed to reach by myself:
-- schema
CREATE TABLE Client (
Id INT NOT NULL AUTO_INCREMENT,
user_name VARCHAR(50) NOT NULL,
user_id VARCHAR(30) NULL,
first_time_buying VARCHAR(3) NOT NULL,
visit_date DATETIME NOT NULL,
PRIMARY KEY(Id)
)
;
-- data
INSERT INTO Client
(user_name, user_id, first_time_buying, visit_date)
VALUES
('Fred', '1','yes' ,'2020-02-15 23:59:59'),
('Fernanda','2', 'yes', '2020-02-17 12:35:00'),
('Fred','1', 'no', '2020-02-21 15:59:09'),
('Fernanda','2', 'no','2020-03-01 11:06:39'),
('Fred','1', 'no', '2020-02-21 16:36:39)
;
the select i created:
SELECT
TIMESTAMPDIFF(MINUTE,
(SELECT t1.visit_date
from Client t1 inner join(select user_name, max(Id) as maxID from Client
group by user_name) t2 on t1.user_name = t2.user_name
and t1.Id = t2.maxID
where t1.user_id = '2'),
CURRENT_TIMESTAMP()) AS Difference
i also need to deduct the current timestamp with the last time the user entered the place and if its less than 360 minutes return null else return true or the minutes amount.
For example:
if the last time he/she visited the shop was up to 6h ago i was expecting to get null or false, if its more than that i was expecting to get the value or true.
ps: it must be compared with the current timestamp value.
i may provide more info only if necessary.
Your code returns correct results but it is overcomplicated.
You can do it like this:
SELECT
TIMESTAMPDIFF(
MINUTE,
(
SELECT MAX(visit_date)
FROM Client
WHERE user_id = '2'
),
CURRENT_TIMESTAMP()
) AS Difference;
See the demo.
Or check this.
SELECT
case when TIMESTAMPDIFF(HOUR, max(c1.visit_date), CURRENT_TIMESTAMP()) < 360 then 'FALSE' ELSE 'TRUE' END as MoreThanSixHours
from client c1
group by c1.user_id

SQL query to select all rows with max column value

CREATE TABLE `user_activity` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`type` enum('request','response') DEFAULT NULL,
`data` longtext NOT NULL,
`created_at` datetime DEFAULT NULL,
`source` varchar(255) DEFAULT NULL,
`task_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
I have this data:-
Now I need to select all rows for user_id=527 where created_at value is the maximum. So I need the last 3 rows in this image.
I wrote this query:-
SELECT *
FROM user_activity
WHERE user_id = 527
AND source = 'E1'
AND task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
AND created_at = (SELECT Max(created_at)
FROM user_activity
WHERE user_id = 527
AND source = 'E1'
AND task_name IN ( 'GetReportTask',
'StopMonitoringUserTask' ));
This is very inefficient because I am running the exact same query again as an inner query except that it disregards created_at. What's the right way to do this?
I would use a correlated subquery:
SELECT ua.*
FROM user_activity ua
WHERE ua.user_id = 527 AND source = 'E1' AND
ua.task_name IN ('GetReportTask', 'StopMonitoringUserTask' ) AND
ua.created_at = (SELECT MAX(ua2.created_at)
FROM user_activity ua2
WHERE ua2.user_id = ua.user_id AND
ua2.source = ua.source AND
ua2.task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
);
Although this might seem inefficient, you can create an index on user_activity(user_id, source, task_name, created_at). With this index, the query should have decent performance.
Order by created_at desc and limit your query to return 1 row.
SELECT *
FROM user_activity
WHERE user_id = 527
AND source = 'E1'
AND task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
ORDER BY created_at DESC
LIMIT 1;
I used EverSQL and applied my own changes to come up with this single-select query that uses self-join:-
SELECT *
FROM user_activity AS ua1
LEFT JOIN user_activity AS ua2
ON ua2.user_id = ua1.user_id
AND ua2.source = ua1.source
AND ua2.task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
AND ua1.created_at < ua2.created_at
WHERE ua1.user_id = 527
AND ua1.source = 'E1'
AND ua1.task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
AND ua2.created_at IS NULL;
However, I noticed that the response times of both queries were similar. I tried to use Explain to identify any performance differences; and from what I understood from its output, there are no noticeable differences because proper indexing is in place. So for readability and maintainability, I'll just use the nested query.

How to get a list of available time slots from calendar database

I have a event/calendar MySQL table where each user have multiple appointments/events throughout the day. If one user can't make that appointment/event "because he/she are running behind on other appointment" I need to be able to re-assign this appointment to a different available user. So I need to display a suggestion of the top 5 users that are available for the scheduled time frame and can take this appointment, a manager will be able to re-assign this appointment to one of the suggested users.
My events table looks something like this
CREATE TABLE `calendar_events` (
`event_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`start_on` datetime NOT NULL,
`end_on` datetime NOT NULL,
`subject` varchar(255) NOT NULL,
`event_type` enum('Phone Call','Meeting','Event','Appointment','Other') CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL DEFAULT 'Phone Call',
`all_day_event` tinyint(1) DEFAULT '0' COMMENT '1 = all day event, 0 = no',
`phone_call_id` int(11) unsigned DEFAULT NULL,
`account_id` int(11) unsigned DEFAULT NULL,
`client_id` int(11) unsigned DEFAULT NULL,
`owner_id` int(11) unsigned NOT NULL,
`created_by` int(11) unsigned NOT NULL,
`created_on` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modified_by` int(11) unsigned DEFAULT NULL,
`modified_on` datetime DEFAULT NULL,
`event_location` varchar(255) DEFAULT NULL,
`event_notes` varchar(10000) DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0 = purged, 1 = active, 2=pass, 3 = cancled, 5 = waiting for auditor to be enabled',
PRIMARY KEY (`event_id`),
UNIQUE KEY `phone_call_id` (`phone_call_id`,`account_id`,`client_id`),
KEY `client_id` (`client_id`),
KEY `account_id` (`account_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
so lets for event_id = 100 is assigned to user_id = 2 and scheduled to start_on = '2014-09-21 10:00:00' and end_on '2014-09-21 10:00:00'
and user_id = 5 has appointment start_on '2014-09-21 11:45:00' and end_on '2014-09-21 12:30:00'
and user_id = 2 can not make his appointment that is scheduled for '2014-09-21 10:00:00' so they system will suggest user_id = 5 as he will be for the following 105 minutes.
The the final data set will need to be
event_id org_owner suggested_owner available_for
100 2 5 105
The following query will give me a list of all available users to from the users table along with a start_on end_on value if the user have an event scheduled (one user can have multiple records.) If the start_on is null in this query that means this user does not have any event otherwise it will return the start of each event.
So if user ID appears in the query above and have a NULL value in the start_on column, this means that this user is available all day so this user should be 1 of the 5 users to recommend because it has one of the highest availability. But if a user has one/multiple rows in the data set with a non-null value in the start on then, we need to look at the start_on that is the closest to the event and then recommend the top 5 that have the greatest availability value.
SELECT user_id, start_on, end_on, subject
FROM view_users AS su
LEFT JOIN calendar_events AS c ON c.owner_id = su.user_id AND c.start_on NOT BETWEEN '2014-09-30 00:00:00' AND '2014-09-30 23:59:59' AND c.status = 1
WHERE su.is_available_today = 1
How can I extract this data set?
First proposal edited thanks to your help, just need to take care of users that don't have any events (could be achieved with a left join in 't' subquery). This could be improved a lot, but right now I'm a bit tired :)
SELECT
c.event_id, -- Event id
c.owner_id AS org_owner, -- Original owner of event
t.owner_id AS suggested_owner, -- Suggested new user
c.start_on, -- Event start
t.free_from, -- Owner free slot start
t.free_to, -- Owner free slot end
TIME_TO_SEC( TIMEDIFF( t.free_to, c.start_on ) ) /60 AS available_for -- Availibility of minutes (diff between event start and free slot end)
FROM calendar_events AS c
-- Join with free slots
LEFT JOIN (
-- Add a slot for beginning, 1999-01-01 to first event start
SELECT * FROM (
SELECT owner_id, '1900-01-01' AS free_from, MIN( start_on ) AS free_to
FROM calendar_events c3
GROUP BY owner_id
) AS deb
UNION
-- select free slots by taking the event end and the following event start
SELECT owner_id, `end_on` AS free_from, (
SELECT start_on
FROM calendar_events c2
WHERE c2.owner_id = c1.owner_id
AND c2.start_on > c1.end_on
ORDER BY c2.start_on
LIMIT 0 , 1
) AS free_to
FROM calendar_events c1
UNION
-- Add a slot for end, last event end to 2100-01-01
SELECT * FROM (
SELECT owner_id, MAX( end_on ) AS free_from, '2100-01-01' AS free_to
FROM calendar_events c3
GROUP BY owner_id
) AS end
) AS t ON t.owner_id <> c.owner_id
-- Join avoid using same user and ensure free slot matches event dates
AND t.free_from <= c.start_on AND t.free_to >= c.end_on
WHERE c.status = 1
AND c.event_id =52
GROUP BY t.owner_id -- To avoid multiple free slots by user
ORDER BY available_for DESC -- Sort to list biggest slots first
LIMIT 0, 5 -- Only five first matching users
Good luck :)
How about this:
SELECT event_id, owner_id, start_on INTO #eventid, #user, #start_on
FROM calender_events WHERE event_id = 100;
SELECT #event_id event_id,
#user org_owner,
c.owner_id suggested_owner,
TIMESTAMPDIFF(MINUTE, $start_on, COALESCE(c.min_start, DATE(#start_on) + INTERVAL 18 HOUR)) available_for
FROM
users u
LEFT JOIN
(SELECT
owner_id,
MIN(start_on)
FROM
calender_events
WHERE
(start_on BETWEEN #start_on AND DATE(#start_on) + INTERVAL 18 HOUR)
OR
(start_on BETWEEN DATE(#start_on) AND DATE(#start_on) + INTERVAL 18 HOUR AND all_day_event = 1)
GROUP BY owner_id
) c
ON u.user_id = c.owner_id
WHERE u.user_id <> #user
ORDER BY available_for DESC
LIMIT 5
Maybe you have to adjust the INTERVAL, I just made an assumption the daay ending 6 P.M.
Try this:
SELECT
co.event_id,
co.owner_id org_owner,
su.user_id suggested_owner,
ifnull(min((to_seconds(c.start_on) - to_seconds(co)) / 60), 999) available
FROM calendar_events co
CROSS JOIN view_users su
LEFT JOIN calendar_events c ON c.owner_id = su.user_id
AND c.start_on BETWEEN co.start_on AND date(adddate(co.start_on, 1))
AND c.status = 1
WHERE co.event_id = 100
AND su.is_available_today = 1
GROUP BY 1, 2, 3
ORDER BY 4 DESC
LIMIT 5
Users that have no appointments for day day after the target event get assigned the available value of "999", putting them at the top of the list.
The next event for each user is found using min() over the time gap, and all users are sorted largest time gap first, them limit gives you the top 5.

Select statement joining 2 tables, searching by date, and status

OK I think I have messed up somewhere but maybe someone can spot my error, because I have little clue of what I am doing.
I have 2 Tables Players and RegionPlayer (see bottom for structure)
I am trying to find when a none of the players on a region have been seen in a while. Players can be on vacation which gives then 58 days, else its only 8 days.
If none of the players on a region have been seen in that time, I want the sql search to return the regionID, as well as the most recent person on that region who was seen.
Now I think that way to do this is to get 2 results from each region, each providing me the most recent player seen who was on vacation, and who was not on vacation.
But while, I thought this would give me that, it doesn't seem to.
SELECT RegionPlayer.Regionid, Players.key, Players.Name, Players.Seen, Players.Vacation
FROM RegionPlayer
JOIN Players
ON Players.Key = RegionPlayer.Playerid
where ( RegionPlayer.Status = 1 )
GROUP BY RegionPlayer.Regionid DESC, Players.Vacation DESC
ORDER BY Players.Seen DESC
Then I am going to need to be able to tell who has not been seen in a while, this should give me that.
Now I know I can link both queries together, but I have no idea how, it has been many years since I last had to put this much effort into sql statements.
Select Players.key FROM Players
WHERE
(( Players.Vacation != 1 ) AND
( Players.Seen <= (NOW() - INTERVAL 8 DAY ) ))
OR
(( Players.Vacation != 0 ) AND
( Players.Seen <= (NOW() - INTERVAL 58 DAY ) ))
Is There a better way of doing this, I sort of remember things like views, and store procedures, and functions, would one or more of them be better?
Table Structure.
Please forgive, the names, of the tables and some of the structure, This is an example of why deciding things late at night after 1/2 a bottle of wine is a bad idea.
CREATE TABLE IF NOT EXISTS `Players` (
`key` int(11) NOT NULL,
`Name` varchar(255) NOT NULL,
`Vacation` varchar(1) NOT NULL,
`Seen` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`Modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
CREATE TABLE IF NOT EXISTS `RegionPlayer` (
`Key` int(11) NOT NULL,
`Playerid` int(11) NOT NULL,
`Regionid` int(11) NOT NULL,
`Type` varchar(1) NOT NULL,
`Status` int(1) NOT NULL DEFAULT '1',
`Modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`Created` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
)
I've put up an SQLFiddle.
The query that answers your basic requirement, which seems to be: list all regions that have no active player seen in the last 8 days and no vacated player seen in the last 58 days, giving also the data of the last seen player in that region:
SELECT r.*
FROM (
SELECT rp.Regionid, p.Key, p.Name, p.Vacation, p.Seen
FROM RegionPlayer rp
JOIN Players p ON p.Key = rp.Playerid
WHERE rp.Status = 1
GROUP BY rp.Regionid
ORDER BY p.Seen DESC
) r
WHERE ((r.Vacation != 1) AND (r.Seen <= (NOW()-INTERVAL 8 DAY)))
OR ((r.Vacation != 0) AND (r.Seen <= (NOW()-INTERVAL 58 DAY)));
I desumed from your SQL that only RegionPlayer rows with a Status of 1 should be considered.
On the SQLFiddle I've create a bit of regions with different combinations, and this query does its job.
As to your first SQL statement. You say it doesn't work as expected, but to me it seems to do it... the last seen active player and last seen vacated player for each region. The sorting may not make it very readable, but it does do that.
Try this
SELECT RegionPlayer.Regionid, m.key, m.Name, m.Seen, m.Vacation
FROM RegionPlayer
JOIN (Select * as key FROM Players
WHERE
(( Players.Vacation != 1 ) AND
( Players.Seen <= (NOW() - INTERVAL 8 DAY ) ))
OR
(( Players.Vacation != 0 ) AND
( Players.Seen <= (NOW() - INTERVAL 58 DAY ) ))) m
ON m.Key = RegionPlayer.Playerid
where ( RegionPlayer.Status = 1 )
GROUP BY RegionPlayer.Regionid DESC, m.Vacation DESC
ORDER BY m.Seen DESC

Find all log events of type A that occur between types B and C

I am trying to find a nice way to find all events of type A that occur at a time between event types B and C.
Also, events B and C must share a process_id.
Each process_id group will have one B event and one C event.
Table structure looks like this:
CREATE TABLE IF NOT EXISTS `eventlog` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`time` datetime NOT NULL,
`process_id` varchar(20) NOT NULL,
`event` varchar(25) DEFAULT NULL,
`data` varchar(45) DEFAULT NULL,
)
My attempt looked like this:
SELECT q3.time, q3.event, q3.process_id, q3.data
FROM `eventlog` as q1, `eventlog` as q2, `eventlog` as q3
WHERE q1.process_id=q2.process_id AND q1.process_id=q3.process_id
AND q1.event='EVENTB' AND q2.event='EVENTC' AND q3.event='EVENTA'
AND q3.time BETWEEN q1.time AND q2.time
When I run this, it hangs. Any thoughts on a more efficient, or fixed way to do this?
Thanks!
You can use a group by to get start and end times for each process_id. This can then be used to join back to the eventlog table and select the required records.
SELECT * FROM eventlog e
JOIN (
SELECT
process_id,
MIN(CASE event WHEN 'EVENTB' THEN time END) start_time,
MAX(CASE event WHEN 'EVENTC' THEN time END) end_time
FROM eventlog
GROUP BY process_id
) t
ON e.time BETWEEN t.start_time AND t.end_time
WHERE
e.event = 'EVENTA'