How to format a wildcard query for dates in MS SQL? - sql

I have a certain day range, let's say 8-01 to 8-08. I also have a database that has historical data from 1975-2014. Dates are formatted in yyyy-mm-dd form. So for example, today is 2014-08-06. How can I get historical records from 8-01 to 8-08 for all years? Keep in mind my date range might not span nicely across the same month. Another example would be 7/31 to 8/07.
Basically, I want to be able to do BETWEEN(%-8-01 AND %-8-08) where % is a wildcard. However, wildcards seem incompatible with date objects in MS SQL. Do I need to convert the dates to strings? What is the most efficient way of getting a generic month-day range independent of year?
Thanks.

This is a different approach than my other answer. It will make all dates have a year of 1900. Then you only need to take whatever is between your selected dates.
SELECT *
FROM Table1
WHERE DATEADD
(
year,
-DATEDIFF(year,'19000101',dateField),
dateField
) BETWEEN'19000801' AND'19000818'

You can extract the month and date part and compare those:
where
month(<yourdate>) = 8
and day(<yourdate>) between 1 and 8
Be aware that if you have an index on this column, you won't be using it this way.

The wildcard operator cannot be used in a BETWEEN statement. It's really only for LIKE statements. If you want to be able to modify your year, and that's it, you should pass it in as a parameter. The BETWEEN statement will take that with no problem.

DECLARE #StartMonth INT
DECLARE #Month INT
DECLARE #EndMonth INT
DECLARE #StartDay INT
DECLARE #Day INT
DECLARE #EndDay INT
SET #StartMonth = 8
SET #EndMonth = 8
SET #StartDay = 1
SET #EndDay = 8
IF #EndMonth > #StartMonth
BEGIN
#Month = #EndMonth
#StartMonth = #EndMonth
#EndMonth= #Month
END
IF #EndDay > #StartDay
BEGIN
#Day= #EndDay
#StartDay = #EndDay
#EndDay = #Day
END
SELECT *
FROM TABLE
WHERE MONTH(dateField) BETWEEN #StartMonthAND #EndMonth
AND DAY(dateField) BETWEEN #StartDay AND #EndDay

Related

Direct access slower than using functions?

