SQL: how to get last items? - mysql

I have a log for documents that go through my application. The log looks like this:
TABLE: log
==================================================
| log_id | document_id | status_code | timestamp |
==================================================
| 1 | 10 | 100 | 12345 |
--------------------------------------------------
| 2 | 10 | 200 | 23456 |
--------------------------------------------------
I need a list of document_id that have been "stuck" in a certain status_code for a given duration (say 10 minutes; timestamp is Unix timestamp, btw). If a particular document_id is "stuck" in a certain status_code, that status_code will be the last status_code for that document_id.
How do I query this? The 2 things I'm not sure of:
How do I check if the document has been in a certain status for a certain period of time? I'm guessing I can use some sort of function/formula in my SQL (e.g. now - 10 minutes), but I don't know how to do that.
How do I check the duration of only the last status code?

SELECT log.document_id
, (UNIX_TIMESTAMP() - log.timestamp) / 60
AS MinutesSinceLastChange
FROM log
JOIN
( SELECT document_id
, MAX(timestamp) AS last_change
FROM log
GROUP BY document_id
HAVING (last_change < (UNIX_TIMESTAMP() - 60 * 10)) <-- that is 10 minutes
) AS grp
ON grp.document_id = log.document_id
AND grp.last_change = log.timestamp
WHERE log.status_code = "200" <-- code you want to check

For Your no. one and no. two question, this is my proposition for (I guess You are using MySQL):
SELECT
`document_id`,
SUBSTR(GROUP_CONCAT(RPAD(`status_code`,5) ORDER BY `timestamp` DESC), 1, 5) AS `last_status`,
SUBSTR(GROUP_CONCAT(RPAD(`status_code`,5) ORDER BY `timestamp` DESC), 7, 5) AS `prev_status`,
UNIX_TIMESTAMP(SUBSTR(GROUP_CONCAT(FROM_UNIXTIME(`timestamp`) ORDER BY `timestamp` DESC), 1, 19)) AS `last_timestamp`,
UNIX_TIMESTAMP(SUBSTR(GROUP_CONCAT(FROM_UNIXTIME(`timestamp`) ORDER BY `timestamp` DESC), 21, 19)) AS `prev_timestamp`
FROM `log`
GROUP BY `document_id`
HAVING `last_timestamp` - `prev_timestamp` > 60*10 AND `last_status` IN (100,200);
All right, what is happing there. We're grouping rows by document_id, and ordering status codes and timestamps inside GROUP_CONCAT to get the last and pre-last entry.
If Your status code can have more than 5 digits, then replace it RPAD(status_code,X), where X is maximal number of status_code length
60*10 - is 10 minutes
last_status IN (100,200) - status codes You want to get only.

SELECT log.document_id, log.status_code, max(log.timestamp) - min(log.timestamp)
FROM (
SELECT MAX(log_id) as log_id
FROM log
GROUP BY document_id) latestLog
INNER JOIN log latestStatus ON latestStatus.log_id = latestLog.log_id
INNER JOIN log on latestStatus.status_code = log.status_code
GROUP BY log.document_id, log.status_code
HAVING (max(log.timestamp) - min(log.timestamp)) > 600

Related

Mysql time spent at work by specyfic user

I have a MySQL table like this:
+-----+----------+------------+--------------+-------------+
| id | user_id | added_on | status_enter | status_exit |
+-----+----------+------------+--------------+-------------+
Is it possible to count the time if the data is in other rows?
12:16:16 - 10:44:1
User Date Enter Exit
----------- -------------------- ------ ------
John 2021-06-25 10:44:15 1 0
John 2021-06-25 12:16:16 0 1
Not tested, but SHOULD get what you are looking for. The outer query is only looking for those where a person clocked IN. The 3rd column-based select is a correlated query to whatever the current user is and the ID is greater than the check-in, AND it is the check-out. So its possible a null value here if the person is still clocked-in. I would have an index on this table by (enter, user, exit, id) to help optimize the query.
select
tc.id,
tc.user,
tc.date,
( select min( tc2.date )
from TimeClockTable tc2
where tc.User = tc2.User
and tc.id < tc2.id
and tc2.enter = 0
and tc2.exit = 1 ) EndTime,
( select min( tc2.id )
from TimeClockTable tc2
where tc.User = tc2.User
and tc.id < tc2.id
and tc2.enter = 0
and tc2.exit = 1 ) EndTimeID
from
TimeClockTable tc
where
tc.enter = 1
FEEDBACK
If the date/time stamp is always going to be sequential with the ID as it is added, ie: ID #1234 on July 5 at 10:00am will ALWAYS be before #1235 on July 5 at 10:01am (you would never have an ID 1235 or higher that was BEFORE the date/time of ID #1234), then the above modification to the query should work for you. You are already getting the lowest date/time for the given user in comparison to the first, then calling it a second time to get the minimum ID would correlate to the same end time.
There you go:
SELECT T.user_id AS User,
CAST(T.added_on AS DATE) AS Date,
DATEDIFF(
HOUR,
MIN(T.added_on),
MAX(T.added_on)
) AS TotalWorkTime
FROM WorkTable AS T
GROUP BY T.user_id,
CAST(T.added_on AS DATE)

