Calculate date based on pattern from anchor date - sql

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

Related

The last day of the previous month - BigQuery

I'm trying to select rows where a timestamp field, recdate, has a date value up to and inclusive of the last completed day of the month. For example, as this is July 2016, I want all rows with date values up to and inclusive of 31-06-2016. This used to work fine in T-SQL, I'd use the following and assign it to #today and chuck that in my WHERE:
DECLARE #today DATETIME SELECT #today = CONVERT(VARCHAR(25),DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())-0,0)));
I'm struggling in BigQuery though, I can't get DATEDIFF or GETDATE to work, was wondering if anybody had thoughts on this?
best wishes
Dave
Another way with Standard SQL.
First day current month:
SELECT DATE_TRUNC(CURRENT_DATE(), MONTH)
Last day previous month (first day current minus 1):
SELECT DATE_SUB(DATE_TRUNC(CURRENT_DATE(), MONTH), INTERVAL 1 DAY)
First day next month:
SELECT DATE_TRUNC(DATE_ADD(CURRENT_DATE(), INTERVAL 1 MONTH), MONTH)
Last day current month (first day next month minus 1):
SELECT DATE_SUB(DATE_TRUNC(DATE_ADD(CURRENT_DATE(), INTERVAL 1 MONTH), MONTH), INTERVAL 1 DAY)
October 2020 Update (BigQuery Standard SQL)
BigQuery now support LAST_DAY function as well as arithmetic operations + and '-' for dates
So, now you can use below options to get last day of previous month
#standardSQL
select
date_trunc(current_date(), month) - 1,
last_day(date_sub(current_date(), interval 1 month)),
date_sub(last_day(current_date()), interval 1 month)
with output (having in mind it is October 14 today)
me personally - i love the first option as least verbose and straightforward!
-- ~~~~~~~~~~~~~~~~~~~
Use below as an example (BigQuery Legacy SQL)
SELECT DATE(DATE_ADD(CURRENT_DATE() , -DAY(CURRENT_DATE()), "DAY"))
BTW, there are 30 days in June :o) - with exception of Priestley's "The Thirty-First of June"
Works in standard SQL
SELECT DATE_ADD(CURRENT_DATE(), INTERVAL -EXTRACT(DAY FROM CURRENT_DATE()) DAY)
DATE_ADD( CURRENT_DATE(), INTERVAL -EXTRACT( DAY FROM CURRENT_DATE()) DAY)
That is the correct syntax for Google BigQuery.
Begining SQL 2012 there is a EOMONTH Function :
https://msdn.microsoft.com/en-us/library/hh213020.aspx
Usage:
DECLARE #date VARCHAR(255) = '07/01/2016';
SELECT EOMONTH ( #date ) AS Result;
GO
Will return 07/31/2016
DECLARE #date VARCHAR(255) = GetDate() ; -- To Get End of month for Current Month
SELECT EOMONTH ( #date ) AS Result;
GO
Will return 07/31/2016
My Bad .. I did not realize the OP was looking for last day of previous month
But this should work:
DECLARE #date Datetime = '12/31/2017' -- Input Date. Has to be Datetime NOT varchar. If incomming date is a varchar it needs to be converted to Datetime.
Select DAY( #date ) -- Returns 31
SELECT #date - DAY( #date ) as LastDayOfPrevMonth
Output:
LastDayOfPrevMonth
2017-11-30 00:00:00.000
I have tried it for most of the Edge cases like leap day , first/last day of any month etc.
Works with any version of SQL Server
SELECT convert(varchar(10),
DATEADD(mm, -1,
DATEADD(s,-1,
DATEADD(mm,
DATEDIFF(m,0,getdate())+1
,0)
)
), 103) AS [LastMonthEnd]

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

Number of days in quarter, SQL Server

I want to calculate the number of days per-quarter if start date and finish dates are given.
for example, one table has two columns, start date and finish date.
start date = 1st september and finish is 14th november.
I want to calculate the number of days present in between these two days that are present in each quarter
Q3 - 30 days
Q4 - 45 days (for this scenario)
Regards.
declare #StartDate date='2012-09-01';
declare #EndDate date='2012-11-14';
select CEILING(month(dateadd(q,datediff(q,0,dateadd(dd,number ,#StartDate)),0))/3.0) as QuarterNo,
COUNT(*) as 'number of days'
from master..spt_values
where type='p'
and dateadd(dd,number ,#StartDate)<=#EndDate
group by dateadd(q,datediff(q,0,dateadd(dd,number ,#StartDate)),0)
SQL fiddle demo
You can use a recursive query to get this. This generates the list of dates between your start and end date and then gets the count of days per quarter:
;with cte (start, enddate) as
(
select startdate, enddate
from yourtable
union all
select dateadd(dd, 1, start), enddate
from cte
where dateadd(dd, 1, start) <= enddate
)
select datepart(q, start) Quarter, count(datepart(q, start)) NoDays
from cte
group by datepart(q, start)
See SQL Fiddle with Demo

SQL Server 2008 - date functions and formulas

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.

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
*/