I was conducting some performance testing and have discovered something quite strange. I have set up a short script to time how long it takes to perform certain actions.
declare #date date
declare #someint int
declare #start datetime
declare #ended datetime
set #date = GETDATE()
DECLARE #count INT
SET #count = 0
set #start = GETDATE()
WHILE (#count < 1000)
BEGIN
--Insert test script here
END
set #ended = GETDATE()
select DATEDIFF( MILLISECOND, #start, #ended)
The table I was running tests againsts contains 3 columns, MDay, and CalDate. Every calendar date has a corresponding M(Manufacturing)Day. The table may look something like this:
MDay | CalDate
1 | 1970-01-01
2 | 1970-01-02
I wanted to test how efficient one of our functions was. This function simply takes in a date and returns the int MDay value. I used direct access, basically the same thing without the function, and tests resulted in this method take twice as long! Code I inserted into the loop is provided below. I used a random date in an attempt to eliminate caching (if exist).
Function
select #someint = Reference.GetMDay(DATEADD( D, convert(int, RAND() * 1000) , #date))
Definition for above
create Function [Reference].[GetMDay]
(#pCaLDate smalldatetime
)
Returns int
as
Begin
Declare #Mday int
Select #Mday = Mday
from Reference.MDay
where Caldate = #pCaLDate
Direct
select #someint = MDay from Reference.MDay where CalDate = DATEADD( D, convert(int, RAND() * 1000) , #date)
I even tried using a static #date for my direct code and the difference in times are negligible, so I know the convert call isn't holding it back.
What the heck is going on here?
Take a look at http://msdn.microsoft.com/en-us/library/ms178071%28v=sql.105%29.aspx is the execution plan the same on your sql server for both methods?

Converting JDE Julian date to Gregorian

I'm trying to convert JDE dates, and have amassed a large quantity of information and figured I'd try to do an SQL conversion function to simplify some tasks.
Here's the function I came up with, which I simply call "ToGregorian"
CREATE FUNCTION [dbo].[ToGregorian](#julian varchar(6))
RETURNS datetime AS BEGIN
DECLARE #datetime datetime
SET #datetime = CAST(19+CAST(SUBSTRING(#julian, 1, 1) as int) as varchar(4))+SUBSTRING(#julian, 2,2)+'-01-01'
SET #datetime = DATEADD(day, CAST(SUBSTRING(#julian, 4,3) as int)-1, #datetime)
RETURN #datetime
END
Takes a "julian" string.
Takes the first letter and adds it to century, starting from 19th.
Adds decade and years from the next 2 characters.
Finally adds the days, which are the final 3 characters, and subtracts 1 as it already had 1 day in the first setup. (eg. 2011-01-01)
Result ex: 111186 => 2011-07-05 00:00:00.000
In my opinion this is a bit clumsy and overkill, and I'm hoping there is a better way of doing this. Perhaps I'm doing too many conversions or maybe I should use a different method alltogether?
Any advice how to improve the function?
Perhaps a different, better, method?
Wouldn't mind if it could be more readable as well...
I've also got an inline version, where if for instance, I only have read privileges and can't use functions, which also looks messy, is it possible to make it more readable, or better?
CAST(REPLACE(Convert(VARCHAR, DATEADD(d,CAST(SUBSTRING(CAST([column] AS VARCHAR), 4,3) AS INT)-1, CAST(CAST(19+CAST(SUBSTRING(CAST([column] AS VARCHAR), 1,1) AS INT) AS VARCHAR)+SUBSTRING(CAST([column] AS VARCHAR), 2,2) + '-01-01' AS DATETIME)), 111), '/', '-') AS DATETIME)
The accepted answer is incorrect. It will fail to give the correct answer for 116060 which should be 29th February 2016. Instead it returns 1st March 2016.
JDE seems to store dates as integers, so rather than converting from strings I always go direct from the integer:
DATEADD(DAY, #Julian % 1000, DATEADD(YEAR, #Julian / 1000, '31-dec-1899'))
To go from a varchar(6) I use:
DATEADD(DAY, CAST(RIGHT(#Julian,3) AS int), DATEADD(YEAR, CAST(LEFT(#Julian,LEN(#Julian)-3) AS int), '31-dec-1899'))
I think it is more efficient to use native datetime math than all this switching back and forth to various string, date, and numeric formats.
DECLARE #julian VARCHAR(6) = '111186';
SELECT DATEADD(DAY, SUBSTRING(#julian,4,3)-1,
DATEADD(YEAR, 100 * LEFT(#julian,1)
+ 10 * SUBSTRING(#julian,2,1)
+ SUBSTRING(#julian,3,1),0));
Result:
===================
2011-07-05 00:00:00
Assuming this data doesn't change often, it may be much more efficient to actually store the date as a computed column (which is why I chose the base date of 0 instead of some string representation, which would cause determinism issues preventing the column from being persisted and potentially indexed). Even if you don't index the column, it still hides the ugly calculation away from you, being persisted you only pay that at write time, as it doesn't cause you to perform expensive functional operations at query time whenever that column is referenced...
Corrected for leap year
USE [master]
GO
/****** Object: UserDefinedFunction [dbo].[ToGregorian] Script Date: 08/18/2015 14:33:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ToGregorian](#julian varchar(6),#time varchar(6))
RETURNS datetime
AS
BEGIN
DECLARE #datetime datetime,#hour int, #minute int, #second int
set #time = ltrim(rtrim(#time));
set #julian = ltrim(rtrim(#julian));
if(LEN(#julian) = 5)
set #julian = '0' + #julian
IF(LEN(#time) = 6)
BEGIN
SET #hour = Convert(int,LEFT(#time,2));
SET #minute = CONVERT(int,Substring(#time,3,2));
SET #second = CONVERT(int,Substring(#time,5,2));
END
else IF(LEN(#time) = 5)
BEGIN
SET #hour = Convert(int,LEFT(#time,1));
SET #minute = CONVERT(int,Substring(#time,2,2));
SET #second = CONVERT(int,Substring(#time,4,2));
END
else IF(LEN(#time) = 4)
BEGIN
SET #hour = 0;
SET #minute = CONVERT(int,LEFT(#time,2));
SET #second = CONVERT(int,Substring(#time,3,2));
END
else IF(LEN(#time) = 3)
BEGIN
SET #hour = 0;
SET #minute = CONVERT(int,LEFT(#time,1));
SET #second = CONVERT(int,Substring(#time,2,2));
END
else
BEGIN
SET #hour = 0;
SET #minute = 0;
SET #second = #time;
END
SET #datetime = DATEADD(YEAR,100*CONVERT(INT, LEFT(#julian,1))+10*CONVERT(INT, SUBSTRING(#julian, 2,1))+CONVERT(INT, SUBSTRING(#julian,3,1)),0);
SET #datetime = DATEADD(DAY, CONVERT(INT,SUBSTRING(#julian, 4, 3))-1,#datetime);
SET #datetime = DATEADD(hour,#hour,#datetime)
SET #datetime = DATEADD(minute,#minute,#datetime);
SET #datetime = DATEADD(second,#second,#datetime);
RETURN #datetime
END
DATE(CHAR(1900000 + GLDGJ)) where GLDGJ is the Julian date value
I don't think anyone has mentioned it, but JDE has a table just for this.
It's the F00365 data table. As far as I know, it's a translation table just for this issue.
To get a Gregorian date, you join the F00365 table using the ONDTEJ field (which is the Julian date),and you return the ONDATE value, which is Gregorian.
e.g.
SELECT
DateReq.ONDATE
FROM F00101 NamesData
INNER JOIN F00365 DateReq
ON DateReq.ONDTEJ = NamesData.ABUPMJ
No math required. No weird issues with leap years.
To convert Julian date to Gregorian:
DATEADD(DAY, #julian % 1000 - 1, DATEADD(YEAR, #julian / 1000, 0))
To convert Gregorian to Julian date:
(YEAR(#date) - 1900) * 1000 + DATEPART(DAYOFYEAR, #date)
Try it:
DECLARE #julian VARCHAR(6);
SET #julian = N'122129';
SELECT #julian [JulianDate],
DATEADD(YEAR, #julian / 1000, 0) [Year],
#julian % 1000 [DayOfYear],
DATEADD(DAY, #julian % 1000 - 1, DATEADD(YEAR, #julian / 1000, 0)) [Date];
DECLARE #george DATETIME;
SET #george = '2022-5-9';
SELECT #george [Date],
YEAR(#george) [Year],
DATEPART(DAYOFYEAR, #george) [DayOfYear],
(YEAR(#george) - 1900) * 1000 + DATEPART(DAYOFYEAR, #george) [JulianDate];
Here a formula that can be used I have looking all over different site and I had even though I had to twick a bit has work well for me and you do not need to create any special function stuff:
DATEADD(DAY,CONVERT(INT,SUBSTRING(CONVERT(CHAR,(JULIANDDATEFIELD + 1900000)),5,3))-1,DATEFROMPARTS(SUBSTRING(CONVERT(CHAR,(JULIANDDATEFIELD + 1900000)),1,4),01,01))

SQL . The SP or function should calculate the next date for friday

I need to write a store procedure that will return a next friday date on a given date? for example - if the date is 05/12/2011, then it should return next friday date as 05/13/2011. If you pass, 05/16/2011, then it should return the date is 5/20/2011 (Friday). If you pass friday as the date, then it should return the same date.
I'd make this a scalar UDF as it is easier to consume the output.
CREATE FUNCTION dbo.GetNextFriday(
#D DATETIME
)
RETURNS DATETIME
WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT
AS
BEGIN
RETURN DATEADD(DAY,(13 - (##DATEFIRST + DATEPART(WEEKDAY,#D)))%7,#D)
END
This is for SQL Server 2008. To use in 2005, just change the date fields to your preference for datetime to date conversions. It also assumes you are not changing the default week begin value.
DECLARE #PassedDate date = '5/21/2011';
SELECT DATEADD(DAY,(CASE DATEPART(DW,#PassedDate) WHEN 7 THEN 6 ELSE 6 - DATEPART(DW,#PassedDate) END),#PassedDate);
Similar to the top answer, but without using ##DATEFIRST in the solution:
DECLARE #Today DATETIME = GETDATE(); -- any date
DECLARE #WeekdayIndex SMALLINT = DATEPART(WEEKDAY, #Today);
DECLARE #DaysUntilFriday SMALLINT = (13 - #WeekdayIndex) % 7;
DECLARE #UpcomingFridayDate DATETIME = DATEADD(DAY, #DaysUntilFriday, #Today);
SELECT #UpcomingFridayDate ;
Great solutions here, but I also recommend looking at time tables: you can generate them easily in Analysis server, and they can be indexed to be very fast, giving you lots of easy ways to get next week days (among other things).
You can find out more about them here
In our case, the same solution would be
Select MIN(PK_Date) from Time Where PK_Date > #SomeDate AND Day_Of_Week= 6
And of course when you're doing this for a large recordset, you can also do joins for maximum speed & efficiency.

Optimal way to convert to date

I have legacy system where all date fields are maintained in YMD format. Example:
20101123
this is date: 11/23/2010
I'm looking for most optimal way to convert from number to date field.
Here is what I came up with:
declare #ymd int
set #ymd = 20101122
select #ymd, convert(datetime, cast(#ymd as varchar(100)), 112)
This is pretty good solution but I'm wandering if someone has better way doing it
try this:
CONVERT(DATETIME, CONVERT(NVARCHAR, YYYYMMDD))
For example:
SELECT CONVERT(DATETIME, CONVERT(NVARCHAR, 20100401))
Results in:
2010-04-01 00:00:00.000
What you have is a pretty good soltuion.
Why are you looking for a better way?
I use exactly that, it has been working fine for me
As it is stored as an integer then you could potential extract the year, month and day by dividing by 100, 1000.
e.g.
DECLARE #Date INT
SET #Date = 20100401
DECLARE #Year INT
DECLARE #Month INT
DECLARE #Day INT
SET #Year = #Date / 10000
SET #Month = (#Date - (#Year * 10000)) / 100
SET #Day = #Date - (#Year * 10000) - (#Month * 100)
SELECT #Date, DATEADD(MONTH,((#Year-1900)*12)+#Month-1,#Day-1)
However, I have no idea if that is faster than the string comparison you already have. I think your solution is far cleaner and easier to read and would stick with that.

Transforming nvarchar day duration setting into datetime

I have a SQL Server function which converts a nvarchar day duration setting into a datetime value.
The day duration format is >days<.>hours<:>minutes<, for instance 1.2:00 for one day and two hours.
The format of the day duration setting can not be changed, and we can be sure that all data is correctly formatted and present.
Giving the function a start time and the day duration setting it should return the end time.
For instance: 2010-01-02 13:30 ==> 2010-01-03 2:00
I'm using a combination of charindex, substring and convert methods to calculate the value,
which is kind of slow and akward. Is there any other way to directly convert this day duration setting into a datetime value?
Not from what I can see. I would end up with a similar bit of SQL like you, using charindex etc. Unfortunately it's down to the format the day duration is stored in. I know you can't change it, but if it was in a different format then it would be a lot easier - the way I'd usually do this for example, is to rationalise the duration down to a base unit like minutes.
Instead of storing 1.2:00 for 1 day and 2 hours, it would be (1 * 24 * 60) + (2 * 60) = 1560. This could then be used in a straightforward DATEADD on the original date (date part only).
With the format you have, all approaches I can think of involve using CHARINDEX etc.
One alternative would be to build a string with the calculation. Then you can run the generated SQL with sp_executesql, specifying #enddate as an output parameter:
declare #startdate datetime
declare #duration varchar(10)
declare #enddate datetime
set #startdate = '2010-01-02 13:30'
set #duration = '0.12:30'
declare #sql nvarchar(max)
set #sql = 'set #enddate = dateadd(mi,24*60*' +
replace(replace(#duration,'.','+60*'),':','+') + ', #startdate)'
exec sp_executesql #sql,
N'#startdate datetime, #enddate datetime out',
#startdate, #enddate out
This creates a string containing set #enddate = dateadd(mi,24*60*0+60*12+30, #startdate) and then runs it.
I doubt this is faster than the regular charindex way:
declare #pos_dot int
declare #day int
declare #hour int
declare #minute int
select
#pos_dot = charindex('.',#duration),
#day = cast(left(#duration, #pos_dot-1) as int),
#hour = cast(left(right(#duration, 5), 2) as int),
#minute = cast(right(#duration, 2) as int),
#enddate = dateadd(mi, 24*60*#day + 60*#hour + #minute, #startdate)