TSQL select columns from rollup data columns - sql

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.

Related

SQL - Correct dates not being presented in result set with date ranges in WHERE clause

Result set without the where clause shows there is data from the 30th September, when using the below where clause the 30th data doesn't come through in the result set (see images below for result sets) - my main aim is to show data from between the first day of the previous month and the last day of the previous month - Any help is much appreciated:
SQL Query
DECLARE #date1 datetime
DECLARE #date2 datetime
SET #date1 = getdate()
SET #date2 = getdate()-15 -- reason for less 15 days is because this will only be run within the first 15 days
-- of the current month, it will enable me to get the last day of the previous
-- month even if I run the SQL in the next month.
SELECT
A.TA_SEQ as 'WO No',
A.TA_DUE_DATEUTC
FROM
F_TASKS A WITH (NOLOCK)
INNER JOIN FINFILE B WITH (NOLOCK) ON A.TA_FKEY_IN_SEQ = B.IN_SEQ
INNER JOIN InstructionSetGroups C WITH (NOLOCK) ON B.InstructionSetGroupId = C.InstructionSetGroupId
WHERE
A.TA_TASK_DESC = 'BREAKDOWN' AND
A.TA_STATUS IN ('ACTIVE', 'ASSIGNED', 'COMPLETE', 'HISTORY') AND
A.TA_DUE_DATE >= DATEADD(DAY, 1, EOMONTH(#date1, -2)) AND
A.TA_DUE_DATE <= EOMONTH(#date2)
ORDER BY
A.TA_DUE_DATE desc
Result Sets
Result set using date ranges in where clause:
Result set without using date ranges in where clause - as you can see there's lots of data from the 30th too which isn't being captured
EOMONTH is terribly ambiguous, because it returns the last day of the month at midnight. I talk about why I don't like this function in Will you use EOMONTH()?
If you want the whole previous month, there are easier and safer ways than assuming the code will run within the first 15 days:
DECLARE #ThisMonth date = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
SELECT ... FROM ...
WHERE TA_DUE_DATE >= DATEADD(MONTH, -1, #ThisMonth)
AND TA_DUE_DATE < #ThisMonth;
I explain why DATEFROMPARTS is best suited for this kind of work in Simplify Date Period Calculations in SQL Server.
With the rest, where do we begin?
getdate()-15 is lazy, unintuitive shorthand that will break in other contexts. Please use DATEADD(DAY, -15, GETDATE()) and see Bad Habits to Kick : Using shorthand with date/time operations.
You can declare/set in a more readable way than an explicit line for each declare and again for each set:
DECLARE #date1 datetime = GETDATE(),
#date2 datetime = DATEADD(DAY, -15, GETDATE());
as 'WO No' - please use AS [Wo No] or AS "Wo No" - string delimiters make those aliases look like, well, strings, and some forms of that syntax are deprecated.
please always specify the schema and never use meaningless aliases like A, B, C.
>= AND <= is the same as BETWEEN. I talk about why BETWEEN is always a terrible idea for date ranges in What do BETWEEN and the devil have in common?
Lots of resources on NOLOCK in I'm using NOLOCK; is that bad?
General date tips in Dating responsibly
I would take a different approach to handling your date values. That whole -15 stuff is bizarre and will cause issues on days towards the end of the month. Instead using a single variable is sufficient if you understand some date math.
DECLARE #date1 date --use the data datatype here because time is important in this case
SET #date1 = getdate()
--this is to show you the values and can be removed.
select BeginningOfLastMonth = dateadd(month, datediff(month, 0, #date1) - 1, 0)
, BeginningOfCurrentMonth = dateadd(month, datediff(month, 0, #date1), 0)
--Your select statement here
where A.TA_DUE_DATE >= dateadd(month, datediff(month, 0, #date1) - 1, 0)
A.TA_DUE_DATE <= dateadd(month, datediff(month, 0, #date1), 0) --ANY datetime value less than this is before the end of last month

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

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

nth day to nth month in SQL Server

I need to get date between two date range. That is nth day of nth month.
For example, I need to know 23rd day of every 2nd month between January 1, 2015 to December 30, 2015.
I need the query in T-SQL for SQL Server
You should use recursive query in MSSQL.
Here the first WITH DT is a table where you set up conditions:
WITH DT AS
(
SELECT CAST('January 1, 2015' as datetime) as dStart,
CAST('December 30, 2015' as datetime) as dFinish,
31 as nDay,
2 as nMonth
),
T AS
(
SELECT DATEADD(DAY,nDay-1,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DStart), 0)
) as d,0 as MonthNumber
FROM DT
UNION ALL
SELECT DATEADD(DAY,nDay-1,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DStart)
+T.MonthNumber+nMonth,0)
)as d, T.MonthNumber+nMonth as MonthNumber
FROM T,DT
WHERE DATEADD(DAY,nDay-1,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DStart)
+T.MonthNumber+nMonth,0)
)<=DT.dFinish
)
SELECT d FROM T,DT WHERE DAY(d)=DT.nDay
SQLFiddle demo
Is this what you are trying to achieve?
DECLARE #startDate datetime
DECLARE #endDate datetime
DECLARE #monthToFind INT
DECLARE #dayToFind INT
SET #startDate = '01/01/2015'
SET #endDate = '12/31/2015'
SET #monthToFind = 2
SET #dayToFind = 20
IF MONTH(#startDate) + (#monthToFind - 1) BETWEEN MONTH(#startDate) AND MONTH(#endDate)
AND YEAR(#startDate) = YEAR(#endDate)
BEGIN
DECLARE #setTheDate datetime
SET #setTheDate = CAST(MONTH(#startDate) + (#monthToFind - 1) AS varchar) + '/' + CAST(#dayToFind AS varchar) + '/' + CAST(YEAR(#startDate) AS varchar)
SELECT DATENAME(DW,#setTheDate)
END
This is clearly homework, and the point of homework is to learn how things work and to solve problems, not to get others to do it for you. So - pointers for doing this properly, rather than an answer to copy and paste.
Numbers / tally tables are ideal for this sort of thing. Create a function that returns a list of sequential integers in a range. More general than a calendar table, and you can use it to derive a calendar table later if you need one.
When you've got that, DATEDIFF will give you the number of days between two dates. Use that to work out the size of your range, DATEADD to increment your date and possibly DATEPART to check that a date is the nth day of the month.
Mess about with those bits for a little while and you'll work it out.

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)

Calculating in SQL the first working day of a given month

I have to calculate all the invoices which have been paid in the first 'N' days of a month. I have two tables
. INVOICE: it has the invoice information. The only field which does matter is called 'datePayment'
. HOLYDAYS: It is a one column table. Entries at this table are of the form "2009-01-01",
2009-05-01" and so on.
I should consider also Saturdays and Sundays
(this might be not a problem because I could insert those days at the Hollidays table in order to consider them as hollidays if neccesary)
The problem is to calculate which is the 'payment limit'.
select count(*) from invoice
where datePayment < PAYMENTLIMIT
My question is how to calculate this PAYMENTLIMIT. Where PAYMENTLIMIT is 'the fifth working day of every month'.
The query should be run under Mysql and Oracle therefore standard SQL should be used.
Any hint?
EDIT
In order to be consistent with the title of the question the pseudo-query should the read as follows:
select count(*) from invoice
where datePayment < FIRST_WORKING_DAY + N
then the question can be reduced to calculate the FIRST_WORKING_DAY of every month.
You could look for the first date in a month, where the date is not in the holiday table and the date is not a weekend:
select min(datePayment), datepart(mm, datePayment)
from invoices
where datepart(dw, datePayment) not in (1,7) --day of week
and not exists (select holiday from holidays where holiday = datePayment)
group by datepart(mm, datePayment) --monthnr
Something like this might work:
create function dbo.GetFirstWorkdayOfMonth(#Year INT, #Month INT)
returns DATETIME
as begin
declare #firstOfMonth VARCHAR(20)
SET #firstOfMonth = CAST(#Year AS VARCHAR(4)) + '-' + CAST(#Month AS VARCHAR) + '-01'
declare #currDate DATETIME
set #currDate = CAST(#firstOfMonth as DATETIME)
declare #weekday INT
set #weekday = DATEPART(weekday, #currdate)
-- 7 = saturday, 1 = sunday
while #weekday = 1 OR #weekday = 7
begin
set #currDate = DATEADD(DAY, 1, #currDate)
set #weekday = DATEPART(weekday, #currdate)
end
return #currdate
end
I'm not 100% sure about whether the "weekday" numbers are fixed or might depend on your locale on your SQL Server. Check it out!
Marc
Rather than a Holidays table of days to exclude, we use the calendar table approach: one row for every day the application will ever need (thirty years spans a modest 11K rows). So not only does it have an is_weekday column, it has other things relevant to the enterprise e.g. julianized_date. This way, every possible date would have a ready-prepared value for first_working_day_this_month and finding it involves a simple lookup (which SQL products tend to be optimized for!) rather than 'calculating' it each time on the fly.
We have dates table in our application (filled with all dates and date parts for some tens of years), what allows various "missing" date manipulations, like (in pseudo-sql):
select min(ourdates.datevalue)
from ourdates
where ourdates.year=<given year> and ourdates.month=<given month>
and ourdates.isworkday
and not exists (
select * from holidays
where holidays.datevalue=ourdates.datevalue
)
Ok, at a first stab, you could put the following code into a UDF and pass in the Year and Month as variables. It can then return TestDate which is the first working day of the month.
DECLARE #Month INT
DECLARE #Year INT
SELECT #Month = 5
SELECT #Year = 2009
DECLARE #FirstDate DATETIME
SELECT #FirstDate = CONVERT(varchar(4), #Year) + '-' + CONVERT(varchar(2), #Month) + '-' + '01 00:00:00.000'
DROP TABLE #HOLIDAYS
CREATE TABLE #HOLIDAYS (HOLIDAY DateTime)
INSERT INTO #HOLIDAYS VALUES('2009-01-01 00:00:00.000')
INSERT INTO #HOLIDAYS VALUES('2009-05-01 00:00:00.000')
DECLARE #DateFound BIT
SELECT #DateFound = 0
WHILE(#DateFound = 0)
BEGIN
IF(
DATEPART(dw, #FirstDate) = 1
OR
DATEPART(dw, #FirstDate) = 1
OR
EXISTS(SELECT * FROM #HOLIDAYS WHERE HOLIDAY = #FirstDate)
)
BEGIN
SET #FirstDate = DATEADD(dd, 1, #FirstDate)
END
ELSE
BEGIN
SET #DateFound = 1
END
END
SELECT #FirstDate
The things I don`t like with this solution though are, if your holidays table contains all days of the month there will be an infinite loop. (You could check the loop is still looking at the right month) It relies upon the dates being equal, eg all at time 00:00:00. Finally, the way I calculate the 1st of the month past in using string concatenation was a short cut. There are much better ways of finding the actual first day of the month.
Gets the first N working days of each month of year 2009:
select * from invoices as x
where
datePayment between '2009-01-01' and '2009-12-31'
and exists
(
select
1
from invoices
where
-- exclude holidays and sunday saturday...
(
datepart(dw, datePayment) not in (1,7) -- day of week
/*
-- Postgresql and Oracle have programmer-friendly IN clause
and
(datepart(yyyy,datePayment), datepart(mm,datePayment))
not in (select hyear, hday from holidays)
*/
-- this is the MSSQL equivalent of programmer-friendly IN
and
not exists
(
select * from holidays
where
hyear = datepart(yyyy,datePayment)
and hmonth = datepart(mm, datePayment)
)
)
-- ...exclude holidays and sunday saturday
-- get the month of x datePayment
and
(datepart(yyyy, datePayment) = datepart(yyyy, x.datePayment)
and datepart(mm, datePayment) = datepart(mm, x.datePayment))
group by
datepart(yyyy, datePayment), datepart(mm, datePayment)
having
x.datePayment < MIN(datePayment) + #N -- up to N working days
)
Returns the first Monday of the current month
SELECT DATEADD(
WEEK,
DATEDIFF( --x weeks between 1900-01-01 (Monday) and inner result
WEEK,
0, --1900-01-01
DATEADD( --inner result
DAY,
6 - DATEPART(DAY, GETDATE()),
GETDATE()
)
),
0 --1900-01-01 (Monday)
)
SELECT DATEADD(day, DATEDIFF (day, 0, DATEADD (month, DATEDIFF (month, 0, GETDATE()), 0) -1)/7*7 + 7, 0);
select if(weekday('yyyy-mm-01') < 5,'yyyy-mm-01',if(weekday('yyyy-mm-02') < 5,'yyyy-mm-02','yyyy-mm-03'))
Saturdays and Sundays are 5, 6 so you only need two checks to get the first working day