In MySQL table I have these value 00:00:00 and 06:00:00.
I need sub three hours on 00:00:00, return 21:00:00 and add three hours on 06:00:00, return 09:00:00.
I have tried this sql query, without success.
Please help me, thank you in advance.
mysql> SELECT
SUBTIME('00:00:00', 3),
ADDTIME('06:00:00', 3);
+------------------------+------------------------+
| SUBTIME('00:00:00', 3) | ADDTIME('06:00:00', 3) |
+------------------------+------------------------+
| -00:00:03 | 06:00:03 |
+------------------------+------------------------+
1 row in set
mysql>
#Edit 1
mysql> SELECT
'00:00:00' - INTERVAL 3 HOUR,
'06:00:00' + INTERVAL 3 HOUR;
+------------------------------+------------------------------+
| '00:00:00' - INTERVAL 3 HOUR | '06:00:00' + INTERVAL 3 HOUR |
+------------------------------+------------------------------+
| NULL | NULL |
+------------------------------+------------------------------+
1 row in set
mysql>
#Edit 2
mysql> SELECT
DATE_ADD('00:00:00', INTERVAL 3 HOUR),
DATE_ADD('06:00:00', INTERVAL 3 HOUR);
SELECT
SUBTIME('00:00:00', '03:00:00'),
ADDTIME('06:00:00', '03:00:00');
+---------------------------------------+---------------------------------------+
| DATE_ADD('00:00:00', INTERVAL 3 HOUR) | DATE_ADD('06:00:00', INTERVAL 3 HOUR) |
+---------------------------------------+---------------------------------------+
| NULL | NULL |
+---------------------------------------+---------------------------------------+
1 row in set
+---------------------------------+---------------------------------+
| SUBTIME('00:00:00', '03:00:00') | ADDTIME('06:00:00', '03:00:00') |
+---------------------------------+---------------------------------+
| -03:00:00 | 09:00:00 |
+---------------------------------+---------------------------------+
1 row in set
mysql>
Since there is actually no time like '-00:00:03', you cannot apply time operation to do such thing.
If you really need to to this, I recommend you to
Change the column to INT type, and do the operation by yourself.
Change the column to DATETIME type, and try to deal with the cross-day problem by yourself.
Or apply the following...
SELECT CONCAT(
TIMESTAMPDIFF(
HOUR,
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s'),
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s') - interval 3 hour
) % 24,
":",
TIMESTAMPDIFF(
MINUTE,
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s'),
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s') - interval 3 hour
) % 60,
":",
TIMESTAMPDIFF(
SECOND,
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s'),
STR_TO_DATE(CONCAT('2015/01/01 ', '00:00:00'), '%Y/%m/%d %H:%i:%s') - interval 3 hour
) % 60
);
Just replace '00:00:00' and 3 hour to anything you want.
It just try to concat you time to a datetime string, and transform it into datetime then apply the interval operation. After that it apply the timediff to compute elapsed time by given unit(do not forget to % to get the correct domain). And finally concatenation the three differences.
This answer is kinda ugly, hope it is helpful to you.
It Should be
SELECT
TIME(SUBTIME(CONCAT(curdate(),' ','00:00:00'), 03:00:00)),
TIME(ADDTIME(CONCAT(curdate(),' ','00:00:00'), 03:00:00));
Related
This SQL.
It is very obvious that the seconds should be 86400 for one day. Why MYSQL return 1000000?
select str_to_date('2021-04-24 00:00:00', '%Y-%m-%d %H:%i:%s') - str_to_date('2021-04-23 00:00:00', '%Y-%m-%d %H:%i:%s') as seconds from dual;
+---------+
| seconds |
+---------+
| 1000000 |
+---------+
Subtracting two dates does not do what you're expecting here. What it does is format the dates as yyyyMMddHHmmss and subtract them as normal numbers, as in;
20210424000000
- 20210423000000
----------------
1000000
What you instead want is the TIMESTAMPDIFF function;
SELECT TIMESTAMPDIFF(
SECOND,
'2021-04-23 00:00:00',
'2021-04-24 00:00:00'
) AS seconds;
+---------+
| seconds |
+---------+
| 86400 |
+---------+
Because difference is
1 day
00 hours
00 minutes
00 seconds
If you try
select str_to_date('2021-05-25 00:04:05', '%Y-%m-%d %H:%i:%s') - str_to_date('2021-04-23 00:00:00', '%Y-%m-%d %H:%i:%s') as seconds;
Result is 102000405 , that means
1 month
2 days
0 hours
4 minutes
5 seconds
For seconds use UNIX_TIMESTAMP
select UNIX_TIMESTAMP('2021-04-24 00:00:00') - UNIX_TIMESTAMP('2021-04-23 00:00:00') as seconds;
I have an insurance policies table like this:
+-------------------------------------------------------------+
| id | cancellation_val | cancellation_interval | expire_date |
+-------------------------------------------------------------+
| 1 | 30 | day | 2019-06-09 |
| 2 | 2 | month | 2019-12-01 |
+-------------------------------------------------------------+
I need to get the ids of the policies that are going to expire based on cancellation, from today and within 4 months, calculating the last day of the month, like this pseudo-code:
'today' <= LAST_DAY( expire_date - cancellation_val/interval ) < 'today + 4 months'
Being not a pro I think I should use JOINs but I don't know how, after days of trying the only thing I achieved was this:
SELECT LAST_DAY(
DATE_FORMAT(
STR_TO_DATE(
(SELECT CASE cancellation_interval
WHEN "day" THEN date_sub(expire_date, INTERVAL cancellation_val DAY)
WHEN "month" THEN date_sub(data_scadenzaexpire_date, INTERVAL cancellation_val MONTH)
END
AS newDate
FROM insurance WHERE id=2
), '%Y-%m-%d'
), '%Y-%m-%d'
)
)
This is working but I don't need the "WHERE id=2" clause (because I need to process ALL rows of the table), and if I remove it I got error "subquery returns more than 1 row".
So how I can proceed? And using the result to stay between 'today' AND 'today + 4 months' ?
I think with some kind of JOIN I could do it in a easier way but I don't know how.
Thank you all
The problem is the structure of the query, not the LAST_DAY function.
We want to return the id values of rows that meet some condition. So the query would be of the form:
SELECT t.id
, ...
FROM insurance t
WHERE ...
HAVING ...
Introducing another SELECT keyword basically introduces a subquery. There are restrictions on subqueries... in the SELECT list, a subquery can return a single column and (at most) a single row.
So let's ditch that extra SELECT keyword.
We can derive the newdate as an expression of the SELECT list, and then we can reference that derived column in the HAVING clause. The spec said we wanted to return the id value, so we include that in the SELECT list. We don't have to return any other columns, but for testing/debugging, it can be useful to return the values that were used to derive the newdate column.
Something like this:
SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
, t.expire_date
, t.cancellation_interval
, t.cancellation_val
FROM insurance t
HAVING newdate >= DATE(NOW())
AND newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY newdate ASC
We don't have to include the newdate in the SELECT list; we could just replace occurrences of newdate in the HAVING clause with the expression.
We could also use an inline view to "hide" the derivation of the newdate column
SELECT v.id
, v.newdate
FROM ( SELECT t.id
, LAST_DAY(
CASE t.cancellation_interval
WHEN 'day' THEN t.expire_date - INTERVAL t.cancellation_val DAY
WHEN 'month' THEN t.expire_date - INTERVAL t.cancellation_val MONTH
ELSE t.expire_date
END
) AS newdate
FROM insurance t
) v
WHERE v.newdate >= DATE(NOW())
AND v.newdate <= DATE(NOW()) + INTERVAL 4 MONTH
ORDER
BY v.newdate ASC
check this query: remove the HAVING Line to see all rows
SELECT
IF(cancellation_interval = 'day',
i.expire_date - INTERVAL i.`cancellation_val` DAY,
i.expire_date - INTERVAL i.`cancellation_val` MONTH
) as cancellation_day,
i.*
FROM `insurance` i
HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
SAMPLES
MariaDB [test]> SELECT IF(cancellation_interval = 'day', i.expire_date - INTERVAL i.`cancellation_val` DAY, i.expire_date - INTERVAL i.`cancellation_val` MONTH ) as cancellation_day, i.* FROM `insurance` i HAVING cancellation_day < NOW() + INTERVAL 4 MONTH;
+------------------+----+------------------+-----------------------+-------------+
| cancellation_day | id | cancellation_val | cancellation_interval | expire_date |
+------------------+----+------------------+-----------------------+-------------+
| 2019-05-10 | 1 | 30 | day | 2019-06-09 |
+------------------+----+------------------+-----------------------+-------------+
1 row in set (0.001 sec)
When you use a SELECT query as an expression, it can only return one row.
If you want to process all the rows, you need to call LAST_DAY() inside the query, not on the result.
SELECT *
FROM insurance
WHERE CURDATE() <= LAST_DAY(
expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH))
AND LAST_DAY(expire_date - IF(cancellation_interval = 'day',
INTERVAL cancellation_val DAY,
INTERVAL cancellation_val MONTH)) < CURDATE + INTERVAL 4 MONTH
I seem to be having a bit of trouble coming up a query to achieve what I want. I have a table like the following..
| Date(TIMESTAMP) | Count |
|---------------------|-------|
| 2016-02-01 01:00:00 | 52 |
| 2016-01-05 11:30:00 | 14 |
| 2016-02-01 04:20:00 | 36 |
| ... | ... |
The table has about 40,000 rows. What I would like to do is grab the totals for multiple date ranges so I end up with the following...
| Period | Total |
|------------|-------|
| All | 10245 |
| Past year | 1401 |
| Past month | 104 |
| Past week | 26 |
Currently I am running through a loop in my PHP script and doing an individual query for each date range I'm looking for. Actually there are about 10 queries I'm doing per loop to grab different stats but for the example I'm simplifying it. This takes forever and I am hoping there is a more elegant way to do this, however I've spent quite a bit of time now trying different things and researching and have gotten nowhere. I understand how to use CASE to group but not when a record may need to be in multiple bins. Any help?
Try this UNION query:
SELECT 'All', COUNT(*) AS Total FROM yourTable
UNION
SELECT 'Past year', COUNT(*) AS Total
FROM yourTable
WHERE DATE(TIMESTAMP) > DATE_ADD(NOW(), INTERVAL -1 YEAR)
UNION
SELECT 'Past month', COUNT(*) AS Total
FROM yourTable
WHERE DATE(TIMESTAMP) > DATE_ADD(NOW(), INTERVAL -1 MONTH)
UNION
SELECT 'Past week', COUNT(*) AS Total
FROM yourTable
WHERE DATE(TIMESTAMP) > DATE_ADD(NOW(), INTERVAL -1 WEEK)
1st. get known to function getting first date of year, first date of month and first date of week.
Then compose your sql using count and filter with first and last date of different period.
ref:
MySQL Select First Day of Year and Month
month
https://stackoverflow.com/a/19259159/1258492
week https://stackoverflow.com/a/11831133/1258492
select 'All' as period, count(1) from
tbl
union
select 'Past Year' as period, count(1) from
tbl
where timestamp between
MAKEDATE(year(now())-1,1) and
last_day(MAKEDATE(year(now())-1,1) + interval 11 month)
union
select 'Past Month' as period, count(1) from
tbl
where timestamp between
LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY and
LAST_DAY(NOW() - INTERVAL 1 MONTH)
union
select 'Past Week' as period, count(1) from
tbl
where timestamp between
adddate(curdate(), INTERVAL 1-DAYOFWEEK(curdate())-7 DAY) and
adddate(curdate(), INTERVAL 7-DAYOFWEEK(curdate())-7 DAY) ;
You may use subqueries. Use one subquery per time breakdown like so:
SELECT everything, 'past year'
FROM
(
SELECT sum(c) AS 'everything'
FROM reports
) t1,
(
SELECT sum(c) AS 'past year'
FROM reports
WHERE d >= DATE_ADD(CURDATE(), INTERVAL -1 YEAR)
) t2
This is a follow up to my previous question A query for finding the nearest free time slot in mysql - why it doesn't work?
Basically I have a table:
id | start_time | duration
1 | 2015-10-21 19:41:35 | 15
2 | 2015-10-21 19:41:50 | 15
3 | 2015-10-21 19:42:05 | 15
4 | 2015-10-21 19:42:35 | 15
etc.
and it contains the event start_time and its duration. I asked for help with finding the nearest time slot in which the event can be placed between the existing events. #Richard came up with a perfect answer https://stackoverflow.com/a/33689786/3766930 and suggested a query:
SELECT (a.start_time + INTERVAL a.duration SECOND) AS free_after FROM notes a
WHERE
NOT EXISTS ( SELECT 1 FROM notes b WHERE b.start_time
BETWEEN (a.start_time + INTERVAL a.duration SECOND) AND
(a.start_time + INTERVAL a.duration SECOND) + INTERVAL 15 SECOND - INTERVAL 1 MICROSECOND) AND
(a.start_time + INTERVAL a.duration SECOND) BETWEEN '2015-10-21 19:41:30' AND '2015-10-21 19:43:50'
which works great.
Now I was wondering if there's a possibility of finding a most suitable date not only between the existing dates, but also right before them.
For example: I would set a begin_date as 2015-10-21 16:00:00 and end_date as 2015-10-21 21:00:00. Currently the result of #Richard's query would be 2015-10-21 19:42:20. But is there a way of creating a query that in this result will return 2015-10-21 19:41:20 as the closest one to the first date that is already in database?
A simple solution for this would be to use date_sub with an order by statement, limiting the results to show only 1 record.
This is the result:
SELECT date_sub(start_time, interval duration second) as free_before FROM `notes` where start_time>'2015-10-21 16:00:00' order by start_time asc limit 1
Bonus for you
Using the previous solution #Richard provided. Putting it all together to show all free times in 1 table could result in the following:
select * from (SELECT date_sub(start_time, interval duration second) as free_times FROM `notes` where start_time>'2015-10-21 16:00:00' order by start_time asc limit 1) a
union
(SELECT (a.start_time + INTERVAL a.duration SECOND) AS free_times FROM notes a
WHERE
NOT EXISTS ( SELECT 1 FROM notes b WHERE b.start_time
BETWEEN (a.start_time + INTERVAL a.duration SECOND) AND
(a.start_time + INTERVAL a.duration SECOND) + INTERVAL 15 SECOND - INTERVAL 1 MICROSECOND) AND
(a.start_time + INTERVAL a.duration SECOND) BETWEEN '2015-10-21 19:41:30' AND '2015-10-21 19:43:50')
Edit:
I will only write my part of the query. The other part was working correctly and only if you want me to change it I will (never fix something that ain't broke)
If you want interval of 10 seconds ->
SELECT date_sub(start_time, interval 10 second) as free_times FROM `notes` where start_time>'2015-10-21 16:00:00' order by start_time asc limit 1
If you want interval of 15 seconds ->
SELECT date_sub(start_time, interval 15 second) as free_times FROM `notes` where start_time>'2015-10-21 16:00:00' order by start_time asc limit 1
In this case you'll have to change your start_time and the duration accordingly.
Would this work for you?
Given:
select * from notes;
+----+---------------------+----------+
| id | start_time | duration |
+----+---------------------+----------+
| 1 | 2015-11-17 10:10:10 | 15 |
| 2 | 2015-11-17 10:20:40 | 15 |
| 3 | 2015-11-17 10:30:00 | 15 |
+----+---------------------+----------+
This result:
select (start_time - interval 15 second) as earlier_date
from notes
where start_time > '2015-11-17 10:15:00'
AND start_time < '2015-11-17 10:25:00'
order by start_time
limit 1;
+---------------------+
| earlier_date |
+---------------------+
| 2015-11-17 10:20:25 |
+---------------------+
Important: This sample doesn't pay any attention to entries that might fall immediately in front of the search window (because your example didn't include any). This query as-is will create overlaps if there are impinging entries just prior to the search window.
Take your base table and insert a fake row that is the minimum start time and subtract 15 seconds.
So instead of notes, use a subquery like this:
SELECT
MIN(start_time) - INTERVAL 15 seconds AS start_time,
0 AS duration
FROM notes
UNION ALL
SELECT start_time, duration
FROM notes
Given a year and calendar week, how can I get the tuesday of that week as a date?
In MySQL the STR_TO_DATE() function can do the trick in just one line!
Example: We want to get the date of the Tuesday of the 32th week of the year 2013.
SELECT STR_TO_DATE('2013 32 Tuesday', '%X %V %W');
would output:
'2013-08-13'
I think this is the best and shortest solution to your problem.
Given you have year and cw (calender week) as variables (e.g. from a SELECT statement) you can get the DATE as following:
DATE_SUB(
DATE_ADD(MAKEDATE(year, 1), INTERVAL cw WEEK),
INTERVAL WEEKDAY(
DATE_ADD(MAKEDATE(year, 1), INTERVAL cw WEEK)
) -1 DAY),
The phrase DATE_ADD(MAKEDATE(year, 1), INTERVAL cw WEEK) is duplicated; did not want to store a variable. The SQL-Statement worked nicely for me on MySQL.
UPDATE: Just for clarification: WEEKDAY(DATE_ADD(MAKEDATE(year, 1), INTERVAL cw WEEK)) will yield the first day of the week. Substracting a number from it (-1 for Tuesday; -2 for Wednesday and so forth will select a specific day in the week for you).
See here.
The definitions of calendar week I found all said "a period of seven consecutive days starting on Sunday".
The following is MySQL specific... your mileage may vary...
DATE_ADD(MAKEDATE(year, 1), INTERVAL cw WEEK) adds the weeks from the 1st of the year which is not correct...
mysql> select DATE_ADD(MAKEDATE(2011, 1), INTERVAL 1 WEEK);
+----------------------------------------------+
| DATE_ADD(MAKEDATE(2011, 1), INTERVAL 1 WEEK) |
+----------------------------------------------+
| 2011-01-08 |
+----------------------------------------------+
By this definition, it is only meaningful to have the calendar week range from 1-53, and have this represent the Sunday of that week. As such, we would add 2 days to the nth Sunday of the year to get Tuesday.
The following gets the date of the first sunday of the year...
mysql> select date_add('2012-01-01', interval (8 - dayofweek('2011-01-01')) % 7 DAY);
+------------------------------------------------------------------------+
| date_add('2012-01-01', interval (8 - dayofweek('2011-01-01')) % 7 DAY) |
+------------------------------------------------------------------------+
| 2012-01-02 |
+------------------------------------------------------------------------+
so this will get the date of the 10th sunday (note interval 9 week since we are already at 1)...
mysql> select date_add( date_add('2010-01-01', interval (8 - dayofweek('2010-01-01')) % 7 DAY) , interval 9 week);
+-----------------------------------------------------------------------------------------------------+
| date_add( date_add('2010-01-01', interval (8 - dayofweek('2010-01-01')) % 7 DAY) , interval 9 week) |
+-----------------------------------------------------------------------------------------------------+
| 2010-03-07 |
+-----------------------------------------------------------------------------------------------------+
add 2 more days to get to tuesday...
mysql> select date_add( date_add( date_add('2010-01-01', interval (8 - dayofweek('2010-01-01')) % 7 DAY) , interval 9 week), interval 2 day);
+--------------------------------------------------------------------------------------------------------------------------------+
| date_add( date_add( date_add('2010-01-01', interval (8 - dayofweek('2010-01-01')) % 7 DAY) , interval 9 week), interval 2 day) |
+--------------------------------------------------------------------------------------------------------------------------------+
| 2010-03-09 |
+--------------------------------------------------------------------------------------------------------------------------------+
or more generally:
select
date_add(
date_add(
date_add('<year>-01-01', interval (8 - dayofweek('<year>-01-01')) % 7 DAY)
, interval <week-1> week)
, interval <dayOfWeek> day
);
In looking at indago's answer and then doing a bunch of tests, I was getting the following week as the results.
I've made a minor adjustment, and the dates then matched:
SELECT STR_TO_DATE('2019 1 Monday', '%x %v %W') -- beginning of week
SELECT STR_TO_DATE('2019 1 Sunday', '%x %v %W') -- end of week
You can compare the results with here.
DELIMITER $$
CREATE FUNCTION fn_yearweek_to_date(
var_yearweek INTEGER UNSIGNED,
var_weekday ENUM(
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday'
)
)
RETURNS DATE DETERMINISTIC
BEGIN
RETURN STR_TO_DATE(CONCAT(var_yearweek, var_weekday), '%x%v%W');
END;
DELIMITER ;
SELECT
fn_yearweek_to_date(YEARWEEK(NOW(), 1), 'Sunday'),
fn_yearweek_to_date(YEARWEEK(NOW(), 1), 7)
;
Well theoretically you could use DATEPART with the dw parameter to get to find the first tuesday of the month and then add 7*[CalenderWeek] to get the appropriate date
http://msdn.microsoft.com/en-us/library/ms174420.aspx
I think it'd be easier to write the logic of the function using php.
If you use a php script, you can put all dates in a format similar to "day-month-year" and use a loop to go through every day (from 1980s to 2038 or from your mysql dates column).
http://www.php.net/manual/en/function.date-format.php
Then use date format on the dates in that loop to convert them to the days of the week.
Here is a listing of things that can be used in date formats. http://www.php.net/manual/en/function.date.php
D
N
l
w
all help you with day of the week.
Given solutions doesn't consider, that the first week of a year may start at the end of december. So we must check, if January 1st belongs to calendarweek of old or new year:
SET #week=1;
SET #year=2014;
SET #x_weeks_after_new_year=DATE_ADD(MAKEDATE(#year, 1), INTERVAL (SELECT IF(WEEKOFYEAR(MAKEDATE(#year, 1))>50 , 0 , -1))+#week WEEK);
SELECT
CONCAT(#year, '-', #week) WeekOfYear,
#weekStart:=DATE_SUB(#x_weeks_after_new_year, INTERVAL WEEKDAY(#x_weeks_after_new_year) DAY) Monday,
DATE_ADD(#weekStart, INTERVAL 6 DAY) Sunday
This will result in:
+------------+------------+------------+
| WeekOfYear | Monday | Sunday |
+------------+------------+------------+
| 2014-1 | 2013-12-30 | 2014-01-05 |
+------------+------------+------------+
Here is a sample that might help:
SET DATEFIRST 1
declare #wk int set #wk = 33
declare #yr int set #yr = 2011
select dateadd (week, #wk, dateadd (year, #yr-1900, 0)) - 2 -
datepart(dw, dateadd (week, #wk, dateadd (year, #yr-1900, 0)) - 4) as date
and the result is:
2011-08-16 00:00:00.000
which is today (Tuesday).
The upvoted solution worked for me in 2014 and 2015 but did not work for me in 2016 (possibly because the start of the Year is on Monday and not on Sunday.
I used the following function to correct this:
STR_TO_DATE(
CONCAT(mod(day_nr + 1 ,7) , '/', week_nr, '/', year), '%w/%u/%Y')
In my data :
day_nr = 0 -> Monday,
day_nr = 6 -> Sunday
So I had to fix that with a mod function