SQL date subtraction - sql

In SQL, I'd like to list all funds whose anniversary is due this year in 2 months time. What is the syntax?

SELECT *
FROM dbo.Funds
WHERE AnniversaryDate <= DATEADD(MONTH, 2, GETDATE())
That should work in SQL Server 2000 and up.
Marc

SELECT DATE_ADD(CURDATE(), INTERVAL 2 MONTH);
This will do it in MySQL. I haven't added the anniversary comparison because I don't know the structure of your tables.

I am not quite sure how to interpret your question, but it seems you are after something like this.
SELECT *
FROM funds
WHERE CURRENT_DATE <= anniversary
AND CURRENT_DATE > DATE_SUB(anniversary, INTERVAL 2 MONTH)
It is possibly not exact as I don't know which flavour of SQL you are using.

I don't know if "fund anniversary" is some kind of a special term in English, so I'm assuming you want to select something like the birthdays which can be stored with a random year, like 1972-01-03.
If it's not so, please correct me.
In SQL Server, you need to generate a list of years and join with it:
WITH q AS
(
SELECT 0 AS num
UNION ALL
SELECT num + 1
FROM q
WHERE num <= 100
)
SELECT *
FROM mytable
WHERE fund_date BETWEEN DATE_ADD(year, -num, GETDATE()) AND DATE_ADD(year, -num, DATE_ADD(month, 2, GETDATE()))
UNION ALL
SELECT *
FROM mytable
WHERE fund_date <= DATEADD(year, -100, GETDATE()
AND DATE_ADD(year, YEAR(GETDATE()) - YEAR(fund_date), fund_date) BETWEEN GETDATE() AND DATE_ADD(month, 2, GETDATE()))
This query is built in two parts:
The first part selects birthdays for each year from the list using the index range scans.
The second part selects birthdays that are more than 100 years old (using a single range scan) and filters them all.
Since birthdates older than 100 years are very unlike, the second part in fact selects almost nothing and does not affects performance too much.

Related

MSSQL Date Analysis

I need to write a query where it looks at a plethora of dates and determines if that date was 3 or more years ago, 2 or more years ago, 1 or more year ago, 6 or more months ago, or less than 6 months ago.
Is there a way to do this without writing in physical dates, so that the analysis can be run again later without needing to change the dates?
I have not started to write the query yet, but I have been trying to map it out first.
You should use case. I would recommend something like:
select (case when datecol < dateadd(year, -3, getdate()) as '3 years ago'
when datecol < dateadd(year, -2, getdate()) as '2 years ago'
. . .
end)
I specifically do not recommend using datediff(). It is counterintuitive because it counts the number of "boundaries" between two dates. So, 2016-12-31 and 2017-01-01 are one year apart.
You can use the DATEDIFF function to calculate the number of months, days, years, etc. between two dates, e.g.
select datediff(day, '2016-01-01', '2017-01-01')
returns 366, because 2016 was a leap year
To get the current date, use the GETDATE() function.
I tend to use a generic Tier Table for several reasons.
Logic is moved from code.
Alternate Tiers may be deployed depending on your audience.
Most importantly, things change.
The following will generate a series of dates, and then summarize by the desired tier. I should add, this is a simplified example
Example
-- Create Sample Tier Table
Declare #Tier table (Tier_Group varchar(50),Tier_Seq int,Tier_Title varchar(50),Tier_R1 int,Tier_R2 int)
Insert into #Tier values
('MyAgeTier',1,'+3 Years' ,36,999999)
,('MyAgeTier',2,'2 - 3 Years' ,24,36)
,('MyAgeTier',3,'1 - 2 Years' ,12,24)
,('MyAgeTier',4,'6 Mths - 1 Year',6 ,12)
,('MyAgeTier',5,'<6 Mths' ,0 ,6)
,('MyAgeTier',6,'Total ' ,0 ,999999)
Select Tier_Title
,Dates = count(*)
,MinDate = min(D)
,MaxDate = max(D)
From #Tier A
Join (
-- Your Actual Source
Select Top (DateDiff(DAY,'2010-01-01','2017-07-31')+1)
D=cast(DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'2010-01-01') as date)
From master..spt_values n1,master..spt_values n2
) B
On Tier_Group = 'MyAgeTier' and DateDiff(MONTH,D,GetDate()) between Tier_R1 and Tier_R2-1
Group By Tier_Title,Tier_R1
Order by Tier_R1 Desc
Returns (this example)

Looking for a birthdate Range in SQL

