How to single out values in a database based on date being month-end? - sql

I have a problem where I need to query a database which includes multiple lines of trade activity for the past 90 days. Currently the query is built to determine the average amount over the 90 day period - so each day has a single exposure value and the query helps us determine the average exposure over 90 days by just summing the daily values and then dividing by 90. And it does this as the date rolls forward, so the value is updated each day the query is run.
The above is simple enough to execute, but now I need to determine the average month-end amounts for the past 3 months. I've figured out how to pull just month-end dates, but not sure how to join that with the current query. Additionally, needs to be able to update itself rolling forward.
/* Test query below */
DECLARE #Date DATETIME = Getdate()
DECLARE #daycount INT = 90
DECLARE #startDate DATETIME = Dateadd(dd, #daycount*-1, #Date)
SELECT sub.Instrument,
( Sum(sub.GrossExposure) / #daycount ) AS AvgGrossExposure
FROM (SELECT DateField,
Instrument,
GrossExposure
FROM table
WHERE DateField <= #Date
AND Datefield >= #startDate
) sub
GROUP BY Instrument
To calculate month-ends in the past 90 days, I've fiddled around with this, but it also includes today's date and I do not need that value in this case.
/* Test query for month-end dates, past 90 days */
DECLARE #Date DATETIME = GetDate()
DECLARE #daycount INT = 90
DECLARE #startDate DATETIME = Dateadd(dd, #daycount*-1, #Date)
SELECT max(datefield) AS month_ends
FROM table
WHERE datefield <= #Date
AND datefield >= #startDate
GROUP BY month(datefield),
year(datefield)
ORDER BY month_ends

Give this a try - you can use a common table expression to append the month end date of each DateField value using EOMONTH(DateField), and then use that in your GROUP BY, with the Average of all GrossExposure values that have that same EOMONTH value for each instrument.
WITH CTE AS (
SELECT EOMONTH(DateField) AS EndOfMonthDate
,DateField
,Instrument
,GrossExposure
FROM TABLE
WHERE DateField BETWEEN GETDATE()-90 AND GETDATE()
)
SELECT CTE.Instrument,
CTE.EndOfMonthDate,
AVG(CTE.GrossExposure) AS AvgGrossExposure
FROM CTE
GROUP BY CTE.Instrument, CTE.EndOfMonthDate

Related

SQL date difference ignoring years

I have two date columns and I would like to calculate their differences in Months/Days and exclude years.
Assuming the first date is 10/30/2017, comparing it to the current date, its difference should one. If the current date is 10/30/2018, the difference should also be one.
A few examples:
Schedule Date:10/30/2017 Current Date 10/29/2017 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2017 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2018 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2018 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2019 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2019 Diff 30
Try This
SELECT ABS(365 * DATEDIFF(year, '10/30/2017', '11/30/2018')
- DATEDIFF(day, '10/30/2017', '11/30/2018')) AS DateDiff;
Note that the difference between 10/30/ and 11/30/ can't be 30 days as you have shown.It is 31 days.
You can use Common Table Expression to get your results like below :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2019'
;WITH usingCTE AS
(
SELECT CAST(STUFF(CONVERT(varchar, #Schedule_Date, 102), 1, 4, CAST(YEAR(#Current_Date) AS varchar)) AS datetime) AS Schedule_Date
)
SELECT abs(DATEDIFF(day, #Current_Date, Schedule_Date)) FROM usingCTE
Another approach of the query :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2017'
SELECT ABS(DATEDIFF(day,
REPLACE(#Schedule_Date, DATEPART(year, #Schedule_Date),
DATEPART(year, #Current_Date)), -- replace the year with current year
#Current_Date))
This might work for you, first adjust the date to be in the same year, then calculate the number of days different between the two dates:
DECLARE #ScheduleDate DATE = '2017-10-30';
DECLARE #Dates TABLE (CurrentDate DATE);
INSERT INTO #Dates VALUES ('2017-10-29'),('2017-11-30'),('2018-10-29'),('2018-11-30'),('2019-10-29'),('2019-11-30');
SELECT #ScheduleDate ScheduleDate, *
FROM #Dates a
CROSS APPLY (SELECT AdjustedDate=DATEADD(YEAR, YEAR(#ScheduleDate) - YEAR(a.CurrentDate), a.CurrentDate)) b
CROSS APPLY (SELECT Diff=ABS(DATEDIFF(DAY, #ScheduleDate, b.AdjustedDate))) c

SQL query to find employee aniversary

I need to find anniversary date and anniversary year of employees and send email in every 14 days.But I have a problem with last week of December when using the following query if start date and end date are in different years.
Select * from Resource
where (DATEPART(dayofyear,JoinDate)
BETWEEN DATEPART(dayofyear,GETDATE())
AND DATEPART(dayofyear,DateAdd(DAY,14,GETDATE())))
Instead of comparing to a dayofyear (which resets to zero at jan 1st and is the reason your query breaks within 14 days of the end of the year) you could update the employee's joindate to be the current year for the purpose of the query and just compare to actual dates
Select * from Resource
-- Add the number of years difference between joinDate and the current year
where DATEADD(year,DATEDIFF(Year,joinDate,GetDate()),JoinDate)
-- compare to range "today"
BETWEEN GetDate()
-- to 14 days from today
AND DATEADD(Day,14,GetDate())
-- duplicate for following year
OR DATEADD(year,DATEDIFF(Year,joinDate,GetDate())+1,JoinDate) -- 2016-1-1
BETWEEN GetDate()
AND DATEADD(Day,14,GetDate())
Test query:
declare #joindate DATETIME='2012-1-1'
declare #today DATETIME = '2015-12-26'
SELECT #joinDate
where DATEADD(year,DATEDIFF(Year,#joinDate,#today),#JoinDate) -- 2015-1-1
BETWEEN #today -- 2015-12-26
AND DATEADD(Day,14,#today) -- 2016-01-09
OR DATEADD(year,DATEDIFF(Year,#joinDate,#today)+1,#JoinDate) -- 2016-1-1
BETWEEN #today -- 2015-12-26
AND DATEADD(Day,14,#today) -- 2016-01-09
(H/T #Damien_The_Unbeliever for a simple fix)
The above correctly selects the joinDate which is in the first week of Jan (note I've had to fudge #today as Ive not managed to invent time travel).
The above solution should also solve the issue with leap years that was hiding in your original solution.
Update
You expressed in comments the requirement to select AnniversaryDate and Years of service, you need to apply some CASE logic to determine whether to add 1 (year or date) to your select
select *,
CASE
WHEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate) < GetDate()
THEN DATEDIFF(Year,JoinDate,GETDATE())+1
ELSE DATEDIFF(Year,JoinDate,GETDATE())
END as [Years],
CASE WHEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate) < GetDate()
THEN DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE())+1,JoinDate)
ELSE DATEADD(YEAR,DATEDIFF(Year,JoinDate,GETDATE()),JoinDate)
end as [AnniversaryDate]
.... // etc
You could do this:
Select * from Resource
where DATEPART(dayofyear,JoinDate)
BETWEEN DATEPART(dayofyear,GETDATE())
AND DATEPART(dayofyear,DateAdd(DAY,14,GETDATE()))
OR
DATEPART(dayofyear,JoinDate)
BETWEEN (DATEPART(dayofyear,GETDATE()) + 365)
AND (DATEPART(dayofyear,DateAdd(DAY,14,GETDATE())) + 365)
Try this:
DECLARE #Today DATE = GETDATE() --'12/25/2013'
DECLARE #Duration INT = 14
;WITH Recur AS
(
SELECT #Today AS RecurDate
UNION ALL
SELECT DATEADD(DAY, 1, RecurDate)
FROM Recur
WHERE DATEDIFF(DAY, #Today, RecurDate)+1 < #Duration
)
SELECT
r.*
FROM
Resource r
JOIN Recur
ON CONVERT(VARCHAR(5), JoinDate, 101) = CONVERT(VARCHAR(5), RecurDate, 101)
WHERE JoinDate < #Today
You can use the SQL DATEADD() function with week number parameter
Here is how you can use it:
DECLARE #date date = getdate()
Select * from Resource
where
JoinDate BETWEEN #date AND DATEADD(ww,2,#date)

TSQL select columns from rollup data columns

I have a rollup data table which stores data for daily counts, monthly counts and yearly counts.
"rollup_type" designates if its a daily(1)/monthly(2)/yearly data(3). For a yearly record, both monthl/daily is null. For monthly record, daily is null.
I am trying to do a simple SELECT based on input start date and end date with no success. Here is what I tried which isn't working correctly.
declare #start_date datetime = '2013-02-01'
declare #enddate datetime = '2014-01-12'
select * from olr_rollup_data rd
where ( rd.Year > DATEPART(YYYY, #start_date) or ( rd.Year = DATEPART(YYYY, #start_date) and ( (rd.Month > DATEPART(MM, #start_date) and rd.day is null) or (rd.MONTH = DATEPART(MM, #start_date) and rd.day >= DATEPART(DD, #start_date) ))))
and ( rd.Year < DATEPART(YYYY, #enddate) or ( rd.Year = DATEPART(YYYY, #enddate) and ( (rd.Month < DATEPART(MM, #enddate) and rd.day is null) or ( rd.MONTH = DATEPART(MM, #enddate) and rd.Day <= DATEPART(DD, #enddate) ))))
Basically, a generic select statement which will use combination of daily, monthly and yearly data from input dates. It should select days plus full month when input dates cover full month in between and so on.
I would appreciate if you help figure out correct select statement. Thank you.
Dates are always a pain to work with, that is why it is nice to use the built in date time functions when available. Sadly that won't work here, we have to figure out some tricks.
One way to work with dates as "numbers" is to multiply the year by 10000, the month by 100 and add these numbers to the day of the month. This will give you an integer where the digits look like this
YYYYMMDD
While disjointed (there are a lot of integers you will never see) any integer with this representation has the same cardinality as the date it represents (those that are larger as a date are also larger as an integer.)
We can use this to solve your problem as follows:
DECLARE #startDate INTEGER = 20130201
DECLARE #endDate INTEGER = 20140112
SELECT *
FROM
(
SELECT ((year*10000)+(ISNULL(month,0)*100)+ISNULL(day,0)) as dateInt, *
FROM olr_rollup_date
) sub
WHERE dateInt >= #startDate AND dateInt <= #endDate
This will include roll up row for the month that are "surrounded". Since we default the null values to 0 a month null for May of 2014 would be 20140500. This is greater than any date in April and less than any date June but smaller than any date in May.
Years will work in a similar way.

calculating working hours in a month

I am working on an attendance software in asp.net, in it i have to make a report which will tell the user about the hours and everything...so far i have created the basic functionality of the system, i.e. the user can check in and check out...i am stuck at making the report...
I have to calculate the working hours for every month, so the user can compare his hours with the total hours...what i had in mind was to create a stored procedure which when given a month name and a year, returns an int containing working hours for that month....but i can seem to get at it....
so far i found out how to create a date from a given month and a date, and found out the last day of that month, using which i can find out the total days in month...now i cant seem to figure out how do i know how much days to subtract for getting the working days.
here's the so far code..
declare
#y int,
#m int,
#d int,
#date datetime
set #y = 2012
set #m = 01
set #d = 01
----To create the date first
select #date = dateadd(mm,(#y-1900)* 12 + #m - 1,0) + (#d-1)
----Last Day of that date
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#date)+1,0))
any help will be appreciated guys, thanks in advance....
The #theDate is any date on the month you want to calculate the work days. This approach does not take care about holidays.
DECLARE #theDate DATETIME = GETDATE()
SELECT MONTH(#theDate) [Month], 20 + COUNT(*) WorkDays
FROM (
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 28) AS theDate
UNION
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 29)
UNION
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, #theDate), 30)
) AS d
WHERE DATEPART(DAY, theDate) > 28
AND DATEDIFF(DAY, 0, theDate) % 7 < 5
Here you can consider the below sql server code to get the first and
last day of the given month and also ignore all the Saturdays and Sundays.
DECLARE #curr_date datetime=getdate()
DECLARE #st_date datetime,#ed_date datetime
select #st_date=DATEADD(mm,datediff(mm,0,#curr_date),0),#ed_date = DATEADD(mm,datediff(mm,-1,#curr_date),-1)
--select #st_date as first_day,#ed_date as last_day
SET DATEFIRST 1 --Monday as first day of week
select DATEADD(dd,number,#st_date) from master..spt_values
where DATEDIFF(dd,DATEADD(dd,number,#st_date),#ed_date) >= 0 and type='P'
and DATEPART(DW,DATEADD(dd,number,#st_date)) <> 6
and DATEPART(DW,DATEADD(dd,number,#st_date)) <> 7
But inorder to calculate the actual working hours, you will have to take into the consideration of following thigs
1.Calculate the time interval between swipe-in and swipe-outs between start and end time for a day.
2.Exclude all the time gap(employee not in office)
3.Consider the company holidays.
etc
Here is a UDF to count work days. You can pass any date of a month to this function. But usually you should use actual "calendar" table to calculate work days and insert in this table weekends, holidays,... and so on.
CREATE FUNCTION dbo.WorkDaysCount (#Date datetime)
RETURNS int AS
BEGIN
DECLARE #BeginOfMonth datetime
SET #BeginOfMonth=DATEADD(DAY,-DAY(#Date)+1,#Date);
DECLARE #EndOfMonth datetime
SET #EndOfMonth=DATEADD(Day,-1,DATEADD(Month,1,#BeginOfMonth));
DECLARE #cDate datetime
set #cDate=#BeginOfMonth
Declare #WorkDaysCount int
SET #WorkDaysCount=0
while #cDate<=#EndOfMonth
begin
if DATEPART(dw,#cDate) not in (1,7) SET #WorkDaysCount=#WorkDaysCount+1 -- not a Sunday or Saturday change (1,7) to (6,7) if you have other week start day (Monday).
set #cDate=#cDate+1;
end;
return (#WorkDaysCount);
END

Grouping by contiguous dates, ignoring weekends in SQL

I'm attempting to group contiguous date ranges to show the minimum and maximum date for each range. So far I've used a solution similar to this one: http://www.sqlservercentral.com/articles/T-SQL/71550/ however I'm on SQL 2000 so I had to make some changes. This is my procedure so far:
create table #tmp
(
date smalldatetime,
rownum int identity
)
insert into #tmp
select distinct date from testDates order by date
select
min(date) as dateRangeStart,
max(date) as dateRangeEnd,
count(*) as dates,
dateadd(dd,-1*rownum, date) as GroupID
from #tmp
group by dateadd(dd,-1*rownum, date)
drop table #tmp
It works exactly how I want except for one issue: weekends. My data sets have no records for weekend dates, which means any group found is at most 5 days. For instance, in the results below, I would like the last 3 groups to show up as a single record, with a dateRangeStart of 10/6 and a dateRangeEnd of 10/20:
Is there some way I can set this up to ignore a break in the date range if that break is just a weekend?
Thanks for the help.
EDITED
I didn't like my previous idea very much. Here's a better one, I think:
Based on the first and the last dates from the set of those to be grouped, prepare the list of all the intermediate weekend dates.
Insert the working dates together with weekend dates, ordered, so they would all be assigned rownum values according to their normal order.
Use your method of finding contiguous ranges with the following modifications:
1) when calculating dateRangeStart, if it's a weekend date, pick the nearest following weekday;
2) accordingly for dateRangeEnd, if it's a weekend date, pick the nearest preceding weekday;
3) when counting dates for the group, pick only weekdays.
Select from the resulting set only those rows where dates > 0, thus eliminating the groups formed only of the weekends.
And here's an implementation of the method, where it is assumed, that a week starts on Sunday (DATEPART returns 1) and weekend days are Sunday and Saturday:
DECLARE #tmp TABLE (date smalldatetime, rownum int IDENTITY);
DECLARE #weekends TABLE (date smalldatetime);
DECLARE #minDate smalldatetime, #maxDate smalldatetime, #date smalldatetime;
/* #1 */
SELECT #minDate = MIN(date), #maxDate = MAX(date)
FROM testDates;
SET #date = #minDate - DATEPART(dw, #minDate) + 7;
WHILE #date < #maxDate BEGIN
INSERT INTO #weekends
SELECT #date UNION ALL
SELECT #date + 1;
SET #date = #date + 7;
END;
/* #2 */
INSERT INTO #tmp
SELECT date FROM testDates
UNION
SELECT date FROM #weekends
ORDER BY date;
/* #3 & #4 */
SELECT *
FROM (
SELECT
MIN(date + CASE DATEPART(dw, date) WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END)
AS dateRangeStart,
MAX(date - CASE DATEPART(dw, date) WHEN 1 THEN 2 WHEN 7 THEN 1 ELSE 0 END)
AS dateRangeEnd,
COUNT(CASE WHEN DATEPART(dw, date) NOT IN (1, 7) THEN date END) AS dates,
DATEADD(d, -rownum, date) AS GroupID
FROM #tmp
GROUP BY DATEADD(d, -rownum, date)
) s
WHERE dates > 0;