I have a table that has two datetime columns (one for start time and one for end time).
I need to be able to select all entries and order them based on the time difference between these two columns (the period between the start and end time columns)
Try this::
select * from table order by TIMEDIFF(to, from)
SELECT ...
FROM ...
ORDER BY DATEDIFF(endDate, starDate);
[edit]
The above query works with dates. As pointed out by Sashi Kant, it would ignore the time part of your DATETIME
On the other hand, TIMEDIFF fails if the difference is outside of the TIME range (-838:59:59 to 838:59:59).
A complete solution could be:
SELECT ...
FROM ...
ORDER BY
DATEDIFF(endDate, starDate),
TIMEDIFF(TIME(endDate), TIME(starDate));
Probably performs terribly, though... If such precision is required, and if the difference between the two dates may be outside of the TIME range, then splitting each of your DATETIME columns into two DATE and TIME columns would certainly perform better, assuming you would apply one index on each of the four resulting columns.
SELECT something FROM table ORDER BY TIMEDIFF(end_date, start_date);
There are mysql defined functions to compute the difference between two timestamps, like TIME_TO_SEC and TIMEDIFF.
SELECT TIME_TO_SEC(TIMEDIFF(from_time, to_time)) diff from your_table order by diff;
This is correct: select * from table order by TIMEDIFF(to, from)
But if you are not selecting timediff in select statement you might have to add this line of code:
SET sql_mode = '';
select * from table order by TIMEDIFF(to, from)
to disable ONLY_FULL_GROUP_BY mode
For anyone who just needs the time distance between two timestamps, irrespective or without having to know which date is earlier and which date is later, you could use the following:
SELECT t.*,
TIMEDIFF( t.date_column, t.other_date_column ) AS distance
FROM table t
ORDER BY IF(distance<0, 0-distance, distance)
Or if you have a fixed/static target timestamp:
SELECT t.*,
TIMEDIFF( t.date_column, '2021-04-20' ) AS distance
FROM table t
ORDER BY IF(distance<0, 0-distance, distance)
The IF() function always returns a positive "distance" measurement of time between the two timestamps. This allowed me to find the nearest available record whether it was earlier or later than the target timestamp.
Related
Similar Questions
First off, I'm pretty sure something similar must be answered somewhere but I haven't been able to find it. Here are some similar pages that aren't what I'm looking for:
MySQL - UPDATE query based on SELECT Query
That's calculating the time difference between two rows which the ID already known of both rows and in my case I have no idea what the ID will be of the previous row. Also, using DATETIME when my table is a simple Unix timestamp.
Calculate the time difference between two timestamps in mysql
Seems to depend on functions related to a specific TIMESTAMP type.
Difference between current and previous timestamp
Seems to be asking the right thing but his table doesn't have realistic timestamps and I can't get any of the answers to work.
MySQL: how to get the difference between two timestamps in seconds
This one is unrelated. My time is already in timestamps so I can just subtract.
Calculate delta(difference of current and previous row) mysql group by specific column
This one is pretty close, but it's a SELECT query and I'm trying to UPDATE the table to have this information in a new column so I can use it in subsequent queries. The DATEDIFF it uses could probably easily be converted to simple subtraction. I don't really want to have to set up a new table with all the possible difference values in seconds.
What I'm Trying To Do
In Excel, I could take rows, sort them by a 'timestamp' column, and then set a new column (call it 'delta') which is equal to one timestamp minus the previous timestamp. So I'll get a value in seconds which is the time that's passed between one timestamp and the previous one. If a row was, for example, 1 second after the previous, the value would be '1' or if it was a minute later it would be '60'. All of the timestamps are Unix timestamps, so it's just seconds since January 1st 1970.
It's easy to add the new column in MySQL, but I can't seem to find the right query to populate it.
Here's an example table with the delta column filled in:
id
timestamp
delta
3
1623400800
NULL
2
1623444000
43200
56
1623444060
60
Solution Constraints
Ideally, since there are a lot of rows, I'd like something that functions similarly to what I'd do in Excel, for efficiency. That is, sorting the table and filling in the delta based on the data of the sorted table.
Granted, if that's not possible, then a query that has to do an individual search to populate 'delta' for every row is probably acceptable for the time being. I'll just have to run it a lot of times on portions of the data.
You would just use lag():
select t.*,
(timestamp - lag(timestamp) over (order by timestamp)) as delta
from t;
In older versions of MySQL, you can do this rather painfully using variables:
select t.*,
(timestamp -
(case when (#tempt := #prevt) = null -- never happens
then 0
when (#prev := timestamp) = null -- never happens
then 0
else #tempt
end)
) as delta
from (select t.* from t order by timestamp) t cross join
(select #prevt := -1) params;
Note that this uses a case expression to implement "sequential" logic for the variable processing. This is a hack and you can understand why MySQL has switched to using standard window functions for this type of operation.
You can try having an index on (timestamp) to improve performance.
EDIT:
If you have an index on timestamp and the timestamps are unique, then you can do:
update t join
(select t.*,
(timestamp - lag(timestamp) over (order by timestamp)) as delta
from t
) tt
using (timestamp)
set t.delta = tt.delta;
I have a table with call records. Each call has a 'state' CALLSTART and CALLEND, and each call has a unique 'callid'. Also for each record there is a unique autoincrement 'id'. Each row has a MySQL TIMESTAMP field.
In a previous question I asked for a way to calculate the total of seconds of phone calls. This came to this SQL:
SELECT SUM(TIME_TO_SEC(differences))
FROM
(
SELECT SEC_TO_TIME(TIMESTAMPDIFF(SECOND,MIN(timestamp),MAX(timestamp)))as differences
FROM table
GROUP BY callid
)x
Now I would like to know how to do this, only for callid's that also have a row with the state CONNECTED.
Screenshot of table: http://imgur.com/gmdeSaY
Use a having clause:
SELECT SUM(difference)
FROM (SELECT callid, TIMESTAMPDIFF(SECOND, MIN(timestamp), MAX(timestamp)) as difference
FROM table
GROUP BY callid
HAVING SUM(state = 'Connected') > 0
) c;
If you only want the difference in seconds, I simplified the calculation a bit.
EDIT: (for Mihai)
If you put in:
HAVING state in ('Connected')
Then the value of state comes from an arbitrary row for each callid. Not all the rows, just an arbitrary one. You might or might not get lucky. As a general rule, avoid using the MySQL extension that allows "bare" columns in the select and having clauses, unless you really use the feature intentionally and carefully.
Possible duplicate of: How to select date from datetime column?
But the problem with the accepted answer is it will preform a full table scan.
I want to do something like this:
UPDATE records SET earnings=(SELECT SUM(rate)
FROM leads
WHERE records.user_id=leads.user_id
AND DATE(leads.datetime)=records.date)
Notice the last portion: DATE(leads.datetime)=records.date. This does exactly what it needs to do, but it has to scan every row. Some users have thousands of leads so it can take a while.
The leads table has an INDEX on user_id,datetime.
I know you can use interval functions and do something like WHERE datetime BETWEEN date AND interval + days or something like that.
What is the most efficient and accurate way to do this?
I'm not familiar with date functions in MySQL, but try changing it to
UPDATE records SET earnings=
(SELECT SUM(rate)
FROM leads
WHERE records.user_id=leads.user_id
AND leads.datetime >= records.date
And leads.datetime < records.date [+ one day]) -- however you do that in MySQL
You are getting a complete table scan because the expression DATE(leads.datetime) is not Sargable. This is because it is a function which needs to operate on the value stored in a column of the table, and which is also stored in any index on that column. The function's value, obviously, cannot be pre-computed and stored in any index, only the actual column value, so no index search can identify which rows will, after having the function executed on them, meet the criteria expressed in the Where clause predicate. Changing the expression so that the column value is, by itself on one side or the other of the where clause operator, (equal sign or whatever), allows the column values in the index to be searched based on a single expression.
You can try this:
UPDATE records
SET earnings = (SELECT SUM(rate)
FROM leads
WHERE records.user_id=leads.user_id AND
leads.datetime >= records.date and
leads.datetime < date_add(records.date, interval 1 day)
);
You need an index on leads(user_id, datetime) for this to work.
I have a table "A" with a "date" field. I want to make a select query and order the rows with previous dates in a descending order, and then, the rows with next dates in ascending order, all in the same query. Is it possible?
For example, table "A":
id date
---------------------
a march-20
b march-21
c march-22
d march-23
e march-24
I'd like to get, having as a starting date "march-22", this result:
id date
---------------------
c march-22
b march-21
a march-20
d march-23
e march-24
In one query, because I'm doing it with two of them and it's slow, because the only difference is the sorting, and the joins I have to do are a bit "heavy".
Thanks a lot.
You could use something like this -
SELECT *
FROM test
ORDER BY IF(
date <= '2012-03-22',
DATEDIFF('2000-01-01', date),
DATEDIFF(date, '2000-01-01')
);
Here is a link to a test on SQL Fiddle - http://sqlfiddle.com/#!2/31a3f/13
That's wrong, sorry :(
From documentation:
However, use of ORDER BY for individual SELECT statements implies nothing about the order in which the rows appear in the final result because UNION by default produces an unordered set of rows. Therefore, the use of ORDER BY in this context is typically in conjunction with LIMIT, so that it is used to determine the subset of the selected rows to retrieve for the SELECT, even though it does not necessarily affect the order of those rows in the final UNION result. If ORDER BY appears without LIMIT in a SELECT, it is optimized away because it will have no effect anyway.
This should do the trick. I'm not 100% sure about adding an order in a UNION...
SELECT * FROM A where date <= now() ORDER BY date DESC
UNION SELECT * FROM A where date > now() ORDER BY date ASC
I think the real question here is how to do the joining once. Create a temporary table with the result of joining, and make the 2 selects from that table. So it will be be time consuming only on creation (once) not on select query (twice).
CREATE TABLE tmp SELECT ... JOIN -- do the heavy duty here
With this you can make the two select statenets as you originally did.
This simple SQL problem is giving me a very hard time. Either because I'm seeing the problem the wrong way or because I'm not that familiar with SQL. Or both.
What I'm trying to do: I have a table with several columns and I only need two of them: the datetime when the entry was created and the id of the entry. Note that the hours/minutes/seconds part is important here.
However, I want to group my selection according to the DATE part only. Otherwise all groups will most likely have 1 element.
Here's my query:
SELECT MyDate as DateCr, COUNT(Id) as Occur
FROM MyTable tb WITH(NOLOCK)
GROUP BY CAST(tb.MyDate as Date)
ORDER BY DateCr ASC
However I get the following error from it:
Column "MyTable.MyDate" is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If I don't do the cast in the GROUP BY, everything fine. If I cast MyDate to DATE in the SELECT and keep the CAST from GROUP BY, everything fine once more. Apparently it wants to keep the same DATE or DATETIME format in the GROUP BY as in the SELECT.
My approach can be completely wrong so I am not necessarily looking to fix the above query, but to find the proper way to do it.
LE: I get the above error on line 1.
LE2: On a second look, my question indeed is not very explicit. You can ignore the above approach if it is completely wrong. Below is a sample scenario
Let me tell you what I need: I want to retrieve (1) the DateTime when each entry was created. So if I have 20 entries, then I want to get 20 DateTimes. Then if I have multiple entries created on the same DAY, I want the number of those entries. For example, let's say I created 3 entries on Monday, 1 on Tuesday and 2 today. Then from my table I need the datetimes of these 6 entries + the number of entries which were created on each day (3 for 19/03/2012, 1 for 20/03/2012 and 2 for 21/03/2012).
I'm not sure why you're objecting to performing the CONVERT in both the SELECT and the GROUP BY. This seems like a perfectly logical way to do this:
SELECT
DateCr = CONVERT(DATE, MyDate),
Occur = COUNT(Id)
FROM dbo.MyTable
GROUP BY CONVERT(DATE, MyDate)
ORDER BY DateCr;
If you want to keep the time portion of MyDate in the SELECT list, why are you bothering to group? Or how do you expect the results to look? You'll have a row for every individual date/time value, where the grouping seems to indicate you want a row for each day. Maybe you could clarify what you want with some sample data and example desired results.
Also, why are you using NOLOCK? Are you willing to trade accuracy for a haphazard turbo button?
EDIT adding a version for the mixed requirements:
;WITH d(DateCr,d,Id) AS
(
SELECT MyDate, d = CONVERT(DATE, MyDate), Id
FROM dbo.MyTable)
SELECT DateCr, Occur = (SELECT COUNT(Id) FROM d AS d2 WHERE d2.d = d.d)
FROM d
ORDER BY DateCr;
Even though this is an old post, I thought I would answer it. The solution below will work with SQL Server 2008 and above. It uses the over clause, so that the individual lines will be returned, but will also count the rows grouped by the date (without time).
SELECT MyDate as DateCr,
COUNT(Id) OVER(PARTITION BY CAST(tb.MyDate as Date)) as Occur
FROM MyTable tb WITH(NOLOCK)
ORDER BY DateCr ASC
Darren White