I am looking for a birthdate range of march 21 to April 20, and the year doesn't matter. and it seems that when i search other months and date are coming out.
select * from AllStudentInfo Where
((MONTH(birthDate) >= 3 and day(birthDate) >= 21)
OR
(MONTH(birthDate) <= 4 and day(birthDate) <= 20))
AND
(BGRStatus = 'S' OR BGRStatus = 'SL')
Chances are that you want to discover up & coming dates. In any case, you can create a virtual date as follows:
SELECT DATEFROMPARTS (2017,month(birthDate),day(birthDate) as birthday
FROM AllStudentInfo
In this case, you can use:
SELECT *
FROM AllStudentInfo
WHERE DATEFROMPARTS (2017,month(birthDate),day(birthDate)
BETWEEN '2017-03-21' AND '2017-04-20';
The year 2017 is arbitrary. The point is that the dates in the BETWEEN clause are in the same year.
Using more modern techniques, you can combine it as follows:
WITH cte AS(
SELECT *,DATEFROMPARTS (2017,month(birthDate),day(birthDate) as birthDay
FROM AllStudentInfo
)
SELECT * FROM cte WHERE birthDay BETWEEN '2017-03-21' AND '2017-04-20';
The cte is a Common Table Expression which is an alternative to using a sub query.
Here is an alternative which is closer to the spirit of the question. You can use the format function to generate an expression which is purely month & day:
format(birthDate,'MM-dd')
The MM is MSSQL’s way of saying the 2-digit month number, and dd is the 2-digit day of the month.
This way you can use:
format(birthDate,'MM-dd') BETWEEN '03-21' AND '04-20'
Again as a CTE:
WITH cte AS(
SELECT *,format(birthDate,'MM-dd') as birthDay
FROM AllStudentInfo
)
SELECT * FROM cte WHERE birthDay BETWEEN '03-21' AND '04-20';
You should get the same results, but the year is completely ignored.
Switch your statement to AND
Like so
select * from AllStudentInfo Where
((MONTH(birthDate) >= 3 and day(birthDate) >= 21)
AND --Make the change here
(MONTH(birthDate) <= 4 and day(birthDate) <= 20))
AND
(BGRStatus = 'S' OR BGRStatus = 'SL')
Using OR you are querying anything that is in that date range OR that month range. And therefore, you would get results from other every months.

SQL Server: How to get rows where the date is older than X years?

Question
I would like to pull all assets from a database which is a certain amount of years old, in this case years. Is this statement correct?
Background
The database is called AssetRegister
The table is called dbo.Assets
the column is called AcquiredDate
Statement so far
SELECT * FROM dbo.Assets WHERE AcquiredDate < '2008-01-01'
SELECT * FROM dbo.Assets
WHERE DATEDIFF(YEAR, AcquiredDate, GetDate()) >= 8
For an performance optimized query look at #Horaciuxs answer.
The answer by #Juergen bring the right results:
SELECT * FROM dbo.Assets
WHERE DATEDIFF(YEAR, AcquiredDate, GetDate()) >= 8
But, the SQL optimizer can't use an index on AcquiredDate, even if one exists. It will literally have to evaluate this function for every row of the table.
For big tables is recommended to use:
DECLARE #limitDate Date
SELECT #limitDate=DATEADD(year,-8,GETDATE()) --Calculate limit date 8 year before now.
SELECT * FROM dbo.Assets
WHERE AcquiredDate <= #limitDate
Or simply:
SELECT * FROM dbo.Assets
WHERE AcquiredDate <= DATEADD(year,-8,GETDATE())
SELECT * FROM dbo.Assets WHERE AcquiredDate >= '2006-01-01'
or
SELECT * FROM dbo.Assets WHERE AcquiredDate >= (year(getdate()) - 8)
CAREFUL - DATEDIFF only looks at year. Consider this query:
SELECT DATEDIFF(year,'2012-10-01','2022-06-01')
clearly the date is 9 years 8 months old but the query returns 10. So if you are deleting records >= 10 years old based on this kind of query you're in a fix....
Better to use something like this as highlighted by #Horaciux
WHERE dt > DATEADD(YEAR,-10,'20220501')
SELECT * FROM dbo.Assets
WHERE YEAR(AcquiredDate) < 2008
The solution posted by juergen d and Ankit are better, but if you want to compare the Year, you can use the YEAR function.

How to create multiple rows (as date column) into CTE? (out of thin air)

I have a stored-procedure and I want to add date column to the tables. Then I wonder how do I get MS-SQL to generate multiple rows on the fly using CTE. Say I have this..
(GETDATE() - 548) --(365 days --> 12 months, 548 days --> 18 months...
How do you guys whip up a query that would create 548 rows and have the date column as row
#1 - '12/17/2012'
#2 - '12/16/2012'
#3 - '12/15/2012'
etc. all the way to row #548? All of that into a CTE?
Thanks...
Unless I am missing something, it sounds like you want the following:
;with dates(value) as
(
select DATEADD(d, -548, cast(getdate() as DATE))
union all
select DATEADD(D, 1, value)
from dates
where DATEADD(D, 1, value) <= cast(getdate() as DATE)
)
select *
from dates
order by value desc
OPTION (MAXRECURSION 1000)
See SQL Fiddle with Demo

Return records between now and previous week

I have the following SQL query in oracle:
SELECT * FROM
(
SELECT s.singleid,s.titel,a.naam,s.taal,SUM(b.aantal) AS "AANTAL VERKOCHT"
FROM singles s
JOIN artiesten a on a.artiestid = s.artiestid
JOIN bestellingen b on b.singleid = s.singleid
GROUP BY s.singleid,s.titel,a.naam,s.taal,b.datum
ORDER BY sum(b.aantal) DESC
)
WHERE ROWNUM <= 5
This works, but I need to return only the records where b.datum is between the time now, and previous week.
How do I do this?
You should be able to add a BETWEEN clause to your where:
WHERE b.datum between SYSDATE - 6 AND SYSDATE
You would want to extract from the BESTELLINGEN table only those rows where [datum] is greater than or equal to (today at midnight minus 7 days) and less than or equal to 'now' (or less than tomorrow at midnight, according to your requirement). I would probably make this set of Bestellingen rows an inline view and join the other tables to it, and then do your grouping.
In SQL Server the syntax is:
AND (b.datum > DATEADD(week, -1, GetDate()) and b.datum < GetDate())
I would assume the syntax is the same, or very similar, in Oracle.