I get to dust off my VBScript hat and write some classic ASP to query a SQL Server 2000 database.
Here's the scenario:
I have two datetime fields called fieldA and fieldB.
fieldB will never have a year value that's greater than the year of fieldA
It is possible the that two fields will have the same year.
What I want is all records where fieldA >= fieldB, independent of the year. Just pretend that each field is just a month & day.
How can I get this? My knowledge of T-SQL date/time functions is spotty at best.
You may want to use the built in time functions such as DAY and MONTH. e.g.
SELECT * from table where
MONTH(fieldA) > MONTH(fieldB) OR(
MONTH(fieldA) = MONTH(fieldB) AND DAY(fieldA) >= DAY(fieldB))
Selecting all rows where either the fieldA's month is greater or the months are the same and fieldA's day is greater.
select *
from t
where datepart(month,t.fieldA) >= datepart(month,t.fieldB)
or (datepart(month,t.fieldA) = datepart(month,t.fieldB)
and datepart(day,t.fieldA) >= datepart(day,t.fieldB))
If you care about hours, minutes, seconds, you'll need to extend this to cover the cases, although it may be faster to cast to a suitable string, remove the year and compare.
select *
from t
where substring(convert(varchar,t.fieldA,21),5,20)
>= substring(convert(varchar,t.fieldB,21),5,20)
SELECT *
FROM SOME_TABLE
WHERE MONTH(fieldA) > MONTH(fieldB)
OR ( MONTH(fieldA) = MONTH(fieldB) AND DAY(fieldA) >= DAY(fieldB) )
I would approach this from a Julian date perspective, convert each field into the Julian date (number of days after the first of year), then compare those values.
This may or may not produce desired results with respect to leap years.
If you were worried about hours, minutes, seconds, etc., you could adjust the DateDiff functions to calculate the number of hours (or minutes or seconds) since the beginning of the year.
SELECT *
FROM SOME_Table
WHERE DateDiff(d, '1/01/' + Cast(DatePart(yy, fieldA) AS VarChar(5)), fieldA) >=
DateDiff(d, '1/01/' + Cast(DatePart(yy, fieldB) AS VarChar(5)), fieldB)
Temp table for testing
Create table #t (calDate date)
Declare #curDate date = '2010-01-01'
while #curDate < '2021-01-01'
begin
insert into #t values (#curDate)
Set #curDate = dateadd(dd,1,#curDate)
end
Example of any date greater than or equal to today
Declare #testDate date = getdate()
SELECT *
FROM #t
WHERE datediff(dd,dateadd(yy,1900 - year(#testDate),#testDate),dateadd(yy,1900 - year(calDate),calDate)) >= 0
One more example with any day less than today
Declare #testDate date = getdate()
SELECT *
FROM #t
WHERE datediff(dd,dateadd(yy,1900 - year(#testDate),#testDate),dateadd(yy,1900 - year(calDate),calDate)) < 0
Related
I need to find the price for an item for each financial year end date in a date range. In this case the financial year is e.g. 31 March
The table I have for example:
ItemID
Value
DateFrom
DateTo
1
10
'2019/01/01'
'2021/02/28'
1
11
'2021/03/01'
'2021/05/01'
SQL Fiddle
The SQL would thus result in the above table to be:
ItemID
Value
DateFrom
DateTo
1
10
'2019/01/01'
'2019/03/30'
1
10
'2020/03/31'
'2021/02/28'
1
11
'2020/03/01'
'2021/03/30'
1
11
'2020/03/31'
'2021/05/01'
You can solve it, but a prerequisite is the creation of a table called financial_years and filling it with data. This would be the structure of the table:
financial_years(id, DateFrom, DateTo)
Now that you have this table, you can do something like this:
select ItemID, Value, financial_years.DateFrom, financial_years.DateTo
from items
join financial_years
on (items.DateFrom between financial_years.DateFrom and financial_years.DateTo) or
(items.DateTo between financial_years.DateFrom and financial_years.DateTo)
order by financial_years.DateFrom;
The accepted answer is not correct, as it does not split out different parts of the year which have different values.
You also do not need a Year table, although it can be beneficial. You can generate it on the fly using a VALUES table.
Note also a better way to check the intervals overlap, using AND not OR
WITH Years AS (
SELECT
YearStart = DATEFROMPARTS(v.yr, 3, 31),
YearEnd = DATEFROMPARTS(v.yr + 1, 3, 31)
FROM (VALUES
(2015),(2016),(2017),(2018),(2019),(2020),(2021),(2022),(2023),(2024),(2025),(2026),(2027),(2028),(2029),(2030),(2031),(2032),(2033),(2034),(2035),(2036),(2037),(2038),(2039)
) v(yr)
)
SELECT
i.ItemID,
i.Value,
DateFrom = CASE WHEN i.DateFrom > y.YearStart THEN i.DateFrom ELSE y.YearStart END,
DateTo = CASE WHEN i.DateTo > y.YearEnd THEN y.YearEnd ELSE i.DateTo END
FROM items i
JOIN Years y ON i.DateFrom <= y.YearEnd
AND i.DateTo >= y.YearStart;
I am generating a report where I SUM price and cost values per month. I would like to show the same defined time period - 1 year. I'm not clear how to nest this. Would I use a case when statement?
My terrible example.
select month, cost, price from table1 where (define months here in SSRS parameter filter)
I'd like to see:
select month, cost, price, lastyearcost, lastyearprice FROM table1 where (define months here in SSRS parameter filter)
I know that I should be using some variant of GETDATE() -1, but will this include the data range parameters passed when running the report? How do I select the column of cost apply the date filter and then get the result of cost for that period last year?
Hopefully that makes sense?
One way is to use DATEPART() with the correct nesting and pairing.
select
month,
cost,
price,
lastyearcost,
lastyearprice
FROM table1
where
--this is limiting the data to the year passed in, and the previous year
(datepart(year,DateColumn) = #YearParam or datepart(year,DateColumn) = #YearParam-1)
and
--this is limiting the months to the two parameters you pass in as integers
(datepart(month,DateColumn) >= #minMonthParam and datepart(month,DateColumn) <= #maxMonthParam)
TEST DATA
See the DEMO HERE
declare #table1 table (DateColumn date)
insert into #table1 (DateColumn)
values
('1/1/2016'),
('2/1/2016'),
('3/1/2016'),
('4/1/2016'),
('5/1/2016'),
('6/1/2016'),
('7/1/2016'),
('8/1/2016'),
('9/1/2016'),
('10/1/2016'),
('11/1/2016'),
('12/1/2016'),
('1/1/2017'),
('2/1/2017'),
('3/1/2017'),
('4/1/2017'),
('5/1/2017')
declare #YearParam int = 2017
declare #minMonthParam int = 2
declare #maxMonthParam int = 5
select
DateColumn
FROM #table1
where
(datepart(year,DateColumn) = #YearParam or datepart(year,DateColumn) = #YearParam-1)
and
(datepart(month,DateColumn) >= #minMonthParam and datepart(month,DateColumn) <= #maxMonthParam)
I thought it was clear, but doesn't seem so.
This question is about T-SQL (since it's tagged with tsql :) )
So I couldn't find any out-of-the-box solution to calculate my problem.
Let's assume you have these two dateTimes:
DECLARE #start DATETIME = '2011-01-01',
#end DATETIME = '2011-04-15'
The difference of these two datetimes in Days should be quivalent to 105.
The calculation works as follows: For every full month add 30 days, for the rest add the days till the date is achieved.
I could program this, but it would be an enormous SQL-statement, which I find find kinda ugly.
Is there any simple solution for this, like a built-in function or something short?
Thanks in advance.
Does this do the trick?
;with dates as
(
SELECT
CAST ('2011-01-01' AS DATETIME) as start_date
,CAST('2011-04-15' AS DATETIME) as end_date
)
SELECT
start_date
,end_date
,CASE WHEN DATEDIFF(MM,start_date,end_date) = 0 THEN DAY(end_date) - DAY(start_date)
WHEN DAY(start_date) = 1 THEN (30 * (DATEDIFF(MM,start_date,end_date))) + DAY(end_date)
WHEN DAY(start_date) <> 1 THEN 30 * DATEDIFF(MM,start_date,end_date) + (DAY(end_date) - DAY(start_date))
END AS gap_in_days
FROM dates
Short Answer
There's no built in function, but you could pretty easily create your own to handle converting a datetime to an int. From there, the SQL you would have to write would be trivial.
Long Answer
There's no built in function that will do this, probably because every month doesn't have 30 days. :)
You can start with this:
DECLARE #start DATETIME = '2011-01-01',
#end DATETIME = '2011-04-15'
DECLARE #endConverted INT
SELECT #endConverted = DATEPART(month, #end) * 30
+ CASE
WHEN DATEPART(DAY, #end) <= 30
THEN datepart(DAY, #end)
ELSE 30
END
DECLARE #startConverted INT
SELECT #startConverted = DATEPART(MONTH, #start) * 30
+ CASE
WHEN DATEPART(DAY, #start) <= 30
THEN DATEPART(DAY, #start)
ELSE 30
END
SELECT #endConverted - #startConverted
This isn't beautiful SQL, but it works. Note that it returns 104 (because 15 days - 1 day = 14 days), but simple enough to tack on a + 1 to the end of the final select if you want to handle the boundry days differently.
Note that the math here could pretty easily be moved into a function, which would allow you to clean your SQL up. Let's assume you created a function called GetDateTimeAsInt which holds the math; your SQL could be as simple as
DECLARE #start INT = GetDateTimeAsInt('2011-01-01'),
#end INT = GetDateTimeAsInt('2011-04-15')
SELECT #end - #start -- may need to add 1 here
In my testing, this seems to work. It will return the same result as the DATEDIFF function for the date range you specify in your post, but this is because there are 2 days with 31 days and 1 day with 28, so effectively, Jan - April have 30 days each. If you use it with a wider date range, you'll begin to get different results with my code vs. the DATEDIFF function.
Hope this helps.
I use PERIODDIFF. To get the year and the month of the date, I use the function EXTRACT:
SELECT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM NOW()), EXTRACT(YEAR_MONTH FROM time)) AS months FROM your_table;
T-sql
SELECT DATEDIFF(dd, "2011-01-01","2011-04-15")
What I am trying to do is get a result from sql where the dates are in a certain range but its not working correctly, here is my query.
DECLARE #CurrDate DATETIME
SET #CurrDate = GETDATE()
SELECT dbo.ProductDetails.PartnerID
,dbo.ProductDetails.ProductID
,dbo.Products.ProductName
,StartDate
,EndDate
FROM dbo.ProductDetails
INNER JOIN dbo.Products
ON dbo.ProductDetails.ProductID = dbo.Products.ProductID
WHERE CONVERT(VARCHAR(10),StartDate,111) <= #CurrDate
AND CONVERT(VARCHAR(10),EndDate, 111) >= #CurrDate
but when the Enddate = #CurrDate the row does not show, but if i make that date just one day higher it gets displayed. Am i doing anything wrong? Any advice will do, thanks.
GetDate() returns date and time, while your conversion to varchar strips away the time part (I'm suspecting that's all it's actually supposed to do). So you would need to do the same conversion for #CurrDate.
If what you want is to simply consider the date only (ignoring the time part), you could use DATEDIFF instead of converting to varchar (see here); example:
DECLARE #CurrDate DATETIME
SET #CurrDate = GETDATE()
SELECT dbo.ProductDetails.PartnerID, dbo.ProductDetails.ProductID,
dbo.Products.ProductName , StartDate, EndDate
FROM dbo.ProductDetails INNER JOIN
dbo.Products ON dbo.ProductDetails.ProductID = dbo.Products.ProductID
-- where StartDate is on the same day or before CurrDate:
WHERE DATEDIFF(day, StartDate, #CurrDate) >= 0 AND
-- and where EndDate is on the same day or after CurrDate:
DATEDIFF(day, EndDate, #CurrDate) <= 0
If you want only DATE comparison, without time use the
cast(CONVERT(varchar, StartDate, 112) as datetime)
I am quite sure that the comparison takes into account the time as well as the date, in which case if the dates are the same but the current time is greater than the time being compared to you won't get that row as a result.
So, what you need to do is just extract the date part and compare those.
GETDATE() gives you date and time
if yours column have only date
then
CONVERT(VARCHAR(10),StartDate,111) <= #CurrDate
can give you unexpected result
remember
19.12.2011 14:41 > 19.12.2011 00:00
If you are using SQL 2008 or later, and wanting to compare only the date, not the time, you can also do:
Cast(StartDate as Date)
(This avoids having to convert to a string.)
Let's say that I have a range of SQL tables that are named name_YYYY_WW where YYYY = year and WW = week number. If I call upon a function that guides a user defined date to the right table.
If the date entered is "20110101":
SELECT EXTRACT (WEEK FROM DATE '20110101') returns 52 and
SELECT EXTRACT (YEAR FROM DATE '20110101') returns 2011.
While is nothing wrong with these results I want "20110101" to either point to table name_2010_52 or name_2011_01, not name_2011_52 as it does now when I concanate the results to form the query for the table.
Any elegant solutions to this problem?
The function to_char() will allow you to format a date or timestamp to output correct the iso week and iso year.
SELECT to_char('2011-01-01'::date, 'IYYY_IW') as iso_year_week;
will produce:
iso_year_week
---------------
2010_52
(1 row)
You could use a CASE:
WITH sub(field) AS (
SELECT CAST('20110101' AS date) -- just to test
)
SELECT
CASE
WHEN EXTRACT (WEEK FROM field ) > 1 AND EXTRACT (MONTH FROM field) = 1 AND EXTRACT (DAY FROM field) < 3 THEN 1
ELSE
EXTRACT (WEEK FROM field)
END
FROM
sub;