last friday of the month in MySQL - mysql

Hallo. How can I get the date of last friday of current month with mysql?
Thanks in advance.
edit. Hi Stefan. This is what I've done
set #ldom = dayofweek(last_day(curdate()));
select
case
when #ldom = 7 then last_day(curdate()) - interval 1 day
when #ldom = 6 then last_day(curdate())
when #ldom = 5 then last_day(curdate()) - interval 6 day
when #ldom = 4 then last_day(curdate()) - interval 5 day
when #ldom = 3 then last_day(curdate()) - interval 4 day
when #ldom = 2 then last_day(curdate()) - interval 3 day
else last_day(curdate()) - interval 2 day
end as last_friday
but I'd like to know if there is a smarter way.
EDIT. I made some test bases on samplebias answer to find last monday,tuesday and so on of a specific month.
These are the correct queries.
-- last sunday of month
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 6) % 7),"%Y%m%d") -- 2011-04-24
-- last saturday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 5) % 7),"%Y%m%d") -- 2011-04-30
-- last friday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 4) % 7),"%Y%m%d") -- 2011-04-29
-- last thursday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 3) % 7),"%Y%m%d") -- 2011-04-28
-- last wednesday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 2) % 7),"%Y%m%d") -- 2011-04-27
-- last tuesday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data)) - 1) % 7),"%Y%m%d") -- 2011-04-26
-- last monday
set #data = '2011-04-01';
select str_to_date(last_day(#data) - ((7 + weekday(last_day(#data))) % 7),"%Y%m%d") -- 2011-04-25
Hope that it helps someone else.
Thanks again to samplebias. ;)

Here is a simplified version using just date math:
SELECT LAST_DAY(NOW()) - ((7 + WEEKDAY(LAST_DAY(NOW())) - 4) % 7);
Depending on how NOW() gets evaluated (once or twice per statement), you might want to still wrap this in a function and store the result of NOW() into a variable, and then use the variable for the LAST_DAY(var) call, to avoid a race condition where the month rolls over between calls to NOW().

-- Today is 05 April 2013
-- Get Last Friday from MySQL
SELECT DATE_FORMAT(LAST_DAY(NOW()) - ((7 + WEEKDAY(LAST_DAY(NOW())) - 4) % 7), '%Y-%m-%d') last_friday;
-- Output
last_friday
-------------
2013-04-26

Related

Converting a SQL Function to DQL function

I have a function like this to exclude mondays in between two dates;
CREATE FUNCTION TOTAL_EXCLUDE_MONDAY(startDate DATE, endDate DATE)
RETURNS INT
RETURN ABS
(DATEDIFF(endDate, startDate)) + 1
- 1 ABS(DATEDIFF(ADDDATE(endDate, INTERVAL 1 - DAYOFWEEK(endDate) DAY),
ADDDATE(startDate, INTERVAL 1 - DAYOFWEEK(startDate) DAY ))) / 7 * 1
- (DAYOFWEEK(IF(startDate < endDate, startDate, endDate )) = 2);
I'm using this function in this query;
SELECT COUNT(ticket.ticketID)/(select TOTAL_EXCLUDE_MONDAY(startDate,endDate))
FROM ticket,exhibitions
WHERE exhibitions.exID = 6 and iptal = 0 and ticket.exID = 6;
I tried to convert to DQL but i cant figure out how.

Get first day of week - SQL

I have the week number and year inside a two variables ($week and $year), and I want to know what is the first day (Monday) of that week, of that year.
For example, the value of my variable ($week) is 30 (ie week 30) and the $year is 2017 (year 2017), I want it to return the first day of week 30 of 2017, which is July 24
How do I do that?
Thank you!
Declare #Year int,#Week int,#YearText varchar(4)
set #Year = 2017
set #Week = 10
set #YearText = #Year
print dateadd(day,1 - datepart(dw, #YearText + '-01-01')+ (#Week-1) * 7,#YearText + '-01-01')

MYSQL Add working days to date

I want to add 5 days to the provided date, but the calculation must skip weekends.
I already know how to add 5 days without skipping weekends:
SELECT DATE_ADD(`date_field`, INTERVAL 5 DAY) As FinalDate
FROM `table_name`;
Now I want the returned value to skip weekends.
Currently if date_field = 2016-07-22 the results will be 2016-07-27
But I want the results to be 2016-07-29
Try this:
SELECT DATE_ADD(
date_field,
INTERVAL 5 +
IF(
(WEEK(date_field) <> WEEK(DATE_ADD(date_field, INTERVAL 5 DAY)))
OR (WEEKDAY(DATE_ADD(date_field, INTERVAL 5 DAY)) IN (5, 6)),
2,
0)
DAY
) AS FinalDate
FROM `table_name`;
How it works:
Firstly, it will add 5 days on your date.
Secondly, when date_field and 5 days later are in two different weeks, it must be added additional 2 days.
Thirdly, when 5 days later is Sat or Sun, it must be added additional 2 days.
This can easily be done for an arbitrary amount of days with a recursive CTE:
WITH RECURSIVE a AS (
SELECT
CURRENT_DATE date, -- Start date
0 days
UNION
SELECT
-- Always increment the date
a.date + INTERVAL 1 DAY AS date,
-- Increment the work day count only if it's not a weekend day
a.days + (WEEKDAY(a.date + INTERVAL 1 DAY) < 5) AS days
FROM a
WHERE
-- Keep going until the week day count reaches 10
a.days < 10 -- Amount of days to add
)
SELECT MAX(date)
FROM a
In the example situation you would use a subquery:
SELECT
(
WITH RECURSIVE a AS (
SELECT
date_field date,
0 days
UNION
SELECT
a.date + INTERVAL 1 DAY AS date,
a.days + (WEEKDAY(a.date + INTERVAL 1 DAY) < 5) AS days
FROM a
WHERE a.days < 5
)
SELECT MAX(date)
FROM a
) AS final_date
FROM table_name
I did try your solution but faced a problem when using it with a larger interval (e.g 20 days). It works perfectly with little intervals though.
Example : for '2017-10-04' + 20 days, your algorithm return '2017-10-26'. It should be '2017-11-01' since we skip 4 weekends.
The numbers of days you add isn't calculated depending on the difference between the 2 week #, so the maximum days you can add is 2 and in my case, it should be 8 (4x2).
I modified your code to end up with this (I also add variables, much more convenient to modify)
SELECT
#ID:='2017-10-04' as initial_date, -- the initial date in the right format to manipulate (add x day)
#DTA:=20 as days_to_add, -- number of days to add
#DA:= DATE_ADD(#ID, INTERVAL #DTA DAY) as date_add,
#LASTDAY := WEEKDAY(#DA) as last_day, -- the day (Monday, Tuesday...) corresponding to the initial date + number of days to add
#WEEK1 := DATE_FORMAT(#ID, '%v') as initial_date_week, -- format the initial date to match week mode 3 (Monday 1-53)
#WEEK2 := DATE_FORMAT(#DA, '%v') as added_date_week_nbr, -- the week # of the initial_date + number of days to add
#WEEKDIFF := #WEEK2 - #WEEK1 as week_difference, -- the difference between week 2 and week 1
DATE_ADD(#ID,
INTERVAL #DTA +
if ( #WEEKDIFF > 0 or #LASTDAY in (5,6),
2,
0
) +
if (#WEEKDIFF > 1,
#WEEKDIFF*2,
0
) DAY
) AS FinalDate
The way I get my week numbers can seems weird but this is because I'm running this in France and my DB seems to be configured in a way that weeks are natively starting by Sunday, "%v" represent the 'mode 3' for weeks, you can check the MySQL documentation here for more details : https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html (ctrl + F > '%v')
I didn't implement the public holiday yet, but I'm thinking of adding X days in the calculation each times one of this day is in the period we're looking at.
According to my (few) tests, this should work. Let me know if not
try this out, should work nicely, basically loop through each of the days and check if they are saturday or sunday, ignore them.
https://social.technet.microsoft.com/wiki/contents/articles/30877.t-sql-extending-dateadd-function-to-skip-weekend-days.aspx
Plus hollydays 1 or 2
select GREATEST(WEEKDAY(NOW()) - 4, 0) 'hollydays'
My solution is to create a function that returns the calculated date, it will consider the consecutive weekends:
DELIMITER $$
CREATE FUNCTION `add_working_days_to_current_month`(ndays INT) RETURNS DATE
NO SQL
BEGIN
declare finalDate, startDate, originalFinalDate DATE;
declare weekNumberStartDate, weekNumberEndDate, weekDiff INT;
set startDate = DATE(CONCAT(YEAR(DATE_SUB(current_date(), INTERVAL 1 MONTH)),"-",MONTH(DATE_SUB(current_date(), INTERVAL 1 MONTH)),"-",DAY(LAST_DAY(DATE_SUB(current_date(), INTERVAL 1 MONTH)))));
set weekNumberStartDate = WEEK(startDate);
set finalDate = DATE_ADD(startDate, INTERVAL ndays DAY);
set originalFinalDate = finalDate;
set weekNumberEndDate = WEEK(finalDate);
IF(weekNumberEndDate != weekNumberStartDate) THEN
set weekDiff = (weekNumberEndDate - weekNumberStartDate) * 2;
set finalDate = DATE_ADD(finalDate, INTERVAL weekDiff DAY);
END IF;
set weekNumberStartDate = WEEK(originalFinalDate);
set weekNumberEndDate = WEEK(finalDate);
IF(weekNumberEndDate != weekNumberStartDate) THEN
set weekDiff = (weekNumberEndDate - weekNumberStartDate) * 2;
set finalDate = DATE_ADD(finalDate, INTERVAL weekDiff DAY);
END IF;
IF(WEEKDAY(finalDate) IN (5, 6)) THEN
set finalDate = DATE_ADD(finalDate, INTERVAL 2 DAY);
END IF;
return finalDate;
END$$
DELIMITER ;
Basically i'm using the same logic as the accepted answer but acumulating the weekends. So for each weekend i will add 2 days and at the end i will if the result date is on weekend i will add 2 more
WHERE datefield BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL 7 DAY
AND WEEKDAY(datefield) NOT IN (5,6);

Date difference in mysql based on 360 days

I working on a financial project where i need to calculate the difference of arrear days. If I use the method datediff() of mysql then it returns the result based on 365 days. I need the result based on 360 days. if use the following sql query
select datediff('20140908','20130908') from dual;
mysql returns the date difference 365. This is the actual date difference but in accounting/Financial calculation the difference is exactly one year (360 days). This is what I want. The result should be 360 instead 365.
Currently I want to use US standard.
to get the same result like Excel, I found the following code to use in MySQL:
select case
when (day(Startdate)>=30 or Startdate=last_day(Startdate) then
case
when(day(Enddate)>=30) then
30*(12*(year(Enddate)-year(Startdate))+month(Enddate)-month(Startdate))
else
30*(12*(year(Enddate)-year(Startdate))+month(Enddate)-month(Startdate))+days(Enddate)-30
end
else
30*(12*(year(Enddate)-year(Startdate))+month(Enddate)-month(Startdate))+days(Enddate)-days(Startdate)
end
Use
TO_DAYS(date2) - To_DAYS(date1)
It returns the number of days between date1 and date2. Then you can do with the result what you need.
sdate = from date
edate = to date
this calculate the days between the 2 date, taking into account that a month is 30 days, 1 year is 360 days, and checking if the date are at the end of the month, so e.g. from 1.1.2019 to 28.2.2019 = 60 days etc.
to be 1 month or 1 year, the edate should be a day before, so 1.1 - 31.1 = 30 days, 1.1 - 1.2 = 31 days
SELECT GREATEST(
0,
(YEAR(DATE_ADD(edate, INTERVAL 1 DAY)) - YEAR(sdate)) * 360 +
(MONTH(DATE_ADD(edate, INTERVAL 1 DAY)) - MONTH(sdate)) * 30 +
(
IF(DAYOFMONTH(DATE_ADD(edate, INTERVAL 1 DAY)) = DAYOFMONTH(LAST_DAY(DATE_ADD(edate, INTERVAL 1 DAY))), 30, DAYOFMONTH(DATE_ADD(edate, INTERVAL 1 DAY))) -
IF(DAYOFMONTH(sdate) = DAYOFMONTH(LAST_DAY(sdate)), 30, DAYOFMONTH(sdate))
)
Pretty late here, but posting my solution for future reference
CREATE FUNCTION `DAYS360_UDF`(sdate DATE, edate DATE)
RETURNS INTEGER
DETERMINISTIC
CONTAINS SQL
BEGIN
DECLARE sdate_360 INTEGER;
DECLARE edate_360 INTEGER;
SET sdate_360 = ( YEAR(sdate) * 360 ) + ( (MONTH(sdate)-1)*30 ) + DAY(sdate);
SET edate_360 = ( YEAR(edate) * 360 ) + ( (MONTH(edate)-1)*30 ) + DAY(edate);
RETURN edate_360 - sdate_360;
END
Usage -
select DAYS360_UDF('20130908', '20140908')

SQL Server. Stored procedure to get the biweekly periods

I'm currently trying to write a stored procedure that can compute the biweekly periods when a date is passed in as a parameter.
The business logic: the first Monday of the year is first day of the biweekly period.
For example in 2010:
period period_start period_end
1 2010-01-04 2010-01-17
2 2010-01-18 2010-01-31
3 2010-02-01 2010-02-14
....
26 2010-12-20 2011-01-02
Passing today's date of 2010-12-31 will return 26, 2010-12-20 and 2011-01-02. How to achieve this?
#Lamak did the hard part, figuring out the first Monday in the year (upvoted). I revised his routine a bit to take a datetime value, like so:
-- Return first Monday for the year being passed in
CREATE FUNCTION dbo.FirstMonday (#TargetDay datetime)
RETURNS DATE
AS
BEGIN
DECLARE #Return DATE
-- Set to first of its year
SET #TargetDay = dateadd(dd, -datepart(dayofyear, #TargetDay) + 1, #TargetDay)
;WITH Dates AS
(
SELECT #TargetDay AS DateVal
UNION ALL
SELECT DATEADD(d, 1, DateVal) AS DateVal
FROM Dates
WHERE DATEADD(d, 1, DateVal) < DATEADD(m, 1, #TargetDay)
)
SELECT #Return = MIN(DateVal)
FROM Dates
WHERE DATENAME(WEEKDAY,DateVal) = 'Monday'
RETURN #Return
END
GO
-- Test it
print dbo.FirstMonday(getdate())
print dbo.FirstMonday('Jan 1, 2010')
From there, it's just some datetime arithmatic:
DECLARE
#FirstMonday datetime
,#TargetDay datetime
,#BiWeek int
SET #TargetDay = getdate()
--SET #TargetDay = 'Jan 17, 2010'
-- Get the first Monday
SET #FirstMonday = dbo.FirstMonday(#TargetDay)
-- Calculate the bi-weekly period
SET #BiWeek = datediff(dd, #FirstMonday, #TargetDay) / 14
-- Print results
PRINT #BiWeek + 1
PRINT dateadd(dd, #BiWeek * 14, #FirstMonday)
PRINT dateadd(dd, #BiWeek * 14 + 13, #FirstMonday)
-- Or return them as a dataset
SELECT
#BiWeek + 1 Period
,dateadd(dd, #BiWeek * 14, #FirstMonday) PeriodStart
,dateadd(dd, #BiWeek * 14 + 13, #FirstMonday) PeriodEnd
This fails when you pick a day in the year that falls before the first Monday, as it returns the first biweekly period after that date. (Or does it fail? You did not specify what the proper period is for such dates... :)
However, as #HLGEM says, depending on what you are doing you are probably better off building and using some form of biweekly period lookup table, especially if you need proper handling of those early dates.
You can create a function to get the first Monday of a year. Something like this should work:
CREATE FUNCTION dbo.FirstMonday(#Year VARCHAR(4))
RETURNS DATE
AS
BEGIN
DECLARE #Return DATE;
WITH Dates AS
(
SELECT CONVERT(DATE,#Year+'0101') AS DateVal
UNION ALL
SELECT DATEADD(d, 1, DateVal) AS DateVal
FROM Dates
WHERE DATEADD(d, 1, DateVal) < DATEADD(m, 1, #Year+'0101')
)
SELECT #Return = MIN(DateVal)
FROM Dates
WHERE DATENAME(WEEKDAY,DateVal) = 'Monday'
RETURN #Return
END