I am using MySQL and having issues getting the end of the week date from a DATETIME column, where the end of the week is considered Sunday,
My table looks like this:
Unique_ID
Date
123
2020-07-13 17:03:31.035
456
2021-01-01 15:02:19.029
789
2020-08-02 18:07:14.011
I am needing to get the week for each line where the week ends on Sunday. The time isn't needed. So the end result for 2021-01-01 would show 2021-01-03 since that week ends on Sunday. Does anyone know what function to use for this?
Here's an elaboration (I hope) of Akina's suggestion in the comment:
SELECT *,
dt + INTERVAL 6 DAY add6 /*add 6 day ahead*/,
DAYNAME(dt + INTERVAL 6 DAY) dn6 /*6 day ahead dayname*/,
dt + INTERVAL (6 - wkd) DAY nxtsun /*add 6 day ahead then subtract weekday value from date column*/,
DAYNAME(dt + INTERVAL (6 - wkd) DAY) nxtsundn
FROM
(SELECT *,
DATE(date) dt,
DAYNAME(date) dn,
WEEKDAY(date) wkd
FROM mytable) A;
Let's take the second row from your data sample to illustrate what is happening. The base query above:
SELECT *,
DATE(date) dt,
DAYNAME(date) dn,
WEEKDAY(date) wkd
FROM mytable
Will return the following.
Unique_ID
Date
dn
wkd
456
2021-01-01 15:02:19
Friday
4
Note that the WEEKDAY(date) (aliased as wkd in the table) returns 4. Which means it's Friday. According to the docs, WEEKDAY() function returns like the following:
0 = Monday
1 = Tuesday
2 = Wednesday
3 = Thursday
4 = Friday
5 = Saturday
6 = Sunday
Adding 6 day interval to the current WEEKDAY() result goes to the day before next same dayname of the current date value. So WEEKDAY(2021-01-01) which is on Friday, becomes 2021-01-07 which is on Thursday after being added with 6 day ahead. With a subtraction of the pervious obtained WEEKDAY() value, the operation becomes DATE + INTERVAL (6 - 4) DAY, which effectively becomes DATE + INTERVAL 2 DAY.
Here's a fiddle
Related
I'm trying to do a query in MYSQL selecting orders where the created_at datetime is between the previous 3:00 in the morning and the next 3:00 in the morning.
For example: if I make that query at 2020-03-31 11:00:00, it should show me all the orders where the created_at timestamp are between 2020-03-31 03:00:00 and 2020-04-01 03:00:00.
I've tried to make this by using this query:
select *
from "orders"
where "created_at" BETWEEN cast(curdate() as datetime) + interval 3 hour
and cast(curdate() as datetime) + interval 1 day + interval 3 hour
but that doesn't work correctly because if i execute this query between 0:00 and 3:00 in the morning (For example at 2020-03-31 00:30:00), it won't show anything, but it should show me all the orders where the created_at timestamp are between 2020-03-30 03:00:00 and 2020-03-31 03:00:00.
Any idea of how to do that? I've been trying to find a solution for 2 hours a i couldn't find the right query to do so.
I would do:
created at >= date(now()- interval 3 hour) + interval 3 hour
and created_at < date(now()- interval 3 hour) + interval 3 hour + interval 1 day
The logic is to offset the current date and time by 3 hours, remove the time component, and then add 3 hours.
Then you could do
select ...
where `created_at` BETWEEN DATE_SUB(concat(CURDATE(), ' 03:00:00'), INTERVAL 1 DAY)
AND concat(CURDATE(), ' 03:00:00');
Last year I set up a project management system, but now as we're on a new year I've been bit in my ass by some newbie coding (of me) :)
So now I really need your help to understand how to fix this in a good way once and for all.
The error:
The major problem right now is in the year roll over, right now my SQL query dosn't understand that the month of the new year (1) is larger than the current years (12) which then dosn't show the right projects in the list.
Any ideas? Thanks in advance!
This is my current SQL Query:
SELECT *
FROM projects
WHERE MONTH(CURDATE()) between MONTH(project_start) and MONTH(project_delivery)
AND YEAR(CURDATE()) between YEAR(project_start) and YEAR(project_delivery)
order by project_id
This is the raw structure of the table projects:
Project_id projet_start projet_delivery
1 2018-12-20 2018-12-22
2 2018-12-25 2018-12-29
3 2018-12-28 2018-12-28
4 2018-12-30 2019-01-22
BETWEEN works for actual dates:
SELECT *
FROM projects
WHERE CURDATE() between project_start and project_delivery
order by project_id
If you wanted everything from the month (month granularity) rather than day granularity:
SELECT *
FROM projects
WHERE CURDATE() between DATE_SUB(project_start, INTERVAL DAY(project_start)-1 DAY) and
DATE_ADD(DATE_SUB(project_delivery, INTERVAL DAY(project_delivery) DAY), INTERVAL 1 MONTH)
order by project_id
So if a project started on Dec 5th and finished on Jan 19th, this would give everything between Dec 1st and Jan 31st. Do bear in mind though that if any date on Jan 31st also has a time component (i.e. later than midnight), it will mean it's fractionally after this end date and won't show.
Comment if that's the case and you want help solving it (easiest to not use BETWEEN because it's always inclusive at each end, use < which is exclusive)
Update: Projects that have had some activity this month, i.e a project that:
started before, ended during
started before, ended after
started during, ended during
started during, ended after
The common thing all these have is that the start date of the project is before the end of this month, and also the end date of the project is after the first of this month
SELECT *
FROM projects
WHERE
--started before the end of this month
project_start < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE()) - 1 DAY), INTERVAL 1 MONTH) AND
--ended after the start of this month
project_delivery > DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE()) DAY)
order by project_id
Doing a DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE()) DAY) is a rather convoluted way of writing "subtract the current day number from the current date" i.e. 2019-01-02 minus 2 -> 2018-12-31. We look for dates > this (so as not to include it)
Similarly, DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAY(CURDATE()) - 1 DAY), INTERVAL 1 MONTH) Takes the current date, subs DAY-1 (so just 1 this time, not 2) off it to reach 1st of this month, then adds a month on to get to first of next month. 2019-01-02 -> 2019-01-01 -> 2019-02-01. Again < it so it's exclusive
There's a bug in going the other way (add a month then sub the day) if you're on eg the 31st of jan and you add a month first - there is no 31st of feb so mysql will cap at 28th of feb, then sub 31 days off it, giving a date that is not the end of jan (i.e. 28th of jan)
Just use date comparisons:
select p.*
from projects p
where curdate() >= project_start and
curdate() <= project_delivery;
I'm not sure why you would want to break the dates into their time components. However, that is totally unnecessary to compare them.
If you want to just do the comparison at the month level, then one method is to convert the values to months:
select p.*
from projects p
where year(curdate()) * 12 + month(curdate()) >= year(project_start) * 12 + month(project_start) and
year(curdate()) * 12 + month(curdate()) <= year(project_delivery) * 12 + month(project_delivery);
Alternatively, just move the dates to the beginning of months:
select p.*
from projects p
where curdate() >= project_start + interval 1 - day(project_start) day and
curdate() < ( project_delivery + interval (1 - day(project_delivery) day) + interval 1 month;
CREATE TABLE `sport_data` (
`id` int(255) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`sport` varchar(255) NOT NULL,
`musclePlan` varchar(255) NOT NULL,
`sport_time` varchar(255) NOT NULL,
`kcal` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
How can i get all data from this table from the last week (from Monday to Sunday)?
I have tried:
WHERE date >= curdate() - INTERVAL DAYOFWEEK(curdate())+5 DAY AND date < curdate() - INTERVAL DAYOFWEEK(curdate())-2 DAY
I don't know if this is correct that way?
Thanks in advance.
The >= and < pattern is what we usually use. That part looks right.
I think the question is about the expressions that returning the range start and end values.
I suggest that we test those expressions for a variety of date values, not just CURDATE(). We can use a value in place of CURDATE(), and check the results, and do that for a series of date values.
Conveniently, those expressions will evaluate the same in a SELECT list as they do in a WHERE clause. So we can run a SELECT statement, and check the results.
For example:
SELECT t.dt AS dt
, t.dt - INTERVAL DAYOFWEEK(t.dt)+5 DAY AS _ge
, t.dt - INTERVAL DAYOFWEEK(t.dt)-2 DAY AS _lt
FROM (
SELECT CURDATE() + INTERVAL 0 DAY AS dt
UNION ALL SELECT CURDATE() + INTERVAL -1 DAY
UNION ALL SELECT CURDATE() + INTERVAL -2 DAY
UNION ALL SELECT CURDATE() + INTERVAL -3 DAY
UNION ALL SELECT CURDATE() + INTERVAL -4 DAY
UNION ALL SELECT CURDATE() + INTERVAL -5 DAY
UNION ALL SELECT CURDATE() + INTERVAL -6 DAY
UNION ALL SELECT CURDATE() + INTERVAL -7 DAY
UNION ALL SELECT CURDATE() + INTERVAL -8 DAY
UNION ALL SELECT CURDATE() + INTERVAL -9 DAY
UNION ALL SELECT CURDATE() + INTERVAL -10 DAY
) t
If the expressions are returning the values we expect, in accordance with the specification, for each possible date value, then the expressions are right.
If the expressions are returning values that don't meet the spec, then we need to make adjustments. Note that an expression that "works" on a Wednesday date might not "work" on a Sunday date.)
DEMO: Showing 3 approaches and why current doesn't work.
If we assume by "last week" you mean the last full week Monday - Sunday of a week prior to the day you're presently on...
So if today was 20180422, you'd want 20180409-20180415
SELECT *
FROM SO50026532_sport_data
CROSS JOIN (SELECT #Today:=curdate()) z
WHERE date >= #today - interval (6 + case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end) day
and date <=#today - interval (case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end) day;
Or if your a fan of the >= and < then
SELECT *
FROM SO50026532_sport_data
CROSS JOIN (SELECT #Today:=curdate()) z
WHERE date >= #today - interval (6 + case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end) day
and date <#today - interval (case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end-1) day;
in the 2nd example we had to subtract by 1 since dayofweek isn't a 0 based. Of course I could have just date shifted everything down 2 and set sunday to 6... then I wouldn't' need to subtract by 1. and then we'd be adding 7 instead of 6 on the 1st part of the where. (demo has these 3 and your initial example showing what happens on 4/22.
Dayofweek starts on Sunday being 1. So I use a case statement to shift 1 to 7 and and all the others down 1 giving us Monday = 1 and sunday = 7
The cross join to derived table z was so I could control the curdate() easier and test. You could replace the variable with curdate() if you want and eliminate the cross join and derived table.
The first where clause subtracts 6 days (1 week and then the # of days from current date back to monday. This ensures we always start 1 week back from current date and on a monday. then we only get dates to the Sunday of that week.
So, I can do the following to get data from last week.
select * from table where week(date)=week(curdate())-1
Same for 2 weeks ago. But this fails if the data is in the prior year. What query can I use to get data from n weeks ago regardless of what year the data belongs to.
Edit: The week starts on Sunday 12AM and ends Saturday 11:59PM
When does a "week" start? Sunday? Monday? The same day of week as today?
Assuming you are happy with the last option, do this:
SELECT ...
WHERE date >= CURDATE() - INTERVAL $n WEEK
AND date < CURDATE() - INTERVAL $n-1 WEEK
Example
mysql> SELECT CURDATE(), CURDATE() - INTERVAL 9 WEEK, CURDATE() - INTERVAL 9-1 WEEK;
+------------+-----------------------------+-------------------------------+
| CURDATE() | CURDATE() - INTERVAL 9 WEEK | CURDATE() - INTERVAL 9-1 WEEK |
+------------+-----------------------------+-------------------------------+
| 2015-02-22 | 2014-12-21 | 2014-12-28 |
+------------+-----------------------------+-------------------------------+
If you need the week to start on a particular DOW, the query is messier, by further subtracting INTERVAL DAYOFWEEK(CURDATE()) DAY. And that could be off a little.
The start of the current week (assuming it is Sunday) is CURDATE() - INTERVAL (WEEKDAY(CURDATE() + INTERVAL 1 DAY)). So, replace CURDATE() in the above expression (twice) with this long mess.
I would like to ask you that i want to find last working day in previous month in MYSQL.
How to do that ?
this code find last day previous month as the following:
LAST_DAY(Now()- INTERVAL 1 MONTH)
Result that i need as below:
Current Month= 2013-07-01====> Results should be 2013-06-28
Current Month= 2013-06-06====> Results should be 2013-05-31
How to find last working day in previous month ?
Regards
Here’s an example of what I thought of:
LAST_DAY(CURDATE() - INTERVAL 1 MONTH) -
INTERVAL (CASE WEEKDAY(LAST_DAY(CURDATE() - INTERVAL 1 MONTH))
WHEN 5 THEN 1
WHEN 6 THEN 2
ELSE 0 END) DAY
Try something like:
SET #lastworkingday = DATE_SUB(CURDATE(), INTERVAL DAYOFMONTH(CURDATE()) DAY);
// If saturday, take it back one day
IF DAYOFWEEK(#lastworkingday) = 7
THEN #lastworkingday := DATE_SUB(#lastworkingday, INTERVAL 1 DAY);
// If sunday, take it back two days
ELSE IF DAYOFWEEK(#lastworkingday) = 1
THEN #lastworkingday := DATE_SUB(#lastworkingday, INTERVAL 2 DAY);
There may be a more elegant way of writing this (I'd be surprised if there wasn't), but Gumbo's solution might look like this...
Note I've used #dt for testing. You could just substitute CURDATE() in your script.
SET #dt = '2012-07-01'; -- Last day of the previous month (June 2012) was a Saturday
SELECT CASE DAYOFWEEK(LAST_DAY(#dt-INTERVAL 1 MONTH))
WHEN 1 THEN LAST_DAY(#dt-INTERVAL 1 MONTH)-INTERVAL 2 DAY
WHEN 7 THEN LAST_DAY(#dt-INTERVAL 1 MONTH)-INTERVAL 1 DAY
ELSE LAST_DAY(#dt-INTERVAL 1 MONTH)
END x;
+------------+
| x |
+------------+
| 2012-06-29 | -- Friday 29th
+------------+
SET #dt = '2012-08-01'; -- Last day of the previous month (July 2012) was a Tuesday
SELECT CASE DAYOFWEEK(LAST_DAY(#dt-INTERVAL 1 MONTH))
WHEN 1 THEN LAST_DAY(#dt-INTERVAL 1 MONTH)-INTERVAL 2 DAY
WHEN 7 THEN LAST_DAY(#dt-INTERVAL 1 MONTH)-INTERVAL 1 DAY
ELSE LAST_DAY(#dt-INTERVAL 1 MONTH)
END x;
+------------+
| x |
+------------+
| 2012-07-31 | -- Tuesday 31st
+------------+
SET #dt = '2012-10-01'; -- Last day of the previous month (September 2012) was a Sunday
SELECT CASE DAYOFWEEK(LAST_DAY(#dt-INTERVAL 1 MONTH))
WHEN 1 THEN LAST_DAY(#dt-INTERVAL 1 MONTH)-INTERVAL 2 DAY
WHEN 7 THEN LAST_DAY(#dt-INTERVAL 1 MONTH)-INTERVAL 1 DAY
ELSE LAST_DAY(#dt-INTERVAL 1 MONTH)
END x;
+------------+
| x |
+------------+
| 2012-09-28 | -- Friday 28th
+------------+
Another way is to build a calendar table of all dates. Ignoring public holidays, the first and last working days of the month then are simply the min and max weekdays of the month.
Although, there is an argument that goes 'so why not just figure this bit out in your application code'!
To get last working date you have to follow following steps :
1. first of all get last date of last month .
2.check whether it is working day or not by weekday function . if it is sunday then subtract 2 from lastdate , if it is saturday then subtract 1 from lastdate otherwise do not change anything .
we can do this thing by case statement . check following query . it will really works :
SELECT
CASE WEEKDAY(LAST_DAY(CONCAT(YEAR(CURDATE()),'-' , MONTH(CURDATE())-1 ,'-' , 1)))
WHEN 6
THEN DATE_ADD(LAST_DAY(CONCAT(YEAR(CURDATE()),'-' , MONTH(CURDATE())-1 ,'-' , 1)),INTERVAL -2 DAY)
WHEN 5
THEN DATE_ADD(LAST_DAY(CONCAT(YEAR(CURDATE()),'-' , MONTH(CURDATE())-1 ,'-' , 1)),INTERVAL - 1 DAY)
ELSE
LAST_DAY(CONCAT(YEAR(CURDATE()),'-' , MONTH(CURDATE())-1 ,'-' , 1)) END;
http://sqlfiddle.com/#!2/d41d8/15940
if you subtract the day of current month then you will get the last day of previous month
(DATE_SUB(CURDATE(),INTERVAL DAYOFMONTH(CURDATE()) DAY))
For example, on the 12th of the month if we subtract 12 days,then well get the the last day of the previous month: