SQL Server - Get first date in a week, given the week number? - sql-server

I've got a query (for use in bug tracker.net) that calculates the number of bugs by week by status. But the query returns the week number, what I really want is the first date of the week
select datepart(wk, DateAdd(day, 0, DateDiff(day, 0, bg_reported_date)))
as [week], bg_status , st_name as [status], count(*) as [count]
from bugs inner join statuses on bg_status = st_id
group by datepart(wk, DateAdd(day, 0, DateDiff(day, 0, bg_reported_date))),
bg_status, st_name
order by [week], bg_status
The part that gets the week number is
datepart(wk, DateAdd(day, 0, DateDiff(day, 0, bg_reported_date))) as [week]
It returns this output:
week bg_status status count
----------- ----------- --------------------------------------------- ------
22 1 new 1
22 5 closed 32
But it would be better to say the first date of each week, eg 01-01-2010, then 08-01-2010, etc
Question is not a duplicate of How do you get the "week start date" and "week end date" from week number in SQL Server? (answer says how to get week start from a date not from a week number)
Not a duplicate of Calculate date from week number (question asks for c#)
Not a duplicate of Get first date of week from provided date (question asks for javascript)
I did search but couldn't find this question answered for SQL Server (2010 if it matters)

If you think about it in the right way, the answer to SO 1267126 can be applied to your problem.
Each of the bug reported dates that you have in the group maps to the same week. By definition, therefore, each of those bug dates must also map to the same start of the week. So, you run the 'start of the week from given date' calculation on the bug report dates, as well as the week number calculation, and group by both (modestly ghastly) expressions, and end up with the answer you seek.
SELECT DATEPART(wk, DATEADD(day, 0, DATEDIFF(d, 0, bg_reported_date))) [week],
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1), bg_reported_date)
AS [weekstart], bg_status, st_name AS [status], COUNT(*) AS [count]
FROM bugs INNER JOIN statuses ON bg_status = st_id
GROUP BY DATEPART(wk, DATEADD(day, 0, DATEDIFF(day, 0, bg_reported_date))),
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1), bg_reported_date),
bg_status, st_name
ORDER BY [week], bg_status
Since bg_reported_date is a DATETIME (see the comment; it includes a time component), it is necessary to cast it to DATE before determining the week-start (but the week number expression doesn't need the cast, and the 'day of week' part of the week start expression doesn't need the cast either):
SELECT DATEPART(wk, DATEADD(day, 0, DATEDIFF(d, 0, bg_reported_date))) [week],
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1),
CAST(bg_reported_date AS DATE)) AS [weekstart],
bg_status, st_name AS [status], COUNT(*) AS [count]
FROM bugs INNER JOIN statuses ON bg_status = st_id
GROUP BY DATEPART(wk, DATEADD(day, 0, DATEDIFF(day, 0, bg_reported_date))),
DATEADD(dd, -(DATEPART(dw, bg_reported_date)-1),
CAST(bg_reported_date AS DATE),
bg_status, st_name
ORDER BY [week], bg_status
NB: Untested code!

I realize this is a very old thread, but "Get first date in a week, given the week number" is exactly what I wanted to do and I do NOT have an actual date to work with, so the accepted answer would not work for me. I thought I'd post my solution for posterity. Note that I suspect different culture settings MAY break this, so test before using.
My answer is built starting from this one.
Let's assume you know a week number and a year and you want to get the start and end dates for that week of that year. Here's what I have:
--These 2 "declared" variables would be passed in somehow
declare #WeekNumber int = DATEPART(wk, GETDATE())
declare #ForYear int = YEAR(GETDATE())-1
--Since we don't have a raw date to work with, I figured I could just start with
--Jan 1 of that year. I'll store that date in a cte here, but if you are doing this
--in a stored proc or function, it would make much more sense to use another #variable
;with x as
(
--this method works in SQL 2008:
SELECT CONVERT(DateTime, ('1/1/' + CONVERT(varchar, #ForYear))) as Jan1ForSelectedYear
--If you are using 2014 or higher, you can use this instead:
--DATETIME2FROMPARTS(#ForYear, 1, 1, 0,0,0,0,0)
)
--Now that we have a date to work with, we'll just add the number of weeks to that date
--That will bring us to the right week number of the given year.
--Once we have THAT date, we can get the beginning and ending of that week
--Sorry to make you scroll, but I think this is easier to see what is going on this way
SELECT CONVERT(varchar(50), DateAdd(wk, (#WeekNumber - 1), (DATEADD(dd, ##DATEFIRST - DATEPART(dw, x.Jan1ForSelectedYear) - 6, x.Jan1ForSelectedYear))), 101) as FirstDayOfWeekXForSelectedYear,
CONVERT(varchar(50), DateAdd(wk, (#WeekNumber - 1), (DATEADD(dd, ##DATEFIRST - DATEPART(dw, x.Jan1ForSelectedYear) , x.Jan1ForSelectedYear))), 101) as LastDayOfWeekXForSelectedYear
FROM x

Related

T-SQL : filter out all employees who left before > 6 months

I need to create a new employee-database that filters out all employees, who left the company longer than 6 months ago from today.
I have a table with entry date, exit date and tried something like :
WHERE [exit date] > = DATEADD(M, -6, getdate())
That didn't work because it shows only the employees who left the company longer than 6 months ago. I just want to filter them out automatically and only show the employees, who are still employed er left the company lesser than 6 months ago.
Thanks in advance for your help.
You need to check for null:
WHERE ([exit date] IS NULL OR [exit date] > = DATEADD(M, -6, getdate()))
Do not try tricks with NOT and <
WHERE NOT ([exit date] < DATEADD(M, -6, getdate()))
This doesn't work because NULL rows will just result in UNKNOWN, so those rows will not be returned.
You can do this though, you may find it performs faster or slower than the first version:
WHERE NOT EXISTS (SELECT 1
WHERE [exit date] < DATEADD(M, -6, getdate())
This works because if exit date is null then no row gets returned from the subquery.

SQL Server: If the beginning of the week is Monday, why is the previous Sunday included in that week?

I'm working on a stored procedure that outputs a grid for charting. Users can input an interval to display the chart in days, weeks, months, etc. The data that drives this grid has a bunch of timestamps in a column called moddate. In order to group things, I'm using:
DATEADD(<interval>, datediff(<interval>, 0, moddate), 0)
Days, Months, Quarters, and Years all work fine. Weeks, however are including the previous Sunday. For example, The following select statement gives me an answer of 2017-04-03:
select "start_of_week" = dateadd(week, datediff(week, 0, '2017-04-02 00:00:00.000'), 0);
start_of_week:
'2017-04-03 00:00:00.000'
Changning ##datefirst does not affect the results.
I guess my question is twofold:
Why is April 2nd considered part of the week that starts with April 3rd?
Is there a better way to get around this than to first check for an interval of weeks, and then check every date to see if it's a Sunday and if it is, put -7 instead of 0 at the end?
Thanks in advance for any help.
I ended up using a CASE in both the select and group by:
SELECT CASE
WHEN #interval = 'week'
THEN DATEADD(<interval>, DATEDIFF(day, 0, moddate)/7, 0)
ELSE DATEADD(<interval>, DATEDIFF(<interval>, 0, moddate), 0)
END as moddate,
fullname,
isnull(totals, 0) as totals
FROM #results
GROUP BY CASE
WHEN #interval = 'week'
THEN DATEADD(<interval>, DATEDIFF(day, 0, moddate)/7, 0)
ELSE DATEADD(<interval>, DATEDIFF(<interval>, 0, moddate), 0)
END, fullname
The crucial bit was using the case again in the group by.
My thanks to ZLK for the /7 bit, as subtracting 7 was taking a week off of every answer.

Query to get the first day of the month not working with DATEADD

I've been writing queries to truncate the time from a given datetime for years now, based on answers like this one and this one (and many other places), that groups a couple functions like
SELECT DATEADD(day, 0, DATEDIFF(day, 0, getdate()))
and it always gives the right answer.
I figured I could translate the same logic to finding the first of the current month by using month instead of day, but it's giving me a weird date for the result: 1903-10-17, instead of 2015-05-01.
My parameters have always been in the wrong order.
It turns out the format for DATEADD I've been using all these years is wrong, and it's only been working because it's using the day datepart. Casting an int to a date increments the day:
SELECT CAST(0 AS datetime) = '1900-01-01 00:00:00.000'
SELECT CAST(1 AS datetime) = '1900-01-02 00:00:00.000'
SELECT CAST(2 AS datetime) = '1900-01-03 00:00:00.000'
I should be using DATEADD(d, DATEDIFF(d, 0, getdate()), 0) - the parameters are (datepart, number, date), as laid out here at MSDN.
Writing it as SELECT DATEADD(month, DATEDIFF(month, 0, getdate()), 0) gives the expected result of 2015-05-01.

Adding Month-to-date and Year-to-date in a query using SQL Server 2012?

so I'm trying to make a query that includes a daily sum of the amount from the first instance the database starts collecting data to the last available instance of that date (database collects data every hour). And while I have done this, now I have to make it show a month to date and a year to date sum amount. I have tried various ways to come up with this but have had no luck. Below is the code that I believe is the closest I have gotten to achieve this. Can someone help me make my code work or suggest another way around this?
Select * from
(
SELECT Devices.DeviceDesc,
SUM(DeviceSummaryData.Amount) AS MTD,
Devices.Area,
MIN(DeviceSummaryData.StartDate) AS FirstOfStartDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate = MONTH(getdate())) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, GETDATE())
AS date))
GROUP BY Devices.DeviceDesc, Devices.Area, DATEPART(day, DeviceSummaryData.StartDate)
--
) q2
UNION ALL
SELECT * FROM (
SELECT Devices.DeviceDesc,
Sum(Amount) as Daily,
Devices.Area,
MIN(StartDate) as FirstDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, GETDATE()) AS date)) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, getdate()) AS date))
GROUP BY Devices.Area,
Devices.DeviceDesc,
DATEPART(day, DeviceSummaryData.StartDate)
ORDER BY Devices.DeviceDesc
) q2
Another type of attempt I have tried would be this:
SELECT Devices.DeviceDesc,
Sum(case
when DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, getdate()) AS date)
THEN Amount
else 0
end) as Daily,
Sum(case
when Month(StartDate) = MONTH(getdate())
THEN Amount
else 0
end) as MTD,
Devices.Area,
MIN(StartDate) as FirstDate,
MAX(DeviceSummaryData.EndDate) AS LastOfStartDate
FROM Devices INNER JOIN DeviceSummaryData ON Devices.DeviceID = DeviceSummaryData.DeviceID
WHERE (DeviceSummaryData.StartDate >= CAST(DATEADD(DAY, 0, GETDATE()) AS date)) AND (DeviceSummaryData.EndDate <= CAST(DATEADD(DAY, 1, getdate()) AS date))
GROUP BY Devices.Area,
Devices.DeviceDesc,
DATEPART(day, DeviceSummaryData.StartDate)
ORDER BY Devices.DeviceDesc
I'm not the best with Case When's, but I saw somewhere that this is a possible way to do this. I'm not too concerned with the speed or efficiency, I just need it to generate the query to be able to get the data. Any help and Suggestions are greatly appreciated!
The second attempt is on the right track but a bit confused. In the CASE statements you are trying to compare months etc, but your WHERE clause restricts the data you're looking at to a single day. Also, your GROUP BY should not include the day anymore. If you say in English what you want, it's "For each device area and type, I want to see a total, a MTD total and a YTD total". It's that "For each" bit that should define what appears in your GROUP BY.
Just remove the WHERE clause entirely and get rid of DATEPART(day, DeviceSummaryData.StartDate) from your GROUP BY and you should get the results you want. (Well, a daily and monthly total, anyway. Yearly is achieved much the same way).
Also note that DATEADD(DAY, 0, GETDATE()) is identical to just GETDATE().