mySQL - Grouping values based on consecutive differences

I have a table in my database that contains an ID and DATETIME column, here is some sample data:
ID | DATETIME
1 | 2014-05-06 01:12
1 | 2014-05-06 01:30
1 | 2014-05-06 01:45
1 | 2014-05-06 02:59
2 | 2014-05-06 01:17
2 | 2014-05-06 01:18
2 | 2014-05-06 01:19
2 | 2014-05-06 02:00
I need to produce a query that determines the ID belonging to the object that has the longest time between its DATETIME values, where the time between consecutive DATETIME values does not exceed 20 minutes.
For example, in the sample data, I would want to return 1 as it has DATETIME values from (01:12 - 01:45) without having a consecutive difference of 20 minutes between DATETIME values.
Thanks.
It looks like you will need a self-join. Because if you had 10 entries for an ID, your 20 minute gap might be between entries 3-6 vs 1-4 or even 4-9. So the second instance of the join would be on the same ID and have a date time higher than that of the primary entry, but less than 20 minutes. Then, it could be ordered by the time-gap and limit to the one you want. Something like:
select
YT.ID,
YT.DTColumn,
MAX( YT2.DTColumn ) as MaxDateWithin20Minutes
from
YourTable YT
JOIN YourTable YT2
ON YT.ID = YT2.ID
AND YT.DTColumn < YT2.DTColumn
AND YT2.DTColumn <= date_add( YT.DTColumn, INTERVAL 20 MINUTE )
group by
YT.ID,
YT.DTColumn
order by
timediff(MAX( YT2.DTColumn ), YT.DTColumn) DESC
limit
1
You need to get the next (or previous) value and get the time difference. I think the following does what you want:
select t.*
from (select t.*,
(select t2.datetime
from table t2
where t2.id = t.id and t2.datetime < t.datetime
order by t2.datetime desc
) prev_datetime
from table t
) t
where datetime <= prev_datetime + interval 20 minutes
order by timestampdiff(second, prev_datetime, datetime) desc
limit 1;

MySQL: query with condition on one-to-many table

