Unexpected behaviour of DATEPART - sql

Script that lists dates and week number of date:
DECLARE #initDate DATE = '2014-01-01';
DECLARE #endDate DATE = '2014-12-31';
WITH dates (date, week)
AS (SELECT #initDate date,
Datepart(ww, #initDate) week
UNION ALL
SELECT Dateadd(ww, 1, t.date),
Datepart(ww, t.date)
FROM dates t
WHERE t.date < #endDate)
SELECT dates.date,
dates.week
FROM dates
The first three rows are:
date: week number
---------- -----------
2014-01-01 1
2014-01-08 1
2014-01-15 2
..... ..
..... ..
I guess it should be 2 for the second row and 3 for the first, isn't it?
Is it some kind of bug in DATEPART? Even if something is depends on first day of the year, first row date differs from second on one week with any settings.
Could you clarify this, please?

It is because of Recursive CTE not because of Datepart.
In the Recursive part of CTE week is still holding the previous week not the new date that generated in recursive part
Try changing your query like this.
DECLARE #initDate DATE = '2014-01-01';
DECLARE #endDate DATE = '2014-12-31';
WITH dates (date, week)
AS (SELECT #initDate date,
Datepart(ww, #initDate) week
UNION ALL
SELECT Dateadd(ww, 1, t.date),
Datepart(ww, Dateadd(ww, 1, t.date))
FROM dates t
WHERE t.date < #endDate)
SELECT dates.date,
dates.week
FROM dates

Related

How to return values of two date ranges, from the same date column and value column, in two different columns as a result?

I have following data in my table,
Table = BillHeader
Sales column = Sales
Date column = CreateDate
Location name = Location
Result needed:
Location
Sum_of_Sale_1
Sum_of_Sale_2
Sum_of_Sale_1 = Sum of Sales up to yesterday for this month.
Sum_of_Sale_2 = Sum of Sales up to same date range as Sum_of_Sale_1 during last month.
For example, if today is 20th of June, Sum_of_Sale_1 = Sum of sales from 1st June to 19th of June
and Sum_of_Sale_2 = Sum of sales from 1st May to 19th of May.
Basically what I need is these two results of different date ranges, which should be selected form the same three columns, should appear next to each other in the result. I want to know how the sales performance was last month's same date range as to this month's date range (up to yesterday for this month).
Thanks!!
EDIT - 1
Here is the actual current working code:
SET #FDM = DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0)
SELECT sum ([LAB_TRN_BillHeader].[AmountToBePaid]) as Total_Sale
,LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode]
,LAB.dbo.[LAB_Comm_MST_CollectingCenter].[Name]
,LAB.dbo.[LAB_Comm_MST_Branch].[BranchName]
FROM Lab.dbo.[LAB_TRN_BillHeader]
INNER JOIN LAB.dbo.[LAB_Comm_MST_CollectingCenter] on LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode] = LAB.dbo.[LAB_Comm_MST_CollectingCenter].[CollectingCenterCode]
INNER JOIN LAB.dbo.[LAB_Comm_MST_Branch] on LAB.dbo.[LAB_TRN_BillHeader].[BranchCode] = LAB.dbo.[LAB_Comm_MST_Branch].[BranchCode]
WHERE Date between #FDM and DATEADD(day,0, CAST(GETDATE() AS date)) and {{select_Laboratory}} and LAB.dbo.[LAB_TRN_BillHeader].[IsVoid] = '0' and LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode] in ('URCR022','MRPMC','KUCC','KOCC','EHECC')
GROUP BY LAB.dbo.[LAB_TRN_BillHeader].[CollectingCenterCode], LAB.dbo.[LAB_Comm_MST_CollectingCenter].[Name], LAB.dbo.[LAB_Comm_MST_Branch].[BranchName]
Current Result:
|Total_Sale|CollectingCenterCode|Name|BranchName|
|xxx |xxx |x |xx |
Required Result:
|Total_Sale|Total_Sale2|CollectingCenterCode|Name|BranchName|
|xxx |xxx |xx |x |xx |
Total_Sale = Sale of current month up to yesterday
Total_Sale2 = Sale of Last month up to current month's yesterday's date.
-- MSSQL Version - 2014
-- <Create_Date> is a time stamp in the table in <Create_Date> column. The date/time is obtained from that timestamp. Each transaction is saved with a respective timestamp at it's time of occurrence.
-- {{select_Laboratory}} is a field filter alias in Metabase (this code was copied from a Metabase dashboard). The actual code is LAB.dbo.[LAB_TRN_BillHeader].[BranchCode] = '001'
Considering a sales CreateDate is likely of type Datetime or Datetime2, a safe approach would be:
DECLARE #yesterday DATE = GETDATE();
DECLARE #lastMonth DATE = DATEADD(MONTH, -1, #yesterday);
DECLARE #firstDayOfThisMonth DATE = DATEADD(DAY, 1 - DAY(#yesterday), #yesterday);
DECLARE #firstDayOfLastMonth DATE = DATEADD(DAY, 1 - DAY(#lastMonth), #lastMonth);
SELECT #yesterday,
#firstDayOfThisMonth,
#lastMonth,
#firstDayOfLastMonth;
SELECT [locationId],
SUM( CASE
WHEN CreateDate >= #firstDayOfThisMonth
AND CreateDate < #yesterday THEN
AmountToBePaid
END
) AS Sum_of_Sale_1,
SUM( CASE
WHEN CreateDate >= #firstDayOfLastMonth
AND CreateDate < #lastMonth THEN
AmountToBePaid
END
) AS Sum_of_Sale_2
FROM BillHeader
GROUP BY [locationId];
EDIT: Note that in dates like March 31,30 previous month's end date could be Feb 28, 29.
You could use conditional aggregation with the following date functions:
DATEADD(Day, 1, EOMONTH(GETDATE(), -1)) gets the first date of the current month, i.e. current month is Jan-2023 it will return '2023-01-01'.
CAST(GETDATE() AS DATE) gets today's date.
DATEADD(Day, 1, EOMONTH(GETDATE(), -2)) gets the first date of the previous month, i.e. current month is Jan-2023 it will return '2022-12-01'.
DATEADD(Month, -1, CAST(GETDATE() AS DATE) gets the date of the day one-month pre today's date.
SELECT Location,
SUM(CASE
WHEN CreateDate >= DATEADD(Day, 1, EOMONTH(GETDATE(), -1)) AND
CreateDate < CAST(GETDATE() AS DATE)
THEN Sales END) Sum_of_Sale_1,
SUM(CASE
WHEN CreateDate >= DATEADD(Day, 1 ,EOMONTH(GETDATE(), -2)) AND
CreateDate < DATEADD(Month, -1, CAST(GETDATE() AS DATE))
THEN Sales END) Sum_of_Sale_2
FROM BillHeader
GROUP BY Location
See demo
There are different ways of doing the calculations and I don't know if this logic will seem more straightforward or as concise. Also you might not be able to use variables. Lastly, you didn't specify which version you're running.
Although you can incorporate this into an existing query that covers a wider range of dates, here is a stand-alone option that should restrict the execution to a narrower range of dates. The case logic, as written, does assume that rows have already been filtered so the only thing left to determine is whether the sale comes from current month or prior month.
select Location,
sum(case when month(CreateDate) <> month(getdate()) then Sales end) as Sales1,
sum(case when month(CreateDate) = month(getdate()) then Sales end) as Sales2
from BillHeader
where
-- go back enough days to guarantee covering last two months
-- this may be able to utilize an index
SalesDate between dateadd(day, -30 - day(getdate()), cast(getdate() as date))
and getdate()
-- now eliminate extra dates that are not relevant
and month(getdate()) - month(CreateDate) in (0, 1, -11) /* year might roll over */
and day(getdate()) > day(CreateDate)
group by Location;
For a YOY comparison over the same calendar month:
select Location,
sum(case when year(CreateDate) <> year(getdate()) then Sales end) as Sales1,
sum(case when year(CreateDate) = year(getdate()) then Sales end) as Sales2
from BillHeader
where
-- go back enough days to guarantee covering last 13 months
-- (or rewind as 396 days via parallel logic from earlier)
SalesDate between
dateadd(year, -1, dateadd(day, 1 - day(getdate()), cast(getdate() as date)))
and getdate()
and month(getdate()) = month(CreateDate)
and day(getdate()) > day(CreateDate)
group by Location;
This might be better done as a union with two different date ranges combined together:
-- ...
where
SalesDate between
dateadd(year, -1, dateadd(day, 1 - day(getdate()), cast(getdate() as date)))
and dateadd(year, -1, getdate())
-- ... union all ...
where
SalesDate between
dateadd(day, 1 - day(getdate()), cast(getdate() as date))
and getdate()
-- ...

How to select data's from this today,week and month sperately?

I have a problem that is I am unable to resolve as of now.
I need to get the data of
this day, this week and this month
I have a table reminder where I want to select reminders according to
following parameters.
1. Today
2. This Week
3. This Month
The column rdate having the date format in dd-mm-yyyy which is stored as nvarchar
For example
If I execute this weeks query I should get data starting from this week i.e.
If it is Friday I should get data from starting from Sunday to Saturday of that week
How can I get the data as mentioned above. I have searched a lot on internet but I didn't get the solution?
This is the query I have been trying
SELECT
*
FROM
reminder
WHERE
date > DATE_SUB(GETDATE(), INTERVAL 1 DAY)
ORDER BY
rdate DESC;
Where I'm converting nvarchar to date format.
If it's not possible to change the [date] column's data type to DATE, then you will incur a massive performance penalty when trying to filter by date.
Add computed column to table
We can add a computed column that will store the date in the correct format, and then index it for quick searchiing:
ALTER TABLE reminder
ADD Date_Value AS (CONVERT(DATE, '12-05-2016', 105)) PERSISTED;
-- This should yield superior performance
CREATE NONCLUSTERED INDEX IX_Date_Value ON reminder (Date_Value);
Table-valued function to calculate date range
Now, let's create an inline table-valued function to generate the date range for specific period types:
CREATE FUNCTION [dbo].[tvfn_Get_Date_Range](
#Period_Type VARCHAR(100)
)
RETURNS
TABLE
AS RETURN
(
WITH date_range AS(
SELECT CAST(GETDATE() AS DATE) d
-- This line works correctly if your week starts on Sunday
,CAST(DATEADD(WEEK, DATEDIFF(WEEK, '19050101', GETDATE()), '19050101') AS DATE) AS week_start
,CAST(DATEADD(DAY, - DAY(GETDATE()) + 1, GETDATE()) AS DATE) AS month_start
,CAST(DATEADD(MONTH, 1, DATEADD(DAY, - DAY(GETDATE()), GETDATE())) AS DATE) AS month_end
)
SELECT d AS From_Date
,d AS To_Date
FROM date_range
WHERE #Period_Type = 'DAY'
UNION ALL
SELECT week_start
,DATEADD(DAY, 7, week_start)
FROM date_range
WHERE #Period_Type = 'WEEK'
UNION ALL
SELECT month_start
,month_end
FROM date_range
WHERE #Period_Type = 'MONTH'
)
In the above function, week starts on Sunday. If you need this to be configurable, then take a look at the answer to SET DATEFIRST in FUNCTION.
Fast, simple querying now possible
You can now use the two together using a simple query:
SET #Range VARCHAR(100) = 'WEEK'
SELECT *
FROM reminder
CROSS APPLY [dbo].[tvfn_Get_Date_Range](#Range) dr
WHERE Date_Value BETWEEN dr.Date_From AND dr.Date_To
If you can't change the columns data type to Date (or DateTime), you must convert it to date in the query.
Here is one way to get the data for today, this week and this month:
Get records from today:
SELECT *
FROM reminder
WHERE CONVERT(Date, [date], 105) = CAST(GETDATE() as date)
ORDER BY rdate DESC;
Get records from this week:
SELECT *
FROM reminder
WHERE DATEPART(WEEK, CONVERT(Date, [date], 105)) = DATEPART(WEEK, GETDATE())
AND DATEPART(YEAR, CONVERT(Date, [date], 105)) = DATEPART(YEAR, GETDATE())
ORDER BY rdate DESC;
Get records from this Month:
SELECT *
FROM reminder
WHERE DATEPART(MONTH, CONVERT(Date, [date], 105)) = DATEPART(MONTH, GETDATE())
AND DATEPART(YEAR, CONVERT(Date, [date], 105)) = DATEPART(YEAR, GETDATE())
ORDER BY rdate DESC;
To my knowledge, SQL server internally deals with date format as MM/dd/yyyy.
Usually I prefer to save date as string in SQL table since it's easier for inserting and retrieving.
For example, suppose that the column rdate is defined as follows in your table reminder:
[rdate] nvarchar NULL
Then you can customize the select statement for a week as follows:
"Select R.* From reminder R Where CAST(R.rdate as datetime) between
'03/04/2011' AND '03/11/2011'"
And for 10 days as follows:
"Select R.* From reminder R Where CAST(R.rdate as datetime) between
'03/04/2011' AND '03/14/2011'"
And so on. If this is not what you want, please provide more details about your requirements.

Last Wednesday of every month

I need to work out the last Wednesday for each month in a range of dates.
I have the code for calculating the last Wednesday, but my cte doesn't seem to step through the months correctly.
Current code:
declare #S_Date date
declare #E_Date date
set #S_Date='2016-01-31' --eomonth of first month
set #E_Date='2016-06-15' --ides of last month ( can actually be any day that is
--not equal to or after the last Wednesday of the month)
;with LW(D_Date) as
(
select dateadd(dd,datediff(dd,0,#S_Date)/7*7+2,0)
union all
select dateadd(dd,datediff(dd,0,eomonth(D_Date,1))/7*7+2,0)
from LW
where D_date<#E_Date
)
select d_date
from LW
changing the eomonth to
select dateadd(dd,datediff(dd,0,dateadd(mm,1,D_Date))/7*7+2,0)
doesn't seem to work either
Expected results:
2016-01-27
2016-02-24
2016-03-30
2016-04-27
2016-05-25
2016-06-29
Here's a way to do it with EOMONTH() and DATEFROMPARTS():
declare #S_Date date
declare #E_Date date
set #S_Date='2016-01-31' --eomonth of first month
set #E_Date='2016-06-15' --ides of last month ( can actually be any day that is
--not equal to or after the last Wednesday of the month)
;With Date (Date) As
(
Select DateFromParts(Year(#S_Date), Month(#S_Date), 1) Union All
Select DateAdd(Day, 1, Date)
From Date
Where Date < EOMonth(#E_Date)
)
Select Max(Date) As LastWednesday
From Date
Where DatePart(WeekDay, Date) = 4
Group By Year(Date), Month(Date)
Option (MaxRecursion 0)
Output
LastWednesday
2016-01-27
2016-02-24
2016-03-30
2016-04-27
2016-05-25
2016-06-29
Here a slightly different approach without the partitioning... result should be the same:
declare #S_Date date
declare #E_Date date
set #S_Date='2016-01-31' --eomonth of first month
set #E_Date='2016-06-15' --ides of last month ( can actually be any day that is
--not equal to or after the last Wednesday of the month)
;With Date (Date, WD) As
(
Select DateFromParts(Year(#S_Date), Month(#S_Date), 1), DatePart(WeekDay, DateFromParts(Year(#S_Date), Month(#S_Date), 1)) AS wd
Union All
Select DateAdd(Day, 1, Date), DatePart(WeekDay, DateAdd(Day, 1, Date)) AS wd
From Date
Where Date < EOMonth(#E_Date)
)
Select Max(Date) As LastWednesday
From Date
Where wd = 4
group by MONTH(Date)
Option (MaxRecursion 0)
You need a calendar table ..I have a calendar table and below screenshot shows you how simple it is check or get last wednesday of each month..
query used:
select * from dbo.calendar where year='2013' and wkdname='Wednesday'
and last=1
Output of above query which you can modify as per your requirement:
This is rather straightforward solution with eomonth.
declare #s_date date='2016-01-01',#e_date date='2016-12-31' --date range
;with eom_tbl as (--CTE
--anchor from start date
select EOMONTH(#s_date,0) eom,datepart(weekday,EOMONTH(#s_date,0)) dw
union all
--recursive query
select EOMONTH(eom,1) eom,datepart(weekday,EOMONTH(eom,1)) dw
from eom_tbl where eom<#e_date
)
select eom,dw,
--calculate last Wednesday (4)
case when dw<4 then dateadd(dd,-3-dw,eom)
else dateadd(dd,4-dw,eom) end lastWed,
--extra check
datepart(dw,case when dw<4 then dateadd(dd,-3-dw,eom)
else dateadd(dd,4-dw,eom) end ) ddw
from eom_tbl

Function to go back 2 years, first day of last month

I'm hoping to find a solution for this to automate a report I have. Basically what I'm trying to accomplish here is grabbing a date (first day of previous month, two years ago through last day of previous month current year).
So the date span if running this month would look like this: between 4/1/2013 and 3/31/2015
I have found code to get the date two years ago but I'm not able to also incorporate the month functions... Any help is very much appreciated!
For year I'm using this:
SELECT CONVERT(VARCHAR(25),DATEADD(year,-2,GETDATE()),101)
First day of previous month 2 years ago:
SELECT CONVERT(DATE,dateadd(day, -1, dateadd(day, 1 - day(GETDATE()), GETDATE())))
Last day of last month:
SELECT CONVERT(DATE,DATEADD(month, DATEDIFF(month, 0, DATEADD(year,-2,GETDATE())), 0))
Then just do whatever logic you need with them
Your where clause can look something like this:
where date >= cast(dateadd(year, -2,
dateadd(month, -1, getdate() - day(getdate()) + 1)
) as date) and
date < cast(getdate() - day(getdate()) + 1 as date)
This makes use of the handy convenience that subtracting/adding a number to a datetime is the same as adding a date. The start date says: get the first day of the month, then subtract one month, then subtract two years. This could have been done as dateadd(month, -25, . . .), but I think separating the logic is clearer.
This gives you two dates you are looking for:
SELECT
CAST((DATEADD(yy, -2, DATEADD(d, -1 * DATEPART(dd, getdate()) + 1 , GETDATE() ))) as date) as yourTwoYearsAgoDate,
CAST((DATEADD(d, -1 * DATEPART(dd, GETDATE()), GETDATE())) as date) as yourEndOfLastMonthDate
Given a reference date (e.g. "today"),
declare #today date = '23 April 2015'
The 1st of the month is computed by subtracting 1 less than the day number of the current month:
select first_of_current_month = dateadd(day,1-day(#today),#today)
The last day of the previous month is day 0 of the current month, so to get the last day of the previous month, just subtract the current day number:
select last_of_previous_month = dateadd(day,-day(#today),#today)
Moving two years back is easy:
select two_years_back = dateadd(year,-2, #today )
Putting it all together, this should do you:
declare #today date = '23 April 2015'
select *
first_day_of_current_month = dateadd(day,1-day(#today),#today),
last_day_of_previous_month = dateadd(day, -day(#today),#today) ,
date_from = dateadd(year,-2, dateadd(day,1-day(#today),#today) ) ,
date_thru = dateadd(day, -day(#today),#today)
yielding the expected results:
first_day_of_current_month: 2015-04-01
last_day_of_previous_month: 2015-03-31
date_from : 2013-04-01
date_thru : 2015-03-31
So you should be able to say something like this:
select *
from foo t
where t.transaction_date between dateadd(year,-2, dateadd(day,1-day(#today),#today) )
and dateadd(day, -day(#today),#today)
If you have to deal with datetime values rather than date, its easier to not use between and say something like this:
declare #today date = current_timestamp -- get the current date without a time component
select *
from foo t
where t.transaction_date >= dateadd(year,-2, dateadd(day,1-day(#today),#today) )
and t.transaction_date < dateadd(year, 0, dateadd(day, -day(#today),#today)
[superfluous addition of 0 years added for clarity]

How can I count and group by between two date?

For example,
the start date = '20100530' and
the end date = '20100602'
How can I write a SQL to display the below result?
month: may, 2 days
month: June, 2 days
Use a recursive CTE to generate all of the dates between the start and end, and then do a simple group and count (caution, not tested, but should be close if not exactly right):
with dates (the_date) as (
select #start_date
UNION ALL
select dateadd(dd, 1, the_date) from dates where the_date <= #end_date
)
select
datepart(mm, the_date) month,
count(*) num_days
from
dates
group by
datepart(mm, the_date)
TBH, you really need to provide more schema and information about the source data. However, given what little we know, you should be able to write:
Select DateName('month', [Date]) As Month
, Cast(DateDiff(d, #StartDate, #EndDate) As varchar(10) - 1) + ' days'
From Table
Where [Date] Between #StartDate And #EndDate
What we'd need to know to refine our solutions is exactly how 2 days is supposed to be calculated. Is it the days between the start and end date? Is it the day of the second parameter?