Conditional SQL Query to fetch last date of the quarter on the basis of few condition - sql

Basically a report is needed once a quarter, and the analysis date(any date) is the last date of the preceding quarter. So if we are loading on 10 July, “Analysis Date” would be 30 June (of the same year). HOWEVER - it is possible that the users will want to load the data early, in order to see what may need to be addressed by the end of the quarter. In that case they may want to load it on 20 June, and “Analysis Date” will still be 30 June.
How do i write the query for these two conditions, considering there will be only one month in advance for which this specific constraint is needed.

This should put you on the right track. If you have a version of SQL Server older than 2012 you will need to use something instead of EOMONTH
CREATE FUNCTION dbo.GetAnalysisDate(#date DATE)
RETURNS DATE
AS
BEGIN
IF MONTH(#date) % 3 = 0 RETURN EOMONTH(#date)
-- else
RETURN DATEADD(dd, -1, DATEADD(qq, DATEDIFF(qq, 0, #date), 0))
END
GO
-- tests
-- these should return 20181231
SELECT dbo.GetAnalysisDate('20190101')
SELECT dbo.GetAnalysisDate('20190120')
SELECT dbo.GetAnalysisDate('20190220')
-- these should return 20190331
SELECT dbo.GetAnalysisDate('20190320')
SELECT dbo.GetAnalysisDate('20190401')
SELECT dbo.GetAnalysisDate('20190420')

Related

Calculate time from begin date in SQL

I'm trying to tally up the number of unique ID accounts that are active for more than a year as well as include how long each one is active. The problem with the code I have is it's not including accounts that are currently active (ones that don't have an end date). For example, if the begin date was May 01 2018 but has no end date since it's currently active, it should also be included in this query. Here's what I have so far..
SELECT UniqueID,
DATEDIFF(yy, Begin_Date,End_Date) as timeactive
FROM TABLE
WHERE DATEDIFF(yy, Begin_Date,End_Date) > 1
I want my output to look like...
Unique ID Time active
F000012 2.5
F000031 1.5
This is what ended up working:
SELECT UniqueID,
CAST(ROUND(DATEDIFF(day, Begin_Date, COALESCE(End_Date, getdate()))/365.0, 1, 0) AS NUMERIC (10,1)) as timeactive
FROM TABLE
WHERE DATEDIFF(day, Begin_Date, COALESCE (End_Date, getdate())) >= 365
If the EndDate is null then the output of the DateDiff function will be null, and any null compared to anything (even another null) is a result of null (usually then interpreted as false)
I suggest you use COALESCE to convert your end date to today if it is null:
SELECT
UniqueID,
DATEDIFF(yy, Begin_Date,COALESCE(End_Date, GetUtcDate()) as timeactive
FROM TABLE
WHERE DATEDIFF(yy, Begin_Date,COALESCE(End_Date, GetUtcDate()) > 1
You should bear in mind that the DATEDIFF function as used here, in SQLserver does NOT return the amount of time that has passed between the two dates. It returns the number of times the named interval has changed between the two dates
For example, DATEDIFF(year, 2000-01-01, 2000-12-31 23:59:59) will return 0 because these are both year 2000 even though they're just one second short of being a year apart. If you do DATEDIFF(year, 2000-12-31 23:59:59, 2001-01-01 00:00:01) even though these dates are only two seconds apart datediff will report them as 1 year apart because the year number has changed from 2000 to 2001.
DATEDIFF counts up by one every time the clock rolls past an interval change and in this case the interval is Year
To get your dates to report as 1.5 years etc you should consider to datediff by a smaller interval and divide, such as asking for the DAYS diff between two dates and then dividing by 365.25- the average number of days in a year. The smaller the interval you ask datediff for the more accurate the result will be but it'll never be 100%. If you're only after results to one decimal place of a year then days will be accurate enough
To get 1 decimal place, cast to a numeric with 1 DP:
SELECT
UniqueID,
CAST(DATEDIFF(day, Begin_Date,COALESCE(End_Date, GetUtcDate())/365.25 AS NUMERIC(5,1)) as timeactive
FROM TABLE
WHERE DATEDIFF(day, Begin_Date,COALESCE(End_Date, GetUtcDate()) >= 365
If you want time active as fractional years, then you need to use a smaller unit of time and divide. For instance:
SELECT UniqueID,
DATEDIFF(month, Begin_Date, COALESCE(End_Date, GETDATE())) / 12.0 as timeactive
FROM TABLE
WHERE Begin_Date < DATEADD(YEAR, -1, COALESCE(End_Date, GETDATE()))
Note the change in the WHERE clause. DATEDIFF() counts the number of year boundaries between dates. So the difference in years between 2019-01-01 and 2020-12-31 is the same as the difference between 2019-12-31 and 2020-01-01 -- exactly 1.
Consider:
SELECT
UniqueID,
DATEDIFF(yy, Begin_Date, COALESCE(End_Date, getdate()) as timeactive
FROM TABLE
WHERE DATEDIFF(yy, Begin_Date, COALESCE(End_Date, getdate()) > 1
This works by using the current date as default value for empty End_Dates. So this allows records with empty end date if their start date is more than one year ago.

Data Preparation End OF Every Month - Moving Over 12 Months

I have data prep procedure in SQL. I want to have data preparation at the end of every month
Say I want the procedure run on last day of month e.g. on 31 January 2020 it should prep data from 1 January to 31 January.
So it's kind of moving window over all months of the year. Because I need data for evaluation at the end of each month.
I tried this, however, this does not give automation. Its sort of manual running end of every month
select '2020-10-01' as beginDate_AnalysisWindow
, '2020 -01-31' as endDate_AnalysisWindow
into #AnalysisWindow --create temporary table #AnalysisWindow
I also tried the following, however, I’m not sure if it does for the whole month or just one day?
SELECT START_OF_MONTH_DATE AS beginDate_AnalysisWindow
,END_OF_MONTH_DATE AS endDate_AnalysisWindow
INTO #AnalysisWindow
FROM [dbo].[Date] WITH (NOLOCK)
WHERE DATE = DATEADD(dd, - 1, CAST(GETDATE() AS DATE))
Could someone pls help me/give me some suggestions.
Thanks in advance
If you want the last day of the current month, use eomonth():
WHERE DATE = CONVERT(date, EOMONTH(GETDATE()))
Note: This assumes that the date column has no time component. If that is the case:
WHERE CONVERT(date, DATE) = CONVERT(date, EOMONTH(GETDATE()))
SQL Server will still use an index (if available) even for this type of conversion.
EDIT:
To get the current months data, one method is:
WHERE DATE <= CONVERT(date, EOMONTH(GETDATE())) AND
DATE > CONVERT(date, EOMONTH(GETDATE(), -1))
The second argument to EOMONTH() is a months offset.
You can also try:
where getdate() <= EOMONTH(GETDATE()) AND
getdate() > DATEADD(DAY, 1, EOMONTH(GETDATE(), -1))
Instead of getdate(), you can use your date column.

Get date for nth day of week in nth week of month

I have a column with values like '3rd-Wednesday', '2nd-Tuesday', 'Every-Thursday'.
I'd like to create a column that reads those strings, and determines if that date has already come this month, and if it has, then return that date of next month. If it has not passed yet for this month, then it would return the date for this month.
Expected results (on 4/22/16) from the above would be: '05-18-2016', '05-10-2016', '04-28-2016'.
I'd prefer to do it mathematically and avoid creating a calendar table if possible.
Thanks.
Partial answer, which is by no means bug free.
This doesn't cater for 'Every-' entries, but hopefully will give you some inspiration. I'm sure there are plenty of test cases this will fail on, and you might be better off writing a stored proc.
I did try to do this by calculating the day name and day number of the first day of the month, then calculating the next wanted day and applying an offset, but it got messy. I know you said no date table but the CTE simplifies things.
How it works
A CTE creates a calendar for the current month of date and dayname. Some rather suspect parsing code pulls the day name from the test data and joins to the CTE. The where clause filters to dates greater than the Nth occurrence, and the select adds 4 weeks if the date has passed. Or at least that's the theory :)
I'm using DATEFROMPARTS to simplify the code, which is a SQL 2012 function - there are alternatives on SO for 2008.
SELECT * INTO #TEST FROM (VALUES ('3rd-Wednesday'), ('2nd-Tuesday'), ('4th-Monday')) A(Value)
SET DATEFIRST 1
;WITH DAYS AS (
SELECT
CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number) AS DATE) Date,
DATENAME(WEEKDAY, DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),N.Number)) DayName
FROM master..spt_values N WHERE N.type = 'P' AND N.number BETWEEN 0 AND 31
)
SELECT
T.Value,
CASE WHEN MIN(D.Date) < GETDATE() THEN DATEADD(WEEK, 4, MIN(D.DATE)) ELSE MIN(D.DATE) END Date
FROM #TEST T
JOIN DAYS D ON REVERSE(SUBSTRING(REVERSE(T.VALUE), 1, CHARINDEX('-', REVERSE(T.VALUE)) -1)) = D.DayName
WHERE D.Date >=
DATEFROMPARTS(
YEAR(GETDATE()),
MONTH(GETDATE()),
1+ 7*(CAST(SUBSTRING(T.Value, 1,1) AS INT) -1)
)
GROUP BY T.Value
Value Date
------------- ----------
2nd-Tuesday 2016-05-10
3rd-Wednesday 2016-05-18
4th-Monday 2016-04-25

Createing a report using financial periods

I have created a report for management that will total everything up by month with in a date range. Management has now decided that rather than by month they would like to go by period. We have 13 periods in a year each is 28 days except the last one is 29 or 30 depending on if its a leap year. The beginning of the first period is always 1-1-YYYY. So now I will need to figure out what the beginning and end of each period is and total up each period. I am not really sure how to do this since every year the dates will change and they may want to look at periods from the previous year through the current period. The code and results I am currently using are enclosed
SELECT
DATEADD(MONTH, DATEDIFF(MONTH, 0, finspecteddate), 0) AS 'Date'
,COUNT(*) AS Lots
,sum(flotSize) as 'Lot Size'
,sum(LReject) 'Lots Rejected'
,sum(fnumreject) as Rejected
,sum(fsampleSize) as 'Sample Size'
,sum(BDueDate) as 'Before Due Date'
FROM
ReportData
WHERE
finspecteddate >= '01-01-2014'
AND finspecteddate <= '10-15-2014'
GROUP BY
DATEADD(MONTH, DATEDIFF(MONTH, 0, finspecteddate), 0)
ORDER BY
date
Modify the following queries to suit your needs:
;WITH Period AS (
SELECT 1 AS ReportingPeriod,
CAST('2013-01-01' AS datetime) AS PeriodStartDate,
CAST('2013-01-28' AS datetime) AS PeriodEndDate
UNION ALL
SELECT CASE
WHEN p.ReportingPeriod = 13 THEN 1
ELSE p.ReportingPeriod + 1
END,
CASE
WHEN p.ReportingPeriod = 13 THEN DATEADD(YEAR,YEAR(p.PeriodStartDate)-1899,'1900-01-01')
ELSE DATEADD(DAY,28,p.PeriodStartDate)
END,
CASE
WHEN p.ReportingPeriod = 12 THEN DATEADD(YEAR,YEAR(p.PeriodStartDate)-1900,'1900-12-31')
ELSE DATEADD(DAY,28,p.PeriodEndDate)
END
FROM Period p
WHERE p.PeriodStartDate < '2017-12-03'
)
SELECT
P.PeriodStartDate
,P.PeriodEndDate
,COUNT(*) AS Lots
,sum(flotSize) as 'Lot Size'
,sum(LReject) 'Lots Rejected'
,sum(fnumreject) as Rejected
,sum(fsampleSize) as 'Sample Size'
,sum(BDueDate) as 'Before Due Date'
FROM
ReportData R
INNER JOIN Period P ON R.finspecteddate >= P.PeriodStartDate AND R.finspecteddate <= P.PeriodEndDate
WHERE
finspecteddate >= '01-01-2014'
AND finspecteddate <= '10-15-2014'
GROUP BY
P.PeriodStartDate
,P.PeriodEndDate
ORDER BY
P.PeriodStartDate
It uses a recursive CTE to build a period table, which is then joined to ReportData to aggregate asccording to your requirements. I don't have SQL Server 2005 to test it on. It works with 2008. Post a SQL Fiddle if you need help in 2005.
If you haven't got one, create a period calendar table with a year, period number, start date and end date columns. Then when you need to refer to periods, you can refer to the table. When they change the definition of what a period is, you can change the table. When they decide that February 29 doesn't count as one of the 28 days, you can change the table. When they decide to use the first Monday instead of the first Thursday as the start of the year, you just change the table. And best of all, changing how next year works won't change how last year works.
Then you just join to the table to determine which period you're in.

SQL server - Select all items with a date on the previous month

I have a table in my SQL Server database called "items" which has a column called "dateFinished".
I have a script that will run on the 1st day of each month which needs to select all items that finished in the previous month.
So, for example, on the 1st February it will need to select all items where the dateFinished is greater than or equal to 00:00 on the 1st of January and less than 00:00 on 1st February.
it also needs to work across new years (e.g. DEC - JAN).
Any ideas?
Select *
from items
where datefinished >= dateadd(m, datediff(m, 0, GETDATE()) - 1, 0)
AND datefinished < dateadd(m, datediff(m, 0, GETDATE()), 0)
You could get a day of the previous month with dateadd(m,-1,getdate()). Then, filter on the year and month of that date in a where clause, like:
select *
from items
where datepart(yy,dateFinished) = datepart(yy,dateadd(m,-1,getdate()))
and datepart(m,dateFinished) = datepart(m,dateadd(m,-1,getdate()))
This should work across years, and also if the query is run on a later day than the first of the month.
Simple just use what I just used: DATEDIFF(mm,dateFinished,GETDATE()) = 1
SELECT *
FROM items
WHERE DATEDIFF(mm,dateFinished,GETDATE()) = 1
I would start by checking out the DATEADD function
http://msdn.microsoft.com/en-us/library/ms186819.aspx