I have a table with schema like this:
clients_actions
id | client_id | action | date_done
1 | 1 | ... | 1394785392
2 | 2 | ... | 1394786392
3 | 2 | ... | 1394787392
date_done can be set both in the past, and in the future from current unix timestamp. I need to select all 'forgotten' clients, which don't have date_done set in future (in all his actions) and last his action is older than 604800 seconds (7 days). Client can have many actions. And also, if it's possible, I need in the same query to select his last action (which is in past and more than 7 days old).
How can it be done?
One way to do it as
select * from clients_actions
where from_unixtime(date_done) < date_sub(now(),INTERVAL 7 day)
AND client_id
NOT IN
(
select client_id from
clients_actions
where from_unixtime(date_done) > now()
)
;
DEMO
In the demo I have added some data with future dates so that they can be ignored and just by getting data older than 7 days. You can do group by in case there are repeated data in your table.
Select client_id, action, MAX(date_done) from clients_actions
WHERE date_done < (UNIX_TIMESTAMP(SYSDATE() - 7)
AND id NOT IN (SELECT id FROM clients_actions
WHERE date_done > (UNIX_TIMESTAMP(SYSDATE()))
GROUP BY client_id;
For the first part you want a query that has Where date_done < SysDate - 7 days and client_id not in (select id from clients_actions where date_done > SysDate (also converted to UNIX). This says I want all records whose date_done is older than 7 days ago, but that don't have any actions due in the future.
the MAX and group by client_id limit it to only the latest record of those selected by client_id.
The following query will get you the desired result.
SELECT *
FROM clients_actions ca
INNER JOIN
(SELECT client_id, MAX(date_done) as date_done
FROM clients_actions
WHERE DATEDIFF(CURRENT_TIMESTAMP, FROM_UNIXTIME(date_done)) >= 7
GROUP BY client_id) latest_date
ON ca.client_id = latest_date.client_id AND ca.date_done = latest_date.date_done;

MySQL find only those that are not within 24 hours with foreign keys

Let's say today is 16th of November, 2013
and, let's say I have a table of dates like so
------------------------
id | foreign_id | date
------------------------
1 | 1 | 2013-11-01 05:42:38
2 | 2 | 2013-11-04 04:21:22
3 | 2 | 2013-11-16 15:11:55
I want to select those entries where there were no records of past 24 hours.
ie: id #3 was today at 15:11, so I don't want to select foreign_id that are 2.
I tried following with CakePHP 2.x in it's find api conditions
'HOUR(TIMEDIFF(NOW(), Traffic.accessed)) > 24'
Where Traffic is some table and accessed is some date field.
Unfortunately this still selects this record because it finds that there are previous entries that are having a date that are older than 24 hours.
Please help.
UPDATE
My actual query is like this
SELECT Traffic.accessed, Traffic.access_ip, Traffic.client_order_id
FROM `mdb`.`client_orders` AS `ClientOrder`
LEFT JOIN `geclicks`.`traffics`
AS `Traffic` ON (`Traffic`.`client_order_id` = `ClientOrder`.`id`)
WHERE `ClientOrder`.`status` = 'ACTIVE' AND `ClientOrder`.`offer_clicks` = 0
AND NOT (`ClientOrder`.`offer_id` = 0) AND ((`Traffic`.`access_ip` IS NULL)
OR (((`Traffic`.`access_ip` <> "444")
OR (((HOUR(TIMEDIFF(NOW(), MAX(`Traffic`.`accessed`))) > 24)
AND (`Traffic`.`access_ip` = "444"))))))
GROUP BY `Traffic`.`client_order_id` ORDER BY RAND() ASC
To select everything:
SELECT * FROM Records
Now let's filter out bad foreign keys (e.g. 1, 2, and 3):
SELECT * FROM Records WHERE foreign_id NOT IN (1, 2, 3)
Park that aside... how do we find the foreign_ids that have entries within 24 hours:
SELECT DISTINCT foreign_id FROM Records WHERE TIMEDIFF(CURRENT_TIMESTAMP(), date) <= 24
Plug that back in the original instead of the (1, 2, 3) example:
SELECT * FROM Records WHERE foreign_id NOT IN (SELECT DISTINCT foreign_id FROM Records WHERE TIMEDIFF(CURRENT_TIMESTAMP(), date) <= 24)

Select rows in mysql where difference between timestamp = 5 Minutes

I have a mysql table with a column "timestamp".
Now I want to select the elements with a difference of 5 minutes from each other.
Simplified example:
id | timestamp
===================
1 | 00:00
2 | 00:01
3 | 00:03
4 | 00:05
5 | 00:07
6 | 00:09
7 | 00:15
should return the ids 1, 4, 7
is this possible with a sql statement?
Here's a solution, which should work no matter what's the first timestamp.
SELECT id, ts FROM (
SELECT
IF(UNIX_TIMESTAMP(ts) = #prev OR UNIX_TIMESTAMP(ts) % 300 = #prev % 300, #mybool:=1, #mybool:=0) AS mybool,
IF(#mybool = 1, #prev:=UNIX_TIMESTAMP(ts), #prev:=#prev),
t.*
FROM Table2 t,
(SELECT #prev:=(SELECT MIN(UNIX_TIMESTAMP(ts)) FROM Table2 sqt), #mybool:=1) vars
ORDER BY id
) sq
WHERE mybool = 1;
See it working live in an sqlfiddle.
If the first timestamp is '00:00' you can simply do
SELECT *
FROM Table1
WHERE UNIX_TIMESTAMP(ts) % 300 = 0
ORDER BY id;
but you have to work with UNIX_TIMESTAMP() to make it work. A timestamp is stored in database as a 32-bit integer, still you have to tell MySQL that you want to work with the integer representation.
select id from table where timestamp % 5 = 0
Precise syntax depends a bit on the datatype of the timestamp column but in general you should be able to do it with the modulo operator.