SQL Server 2008 - date functions and formulas - sql

I want to return results where if the date falls on 10 & 28 of each month, but if either is a weekend return the result for Friday (first working day before).
Eg. if the following lines to be returned are
10 Oct 2010 Sunday
28 Oct 2010 Thursday
In the table I have
LineId Date
1 08 Oct 2010
2 28 Oct 2010
so, because 10 October is a Sunday, therefore won't be in the table, it will return LineID 1 as the is first working day before.
Thank you.

DATEPART(WEEKDAY and DATEPART(DW are dependant on the DATEFIRST setting. To avoid incorrect results the ##DATEFIRST function can be used.
WITH T(D) AS
(
SELECT CAST('20111008' AS DATE) UNION ALL
SELECT CAST('20111009' AS DATE) UNION ALL
SELECT CAST('20111010' AS DATE) UNION ALL
SELECT CAST('20111011' AS DATE) UNION ALL
SELECT CAST('20111012' AS DATE) UNION ALL
SELECT CAST('20111013' AS DATE) UNION ALL
SELECT CAST('20111014' AS DATE)
)
SELECT CASE
WHEN ( ##DATEFIRST + DATEPART(WEEKDAY, D) ) % 7 > 1 THEN D
ELSE DATEADD(DAY, -( 1 + ( ##DATEFIRST + DATEPART(WEEKDAY, D) ) % 7 ), D)
END AS WeekDayOrPrecedingFriday
FROM T

Select Case
When DatePart(dw,SampleData.[Date]) = 1 Then DateAdd(d,-2,SampleData.[Date])
When DatePart(dw,SampleData.[Date]) = 7 Then DateAdd(d,-1,SampleData.[Date])
Else SampleData.[Date]
End
From (
Select Cast('2010-10-10' As datetime) As [Date]
Union All Select '2010-10-28' As [Date]
) As SampleData
You may find that it is easier to have a Calendar table with one row for all days you need where you indicate whether the given day is a "working" day. In this way, you can easily account for holidays and the actual day off for holidays (e.g. if July 4th, in the US, is on a Saturday, mark the preceding Friday as a day off.).
If you are really worried about dealing with DateFirst, just set it prior to running your query:
Set DateFirst 7;
The above is the US default setting which is that Sunday is the first day of the week.

I really like to use a calendar table for queries like these.
-- For convenience, I'll use a view. The view "weekdays" is a proper
-- subset of the table "calendar".
create view weekdays as
select * from calendar
where day_of_week in ('Mon', 'Tue', 'Wed', 'Thu', 'Fri');
Having done that, the query is not only dead simple, it can easily be seen to be right.
select max(cal_date)
from weekdays
where cal_date <= '2010-10-10' -- Returns 2011-10-08
Doesn't account for holidays that might fall on the 10th or 28th, but that's easy enough to remedy.

Related

Shipping dates job

I've a job that a need to work with dates, like this:
A ship leaves port of load only from Monday to Saturday, how can I show those dates in a field in sql?
I tried to get weekday and getdate() but I've no sucessfull
CASE
WHEN PORT_ID = 333
THEN CONVERT(VARCHAR(15),DATEADD(D, preview_Date)),103) END AS 'date of load'
but I need the date to always be from Monday to Saturday according to the calendar.
Image Example
CASE
WHEN DATEPART(DW, CONVERT(DATE,departure_Date))) IN (2,3,4,5) AND harbor_id = 412
THEN CONVERT(VARCHAR(15),DATEADD(DAY, -8, CONVERT(DATE,departure_Date)))+ ' TO ' + CONVERT(VARCHAR(15),DATEADD(DAY,-4, CONVERT(DATE,departure_Date)))
END AS 'DEADLINE' ,
Your question is not completely clear to me so I'll make some assumptions. If they are incorrect tell me in the comments, so I can change the answer.
You have a list of departure dates for ships
Customers have 4 workdays (5 normal days if a Sunday is in between) prior to the departure date to deliver goods
Goods can not be delivered on Sunday, ships do not depart on Sundays
example 1: Ship departure = 15/12/2018 (Saturday), date_of_load = 11/12/2018 (Tuesday)
example 2: Ship departure = 17/12/2018 (Monday), date_of_load = 12/12/2018 (Wednesday)
You could solve this with the query below.
It works like this: First set DATEFIRST to 1. SQL uses datefirst to determine how to number the weekdays. Now it numbers Monday as day 1.
Next use the DATEPART function to determine what day of the week the departure date is.
If departure date is a Monday,Tuesday,Wednesday,Thursday (day numbers 2,3,4,5) we subtract 5 days to get the date_of_load, to account for the Sunday. Else we subtract 4.
SET DATEFIRST 1 -- Set monday as day 1
SELECT Ship_Departure_Date,
CASE WHEN DATEPART(dw,Ship_Departure_Date) IN (1,2,3,4) --dw means 'day of the week'
THEN DATEADD(DAY, -5, Ship_Departure_Date)
ELSE DATEADD(DAY, -4, Ship_Departure_Date)
END AS date_of_load
FROM ShipDepartureList
Even if this is not exactly the solution to your problem, I hope this guides you in the right direction.
The with block just generates example data
WITH (
select GetUtcDate() as shipsaildate
Union all
select GetUtcDate() +1
Union all
select GetUtcDate() +2
Union all
select GetUtcDate() +3
Union all
select GetUtcDate() +4
Union all
select GetUtcDate() +5
Union all
select GetUtcDate() +6
Union all
select GetUtcDate() +7
) as departs
Select
shipsaildate,
shipsaildate -
case
when datepart(dw, shipsaildate) >= 6 then datepart(dw, shipsaildate) - 2
else 6
end as deadline
From departs
If the ship departs on mon tue wed or thu then the deadline is 6 days (4 working days) prior. If the ship departs on fri sat or sun, the deadline is 4, 5, or 6 days prior accordingly,
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=0801ac47b0fcd11dfa9c2ee54001fa6a

SQL Server : is there a way for me to get weeks per month using Month and Year?

I'm not really good when it comes to database...
I'm wondering if it is possible to get the weeks of a certain month and year..
For example: 1 (January) = month and 2016 = year
Desired result will be:
week 1
week 2
week 3
week 4
week 5
This is what I have tried so far...
declare #date datetime = '01/01/2016'
select datepart(day, datediff(day, 0, #date) / 7 * 7) / 7 + 1
This only returns the total of the weeks which is 5.
declare #MonthStart datetime
-- Find first day of current month
set #MonthStart = dateadd(mm,datediff(mm,0,getdate()),0)
select
Week,
WeekStart = dateadd(dd,(Week-1)*7,#MonthStart)
from
( -- Week numbers
select Week = 1 union all select 2 union all
select 3 union all select 4 union all select 5
) a
where
-- Necessary to limit to 4 weeks for Feb in non-leap year
datepart(mm,dateadd(dd,(Week-1)*7,#MonthStart)) =
datepart(mm,#MonthStart)
Got the answer in the link: http://www.sqlservercentral.com/Forums/Topic1328013-391-1.aspx
Here is one way to approach this:
A month has a minimum of 29 or more days, and a max of 31 or less. Meaning there are almost always 5 weeks a month, with the exception of a non-leap year's feburary, and in those cases, 4 weeks a month.
You can refer to this to find out which years are "leap".
Check for leap year
Hope this helps!
The following code will allow you to select a start and end date, and output one row per week, with numbered weeks, between those dates:
declare #start date = '1/1/2016'
declare #end date = '5/1/2016'
;with cte as (select #start date
, datename(month, #start) as month
union all
select dateadd(dd, 7, date)
, datename(month, dateadd(dd, 7, date))
from CTE
where date <= #end
)
select *, 'week '
+ cast(row_number() over (partition by month order by date) as varchar(1))
from CTE
order by date

Show number of weeks in a given month

So, I've been stuck on this problem for last couple of days and I still couldn't come up with solution.
I want to group given month into weeks which is fairly easy but the (horrible)business requirement is to consider a single day also as a week if it
falls on any day between Monday to Sunday. The end day of the week is going to be Sunday.
For example I'll take month of August for demonstration. According to business requirement, this is how the data should be displayed for the given month
First week - August 1st to August 2nd, 2015
Second week - August 3nd to August 9th, 2015
Third week - August 10th to August 16th, 2015
Fourth week - August 17th to August 23rd, 2015
Fifth week - August 24th to August 30th, 2015
Sixth week - August 31st, 2015
I'm completely clueless on how to proceed with the problem due to the sixth week occurrence.
I came across this query on AskTom which display 5 weeks but resets back to 1 on the 31st of August. Moreover, the query doesn't look like an elegant solution.
select dt, to_char( dt+1, 'w' )
from ( select to_date('1-aug-2015')+rownum dt
from all_objects
where rownum < 31 );
Looking for suggestions/insights on the problem.
Thanks
WITH x (dt)
AS ( SELECT DATE '2015-08-01' + LEVEL - 1 dt
FROM DUAL
CONNECT BY DATE '2015-08-01' + LEVEL - 1 < DATE '2015-09-01')
SELECT dt,
SUM (
CASE
WHEN TO_CHAR (dt, 'd') = '2' --if the day is monday
OR TO_CHAR (dt, 'fmdd') = '1' --or if its the first day of the month, assign 1.
THEN
1
ELSE
0
END)
OVER (ORDER BY dt)
wk_nr
FROM x;
First generate all days for the given month.
Identify the beginning of each week and the start of the month by marking it as 1. Mark rest of the days as 0. Here to_char(dt,'d') gives 2 for monday. But may change based on NLS territory of the session.
Now that you have beginning of each week, use SUM to calculate the cumulative sum. This gives you the desired week number.
Sample fiddle.
UPDATE
Looks like 10g doesn't support column alias with the CTE name. Remove it and try.
WITH x
AS (SELECT ....
--TRY THIS
--I DID IT FOR SYSDATE.
SELECT DT,
CASE
WHEN TO_CHAR(DT+1, 'W')='1'
AND SUBSTR(DT,1,2)>'24' THEN '6'
ELSE TO_CHAR(DT+1, 'W')
END
FROM
(SELECT TO_DATE(SYSDATE)+ROWNUM DT FROM ALL_OBJECTS
);
--THE QUERY IN YOUR EXAMPLE.
SELECT DT,CASE WHEN TO_CHAR( DT+1, 'w' )='1' AND SUBSTR( DT,1,2)>'24' THEN '6' ELSE TO_CHAR( DT+1, 'w' ) END
from ( select to_date('1-aug-2015')+rownum dt
FROM ALL_OBJECTS
WHERE ROWNUM < 31
);
A table variable works really well:
declare #calendar table (WkDay date, DayOfWk int, YR INT, MO INT)
DECLARE #BEG_DT DATE
SET #BEG_DT='2016-12-01'
WHILE #BEG_DT <='2250-01-01'
BEGIN
INSERT INTO #calendar VALUES (#BEG_DT, DATEPART(WEEKDAY,#BEG_DT), DATEPART(YEAR,#BEG_DT), DATEPART(MONTH,#BEG_DT))
SET #BEG_DT=DATEADD(DAY,1,#BEG_DT)
END
SELECT *
FROM #calendar
then count the number of "1" dayofwk and you get the number of weeks in a given month

Calculate date based on pattern from anchor date

I have a rolling 4 week (28 day) date pattern starting from an anchor date in the past. I need to know what the nearest previous pattern start date is relative to the current date.
For example:
Anchor date = Monday June 30 2013. If today's date is August 7 2013 then the date I would like returned is July 29 2013. Previous pattern start dates were June 30, July 29. The next pattern start date is August 26 but that is after today's date of August 7 2013.
Hope that makes sense and thanks
While I'm not 100% sure I understand where July 29th comes from, if you want to retrieve the closest date 28 days from an anchor date, one way would be to use a recursive CTE (although it could have a performance implication).
declare #anchor datetime
set #anchor = '6/30/2013'
;with cte as (
select #anchor dt
union all
select dateadd(day, 28, dt) dt
from cte
where dt <= dateadd(day, -28, '8/7/2013')
)
select max(dt) from cte
SQL Fiddle Demo
Perhaps an even simpler solution would be to use datediff as well:
declare #anchor datetime
set #anchor = '6/30/2013'
select dateadd(day, 28 * (datediff(day, #anchor, getDate())/28), #anchor)
More Fiddle
I think your calculation is a little off. 28 days after 30/Jun in 28/Jul then 25/Aug.
Anyways, here is my query which will return 28/Jul, which I believe to be the correct answer.
WITH my_date AS
(SELECT CAST('30/Jun/2013' AS DATETIME)AS the_date
UNION ALL
SELECT dateadd(day,28,the_date)AS the_date
FROM my_date
WHERE my_date.the_date <= dateadd(day,-28,getdate())
)
SELECT max(the_date)
FROM my_date
http://sqlfiddle.com/#!6/d41d8/6267

How to get Monday, Sunday based on a Week Number in SQL Server?

I have a week number (e.g. 23) and I'd like to get the dates for Monday and Sunday of that week.
I am assuming Monday as the first day of the week (e.g. I have SET DATEFIRST 1 in my script).
DECLARE #startweek1 datetime
SET DATEFIRST 1
--first monday of year
SELECT #startweek1 = DATEADD(day, 8-DATEPART(weekday, '2011-01-01'), '2011-01-01')
--day based
SELECT
DATEADD(day, 22 * 7, #startweek1) AS MondayWeek23,
DATEADD(day, 23 * 7 -1 , #startweek1) AS SundayWeek23
--week based
SELECT
DATEADD(week, 22, #startweek1) AS MondayWeek23,
DATEADD(day, -1, DATEADD(week, 23 , #startweek1)) AS SundayWeek23
Edit:
This solution works if week 1 does not start on day 1 as Andomar said
Edit 2:
According to Wikipedia: 2008-12-29 is day 1 of week 1 of 2009 in ISO.
And week numbers vary as Andomar said
Cyberkiwi mentioned this code is wrong for 2007: it's wrong far more often than that. The same applies to his code too which matches 2007 but is equally wrong for the rest.
Declare #StartDate datetime;
Set #StartDate = '20110101';
With StartOfWeek As
(
Select DateAdd(
week
, 23
, DateAdd(
d
, -(DatePart(dw, #StartDate) - 1)
, #StartDate
) ) As Sunday
)
Select Sunday, DateAdd(d,1,Sunday) As Monday
From StartOfWeek
We can use the DATEFIRST variable to set the start day of the week. By default, the week starts on Sunday(7). In order to start with Monday, we will set its value to 1.
# Printing default value
Select ##DATEFIRST; -- This will give 7 value i.e. default value
# Changing first day of the week to Monday.
Declare #CurrentWeekNumber int
SET DATEFIRST 1
set #CurrentWeekNumber= (SELECT DATEPART(WEEK, GETDATE()))
select #CurrentWeekNumber
PS: Changes to DATEFIRST are valid for current session i.e. it doesn't impact other DB queries which might rely on different week start day.
A calendar table makes this kind of date query pretty simple. A calendar table just has the relevant calendar details calculated and stored at load time instead of calculated at run time. Depending on the application, that can speed things up a lot. This way is dbms agnostic; it just uses literals in the WHERE clause.
select cal_date from calendar
where iso_year = 2011
and iso_week = 23
and cal_dow in ('Mon', 'Sun');
And another way that relies only on having a table of dates.
select cal_date from calendar
where extract(isoyear from cal_date) = 2011
and extract(week from cal_date) = 23
and (extract(isodow from cal_date) = 1 or
extract(isodow from cal_date) = 7);
EXTRACT() is standard SQL, but I'm not sure whether the names of the subfields isoyear, week, isodow are standard SQL.
Note: Any answer that starts with assuming year is 2011 might as well take a static first date, i.e. by replacing #firstMon in my last query below with just the static date '20101227'.
SET DATEFIRST 1
declare #targetYear int
declare #targetWeek int
select #targetYear = 2011, #targetWeek = 23
declare #firstMon datetime
set #firstMon = dateadd(d,1-datepart(dw, right(#targetYear,4)+'0101'),right(#targetYear,4)+'0101')
select
MonOfTargetWeek = dateadd(week, #targetWeek-1, #firstMon),
SunOfTargetWeek = dateadd(week, #targetWeek-1, #firstMon+6)
And the real, final query - which returns a Mon/Sun of NULL when it doesn't exist in that week, such as Mon of partial-week-one or Sun of partial-week-53
;with tmp(Mon, Sun) as (
select
MonOfTargetWeek = dateadd(week, #targetWeek-1, #firstMon),
SunOfTargetWeek = dateadd(week, #targetWeek-1, #firstMon+6)
)
select
RealMonday = case when Year(Mon)=#targetYear then Mon end,
RealSunday = case when Year(Sun)=#targetYear then Sun end
from tmp
For GBN's claim that this answer is wrong (within his answer), I submit below the proof of correctness
I have turned the code into a function.
CREATE function dbo.getMonSunForWeek(#targetYear int, #targetWeek int)
returns table as return
with pre(firstMon) as (
select dateadd(d,1-datepart(dw, right(#targetYear,4)+'0101'),right(#targetYear,4)+'0101'))
, tmp(Mon, Sun) as (
select
dateadd(week, #targetWeek-1, firstMon),
dateadd(week, #targetWeek-1, firstMon+6)
from pre
)
select
Mon, Sun,
RealMonday = case when Year(Mon)=#targetYear then Mon end,
RealSunday = case when Year(Sun)=#targetYear then Sun end
from tmp
GO
And below I present the ENTIRE range of years and weeks (1-53) for the years from 1950 through 2047. In EVERY SINGLE case, when the Monday/Sunday has been determined, and working backwards using DATEPART(week), SQL Server agrees with the week numbering from the function.
set datefirst 1;
select
[Year] = years.number,
[Week] = weeks.number,
[Mon] = fun.realmonday,
[Sun] = fun.realsunday,
[WeekX] = isnull(datepart(week, fun.realmonday), datepart(week, fun.realsunday)),
[MonX] = fun.Mon,
[SunX] = fun.Sun
from master..spt_values years
inner join master..spt_values weeks on weeks.type='P' and weeks.number between 1 and 53
cross apply dbo.getMonSunForWeek(years.number, weeks.number) fun
where years.type='P' and years.number between 1950 and 2047
order by [year], [Week]
Output around the 2005-2006 changeover
when including prior/next year
Year Week Mon-of-week Sun-of-week datepart(week) Mon -and- Sun
2005 52 2005-12-19 2005-12-25 52 2005-12-19 2005-12-25
2005 53 2005-12-26 NULL 53 2005-12-26 2006-01-01
2006 1 NULL 2006-01-01 1 2005-12-26 2006-01-01
2006 2 2006-01-02 2006-01-08 2 2006-01-02 2006-01-08
You might try some dateadd logic...
dateadd(week, 23, '2011-01-01')
dateadd(day, 7, dateadd(week, 23, '2011-01-01'))
UPDATE:
select dateadd(day, 23, dateadd(week, 23, '2011-01-01')) , Datename(weekday,dateadd(day, 23, dateadd(week, 23, '2011-01-01')));
// RETURNS 7/4/2011, Monday
select Datename(weekday,dateadd(day, 23, dateadd(week, 23, '2011-01-01')));
// RETURNS Monday
EXAMPLE FROM: http://msdn.microsoft.com/en-us/library/ms186819.aspx
DECLARE #datetime2 datetime2 = '2007-01-01 13:10:10.1111111'
SELECT 'year', DATEADD(year,1,#datetime2)
UNION ALL
SELECT 'quarter',DATEADD(quarter,1,#datetime2)
UNION ALL
SELECT 'month',DATEADD(month,1,#datetime2)
UNION ALL
SELECT 'dayofyear',DATEADD(dayofyear,1,#datetime2)
UNION ALL
SELECT 'day',DATEADD(day,1,#datetime2)
UNION ALL
SELECT 'week',DATEADD(week,1,#datetime2)
UNION ALL
SELECT 'weekday',DATEADD(weekday,1,#datetime2)
UNION ALL
SELECT 'hour',DATEADD(hour,1,#datetime2)
UNION ALL
SELECT 'minute',DATEADD(minute,1,#datetime2)
UNION ALL
SELECT 'second',DATEADD(second,1,#datetime2)
UNION ALL
SELECT 'millisecond',DATEADD(millisecond,1,#datetime2)
UNION ALL
SELECT 'microsecond',DATEADD(microsecond,1,#datetime2)
UNION ALL
SELECT 'nanosecond',DATEADD(nanosecond,1,#datetime2);
/*
Year 2008-01-01 13:10:10.1111111
quarter 2007-04-01 13:10:10.1111111
month 2007-02-01 13:10:10.1111111
dayofyear 2007-01-02 13:10:10.1111111
day 2007-01-02 13:10:10.1111111
week 2007-01-08 13:10:10.1111111
weekday 2007-01-02 13:10:10.1111111
hour 2007-01-01 14:10:10.1111111
minute 2007-01-01 13:11:10.1111111
second 2007-01-01 13:10:11.1111111
millisecond 2007-01-01 13:10:10.1121111
microsecond 2007-01-01 13:10:10.1111121
nanosecond 2007-01-01 13:10:10.1111111
*/