I am having trouble understanding this code. It actually seems to work, but I don't understand how the correct value for activity year and month is "found" get the proper min and max? Or is it running all permutations getting the highest? This is very strange to me.
I do understand how the dateadd works, just not how the query is actually working on the whole. This may be a bad question since I don't actually need help solving a problem, just insight into why this works.
select
EmployeeNumber,
sum(BaseCalculation) as BaseCalculation,
min(dateadd(mm, (ActivityYear - 1900) * 12 + ActivityMonth - 1 , 0)) as StartDate,
max(dateadd(mm, (ActivityYear - 1900) * 12 + ActivityMonth - 1 , 0)) as EndDate
from
Compensation
where
1=1
-- and
group by
EmployeeNumber
for both the min and max function call, the algorithm is
dateadd(mm, (ActivityYear - 1900) * 12 + ActivityMonth - 1 , 0)
Your query compute all possible date from the Compensation table using this algorithm. Then, you select the minimum date as StartDate and the maximum as EndDate.
This is how the proper max and min are returned.
Note that the dateadd signature is DATEADD (datepart , number , date )
Since the last parameter is 0, you are addind to month(mm) the number calculated in the algorithm, and return the corresponding date starting from 0.
Check this out for more information : https://msdn.microsoft.com/en-us/library/ms186819.aspx
It is converting the columns ActivityYear and ActivityMmonth to a date. It is doing so by counting the number of months since 1900 and adding them to time zero. So, Jan 2000 would become something like Jan, 100. This seems like a very arcane calculation, because dates that are about 2,000 years old are not really useful.
Of course, this assumes that ActivityYear is a recognizable recent year.
I would convert the year and month to the first day of the beginning of the month, with something like this:
min(cast(cast(ActivityYear * 10000 + ActivityMonth + 1 as varchar(255)) as date)
Sql Server will calculate every value of that statement, and then only return the min and max.
Although I cannot say for sure that sql server executes this way internally, the way I think about it I imagine that the engine strips off the group by and all the aggregate functions, runs that query. And then just sums/finds the min etc off of that.
Related
Sorry if this has been asked, but I didn't find anything when searching: I have a large table of ~100k rows in SQL Server. Within each row is a date range, which in many cases spreads across multiple months (and years to a lesser extent). The ranges are typically about 30-35 days however they usually don't start at the the 1st of the month. An example of a typical date range is 01/10/2017-02/11/2017.
I'm looking for the most efficient way to output the month with the most days within in that range as it's own column. I'm doing the same thing for the year
Right now I have the following in my query:
SELECT DISTINCT
a.START_DATE,
a.END_DATE,
cast(month(dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date)) as tinyint) as Main_Month,
cast(year(dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date)) as smallint) as Main_Year
FROM TABLE
The output from that query using the above date range example would give me:
Start_Date: 01/10/2017 End_Date: 02/11/2017 Main_Month: 1 Main Year: 2017
That method has worked alright, but it slows when being done for all rows in the table. Are there any more efficient alternatives that I can use for the Main_Month and Main_Year columns?
EDIT IN RESPONSE TO COMMENTS:
By "month w/ the most days within a date range", using my example range of 01/10/2017-02/11/2017, since that range contains 21 days in January and only 11 in February, the output I'd get for Main_Month is 1. Also since the year is 2017 throughout the range the output I'd get for Main_Year is 2017
For ties, I'd go with the 1st month containing the max # of days. In the example of, 6/20/2017 - 9/5/2017 I'd go with 7, since that is the 1st month with 31 days in the range
As discused in the comments above, this solution is not perfectly accurate, but it mirrors the accuracy of the original slower solution:
SELECT b.START_DATE, b.END_DATE, month(b.mid_point) as Main_Month, year(b.midpoint) as Main_Year FROM
(SELECT DISTINCT
a.START_DATE,
a.END_DATE,
dateadd(day, datediff(day, a.Start_Date, a.End_Date)/2, a.Start_Date) as mid_Point
FROM a) as b
You should be able to speed it up by making these two changes. First, only compute the datediff and dateadd once, then take it from the derived table to get the two fields you need. Next, don't bother with casting, since Month() and year() both do that for you. Were you able to see a speed difference with this method?
I think using CASE statements could be more efficient, and if you can avoid casting, like this:
, CASE WHEN datediff(day, a.Start_Date, a.End_Date) + 1 - DAY(a.END_Date) < DAY(a.END_DATE) THEN MONTH(a.End_Date)
ELSE MONTH(a.Start_Date) END AS Main_Month
, CASE WHEN YEAR(a.END_Date) = YEAR(a.Start_Date) THEN YEAR(a.Start_Date)
WHEN datediff(day, a.Start_Date, a.End_Date) + 1 - DAY(a.END_Date) < DAY(a.END_DATE) THEN YEAR(a.End_Date)
ELSE YEAR(a.Start_Date) END AS Main_Year
I'm trying to convert the below Excel formula into SSRS but having looked around I cannot seem to find a solution. I can calculate the number of working days between two dates but what I'm trying to do is add on a number of working days to a date. Essentially I don't have the 2nd date.
I guess it would be something along the lines of the DATEADD function?
=WORKDAY($A1,B$1)
Hope someone can help
Many thanks
Here is a tsql solution to add X Business Days to a date.
declare #calendar as table (theDate date, dayOfWeek varchar (10));
declare #startDate as date = '20170704';
declare #businessDaysToAdd as integer = 10;
insert into #calendar
select theDate
, datename(dw, theDate) dow
from
dbo.dateTable('20170701', '20170720') ;
with temp as (
select theDate
, dayOfWeek
, rank() over (order by theDate) theRank
from #calendar
where theDate > #startDate
and dayOfWeek not in ('Saturday', 'Sunday')
)
select * from temp
where theRank = #businessDaysToAdd;
Notes
dbo.DateTable is a table valued function that just happens to exist in the database I was using. In real life, you might have an actual calendar table of some sort.
This example does not include holidays.
This is only the start of the answer to the posted question. It only solves the problem of Essentially I don't have the 2nd date.
Type this into the expression for the textbox. (From SSRS 2008 Datediff for Working Days)
=(DateDiff(DateInterval.day,Parameters!STARTDATE.Value,Parameters!ENDDATE.Value)+1)
-(DateDiff(DateInterval.WeekOfYear,Parameters!STARTDATE.Value,Parameters!ENDDATE.Value)*2)
-(iif(Weekday(Parameters!STARTDATE.Value) = 7,1,0)
-(iif(Weekday(Parameters!ENDDATE.Value) = 6,1,0))-1)
Ok after much perseverance I managed to get what I wanted in both TSQL and SSRS. My objective was to measure Agent productivity so I didn’t want to count the weekend and this would be unfair. If a date fell on a weekend then I wanted it to jump to a Monday. Likewise if adding number of days onto a date went over a weekend in the future then I needed the incremented date to reflect this. For the end user (In SSRS) I wanted a leading edge (Like an Upside down triangle) so that if the date + number working days was in the future then set to NULL, showing a zero would look like no productivity which is incorrect.
First TSQL - My base query started with the following SO thread but after trying many of the options I was finding when the date fell on a Saturday or Sunday the solution did not work for me (I was unable to create functions due to permissions). However tweaking the below got me there and I dealt with Sunday specifically
Add business days to date in SQL without loops
SELECT
,DATEADD(WEEKDAY, (/*Your Working Days*//5)*7+(/*Your Working Days*/ % 5) +
(CASE WHEN DATEPART(WEEKDAY,/*Your Date*/) <>7 AND DATEPART(WEEKDAY,/*Your Date*/) + (/*Your Working Days*/ % 5) >5 THEN 2
WHEN DATEPART(WEEKDAY,/*Your Date*/) = 7 AND DATEPART(WEEKDAY,/*Your Date*/) + (/*Your Working Days*/ % 5) >5 THEN 1 ELSE 0 END), /*Your Date*/) AS [IncrementedDate]
FROM /*YourTable*/
Then for SSRS - The 2 key points here is that TSQL will divide as an integer if the source number is an integer so this needs to be handled in SSRS and secondly you need to set the first week day to Monday as part of the expression.
I put this expression into a Matrix with Date Created being my Row Group and Contact Working Days being my Column Group.
=DATEADD("W",(INT(ReportItems!ContactWorkingDays.Value/5))*7+(ReportItems!ContactWorkingDays.Value MOD 5) + IIF(DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) <> 7 AND (DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) + (ReportItems!ContactWorkingDays.Value MOD 5) >5),2,IIF(DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) = 7 AND (DATEPART("W",ReportItems!DateCreated.Value,FirstDayOfWeek.Monday) + (ReportItems!ContactWorkingDays.Value MOD 5) >5),1,0)),ReportItems!DateCreated.Value)
This does not include holidays - I'm not too bothered at this stage and that is for a rainy day! :)
I know this one is pretty easy but I've always had a nightmare when it comes to comparing dates in SQL please can someone help me out with this, thanks.
I need to get the month and year of now then compare it to a date stored in a DB.
Time Format in the DB:
2015-08-17 11:10:14.000
I need to compare the month and year with now and if its > 12 months old I will increment a count. I just need the number of rows where this argument is true.
I assume you have a datetime field.
You can use the DATEDIFF function, which takes the kind of "crossed boundaries", the start date and the end date.
Your boundary is the month because you are only interested in year and month, not days, so you can use the month macro.
Your start time is the value stored in the table's row.
Your end time is now. You can get system time selecting SYSDATETIME function.
So, assuming your table is called mtable and the datetime object is stored in its date field, you simply have to query:
SELECT COUNT(*) FROM mtable where DATEDIFF(month, mtable.date, (SELECT SYSDATETIME())) > 12
I am new to T-SQL and needed urgent assistance here.
I am trying to get the week number from a given date.
I understand that there is a build in function for it but the value return is not exactly what I wanted.
For e.g., by using select datepart(wk, '2013-01-07'), it would return me '2'.. but the actually fact is it should return '1' instead of '2'.
Any ideas how to correct this issue?
You can use dy datepart specifier to get dayOfYear number and divide it by 7:
select (datepart(dy, '2013-01-05') - 1) / 7 + 1;
Working DEMO.
Try this
SELECT DATEPART(WEEK,GETDATE())
This depends on hop you define the first week. Does it always start on the same weekday? or does it always start on the first of January? If you want it to always start on the same weekday, then use Set datefirst to tell T-SQL what weekdaty you want to define as the start of the week. If you want it to always start on Jan 1, then just use day of year instead of week, subtract 1, integer divide by 7 and add 1.
declare #dat DateTime = getdate()
Select Select (datepart(dy, #dat)-1) / 7 + 1
Although going from memory, I believe the ISO standard for the first week of the year is the week in the year that the first Thursday of the year is in. This would possibly explain why the built in function gives a result different to that you require.
I am trying to get the last of month, and in order to that i have written the following, to calculate the no. of days between today and the last date.
select datediff(DAY,GETDATE(),dateadd(m,1,getdate()))-GETDATE()
the bold part gives me the no. of days between today and a month from today, say 30 or 31. and then I am subtracting today's date from 30 or 31, which is " -getdate() "
The output for the above query is
1786-06-06 11:44:30.540
Could you please explain what is happening in the query? I am not looking for a solution, I would like to know how is SQL-Server interpreting the query.
Thanks. :)
The bold part of the expressions does not return a date, it returns a number of days:
31
Convert that to a datetime:
SELECT CONVERT(DATETIME, 31);
This is 31 days after day 0 (1900-01-01):
1900-02-01
Now, subtract GETDATE() as an integer (41512 days after day 0):
SELECT 31 - 41512 = -41481
Now add -41481 days to day 0:
SELECT DATEADD(DAY, -41481, 0);
-- or
SELECT DATEADD(DAY, -41481, '19000101');
Or:
SELECT CONVERT(DATETIME, 31 - CONVERT(INT, GETDATE()));
Now, I strongly recommend a couple of things:
Don't use implicit date math. #date_var_or_col - 1 for example fails with new data types like DATE and DATETIME2.
Don't use shorthand like m. If you mean MONTH, just take the massive productivity hit and type out MONTH. To see why, tell me if this provides the results you expect:
SELECT DATEPART(y, GETDATE()), DATEPART(w, GETDATE());
I am subtracting today's date from 30 or 31, which is " -getdate() "
Sounds like you understand exactly what is happening, but maybe don't understand the results.
You are implicitly converting GETDATE() to a number, which represents the number of days (and fractional days) since 1/1/1900 12:00:00 AM
When you "subtract" GETDATE() (41,511 as of 8/27/2013) from 30 or 31 you get an answer of -41,480, or 41,480 days before 1/1/1900, which would be about 6/6/1786 (plus or minus a few hours for the fractional part).