SQL Server - Finding the first day of the week

I've been looking around for a chunk of code to find the first day of the current week, and everywhere I look I see this:
DATEADD(WK, DATEDIFF(WK,0,GETDATE()),0)
Every place says this is the code I'm looking for.
The problem with this piece of code is that if you run it for Sunday it chooses the following Monday.
If I run:
SELECT GetDate() , DATEADD(WK, DATEDIFF(WK,0,GETDATE()),0)
Results for today (Tuesday):
2013-05-14 09:36:39.650................2013-05-13 00:00:00.000
This is correct, it chooses Monday the 13th.
If I run:
SELECT GetDate()-1 , DATEADD(WK, DATEDIFF(WK,0,GETDATE()-1),0)
Results for yesterday (Monday):
2013-05-13 09:38:57.950................2013-05-13 00:00:00.000
This is correct, it chooses Monday the 13th.
If I run:
SELECT GetDate()-2 , DATEADD(WK, DATEDIFF(WK,0,GETDATE()-2),0)
Results for the 12th (Sunday):
2013-05-12 09:40:14.817................2013-05-13 00:00:00.000
This is NOT correct, it chooses Monday the 13th when it should choose the previous Monday, the 6th.
Can anyone illuminate me as to what's going in here? I find it hard to believe that no one has pointed out that this doesn't work, so I'm wondering what I'm missing.
It is DATEDIFF that returns the "incorrect" difference of weeks, which in the end results in the wrong Monday. And that is because DATEDIFF(WEEK, ...) doesn't respect the DATEFIRST setting, which I'm assuming you have set to 1 (Monday), and instead always considers the week crossing to be from Saturday to Sunday, or, in other words, it unconditionally considers Sunday to be the first day of the week in this context.
As for an explanation for that, so far I haven't been able to find an official one, but I believe this must have something to do with the DATEDIFF function being one of those SQL Server treats as always deterministic. Apparently, if DATEDIFF(WEEK, ...) relied on the DATEFIRST, it could no longer be considered always deterministic, which I can only guess wasn't how the developers of SQL Server wanted it.
To find the first day of the week's date, I would (and most often do actually) use the first suggestion in #Jasmina Shevchenko's answer:
DATEADD(DAY, 1 - DATEPART(WEEKDAY, #Date), #Date)
DATEPART does respect the DATEFIRST setting and (most likely as a result) it is absent from the list of always deterministic functions.
Try this one -
SET DATEFIRST 1
DECLARE #Date DATETIME
SELECT #Date = GETDATE()
SELECT CAST(DATEADD(DAY, 1 - DATEPART(WEEKDAY, #Date), #Date) AS DATE)
SELECT CAST(#Date - 2 AS DATE), CAST(DATEADD(WK, DATEDIFF(WK, 0, #Date-2), 0) AS DATE)
Results:
---------- ----------
2013-05-12 2013-05-13
SQL Server has a SET DATEFIRST function which allows you to tell it what the first day of the week should be. SET DATEFIRST = 1 tells it to consider Monday as the first day of the week. You should check what the server's default setting is via ##DATEFIRST. Or you could simply change it at the start of your query.
Some references:
MSDN
Similar Question
That worked for me like a charm:
Setting moday as first day of the week without changing DATEFIRST variable:
-- FirstDayWeek
select dateadd(dd,(datepart(dw, case datepart(dw, [yourDate]) when 1 then dateadd(dd,-1,[yourDate]) else [yourDate] end) * -1) + 2, case datepart(dw, [yourDate]) when 1 then dateadd(dd,-1,[yourDate]) else [yourDate] end) as FirstDayWeek;
-- LastDayWeek
select dateadd(dd, (case datepart(dw, [yourDate]) when 1 then datepart(dw, dateadd(dd,-1,[yourDate])) else datepart(dw, [yourDate]) end * -1) + 8, case datepart(dw, [yourDate]) when 1 then dateadd(dd,-1,[yourDate]) else [yourDate] end) as LastDayWeek;
Setting sunday as fist day of the week without changing DATEFIRST variable
select convert(varchar(50), dateadd(dd, (datepart(dw, [yourDate]) * -1) + 2, [yourDate]), 103) as FirstDayWeek, convert(varchar(50), dateadd(dd, (datepart(dw, [yourDate]) * -1) + 8, [yourDate]), 103) as LastDayWeek;
You can change [yourDate] by GETDATE() for testing

Resources