Creating User Define Function to Convert Dates in SQL Server - sql

I know how to convert to date in SQL Server (T-SQL) but how do I create a UDF so that I can call it each time in my code?
Example 1: The below code will format this 20120428 to 04/28/2012
SELECT CONVERT(CHAR(10), CONVERT(DATE, TEST_DATE), 101) AS MY_DATE
FROM
MEMBER
WHERE ISDATE(TEST_DATE) <> 0
Example 2: The below code will format this 20120428 to 2012-04-28
SELECT CONVERT(DATE, TEST_DATE) AS MY_DATE
FROM
MEMBER
WHERE ISDATE(TEST_DATE) <> 0
Thank for the input!
Guy

For the second example you can do this:
CREATE FUNCTION dbo.ConvertDate(#d CHAR(10))
RETURNS DATE
AS
BEGIN
RETURN (SELECT CONVERT(DATE, #d));
END
GO
But a more flexible approach might be:
CREATE FUNCTION dbo.ConvertRegional
(
#d CHAR(10),
#style TINYINT
)
RETURNS CHAR(10)
AS
BEGIN
RETURN (SELECT CONVERT(CHAR(10), CONVERT(DATE, #d), #style));
END
GO
DECLARE #d CHAR(10);
SELECT #d = '20120428';
SELECT
dbo.ConvertDate(#d),
dbo.ConvertRegional(#d, 101),
dbo.ConvertRegional(#d, 103),
dbo.ConvertRegional(#d, 120);
Results:
---------- ---------- ---------- ----------
2012-04-28 04/28/2012 28/04/2012 2012-04-28
If you don't want to continue filtering out bad non-dates from your source table (keeping the ISDATE() in the WHERE clause should prevent the function from having to deal with those rows), you can change the function this way to avoid errors, if NULL is okay as a substitute:
CREATE FUNCTION dbo.ConvertRegional
(
#d CHAR(10),
#style TINYINT
)
RETURNS CHAR(10)
AS
BEGIN
RETURN (SELECT CASE WHEN ISDATE(#d) = 1 THEN
CONVERT(CHAR(10), CONVERT(DATE, #d), #style)
END);
END
GO
In SQL Server 2012, you can do this instead, which does the same thing without having to write your own CASE:
CREATE FUNCTION dbo.ConvertRegional
(
#d CHAR(10),
#style TINYINT
)
RETURNS CHAR(10)
AS
BEGIN
RETURN (SELECT CONVERT(CHAR(10), TRY_CONVERT(DATE, #d), #style));
END
GO
(In fact in SQL Server 2012 you can also use FORMAT() so that you don't have to memorize the style numbers, but since I don't know what version you're using I'll leave that for another day.)
All that said, other than saving a few keystrokes in your queries, this encapsulation is actually going to make your queries perform worse (depending on where they are used). For simple conversions like this it is much better to just perform them inline in most cases.

Related

How to pass the Datetime value as parameter to a stored function?

I created a function in SQL SERVER that takes in parameter a datetime value, and when I tried to execute it, I didn't know how to pass a Datetime value as parameter to this function, I got this error : Failed to convert date and / or time from a string.
this is the code of my function :
CREATE FUNCTION [dbo].[nb_pieces_produites] (#dateInsertion datetime)
returns decimal(4,0)
AS
BEGIN
DECLARE #year decimal(4,0), #month decimal(2,0), #day decimal(2,0) ,#hour
decimal(2,0), #nbPiecesProduites decimal(4,0)
set #year = (select DATEPART(yyyy, #dateInsertion))
set #month = (select DATEPART(mm, #dateInsertion))
set #day = (select DATEPART(dd, #dateInsertion))
set #hour = (select DATEPART(hh, #dateInsertion))
set #nbPiecesProduites = (SELECT COUNT(DISTINCT Num_Serie) FROM [dbo].
[dbo_Test] WHERE #dateInsertion BETWEEN '#year-#month-#day #hour:00:00' AND
'#year-#month-#day #hour:59:59')
return #nbPiecesProduites
END
and this is my query :
select [dbo].[nb_pieces_produites]('2017-06-19 11:38:52')
Can anyone help me please ?
Don't spend lots of time fiddling around with strings - try to keep your data as datetime data throughout.
To round a datetime down to the previous hour boundary, use a DATEADD/DATEDIFF pair:
CREATE FUNCTION [dbo].[nb_pieces_produites] (#dateInsertion datetime)
returns decimal(4,0)
AS
BEGIN
set #nbPiecesProduites = (SELECT COUNT(DISTINCT Num_Serie) FROM [dbo].
[dbo_Test] WHERE
date_column_from_table >= DATEADD(hour,DATEDIFF(hour,0,#dateInsertion),0)
AND
date_column_from_table < DATEADD(hour,DATEDIFF(hour,0,#dateInsertion)+1,0)
)
return #nbPiecesProduites
END
And, just to be safe, call it like this:
select [dbo].[nb_pieces_produites]('2017-06-19T11:38:52')
(Occasionally, under some settings, SQL Server will interpret nnnn-nn-nn as yyyy-dd-mm rather than yyyy-mm-dd, if it's followed by a space and then a time, rather than using T as the separator)
it's error because your function expect dateTime value, but when you called it you passing string not dateTime
change your query into :
select [dbo].[nb_pieces_produites](getDate())
CREATE FUNCTION [dbo].[nb_pieces_produites] (#dateInsertion datetime)
RETURNS decimal(4,0)
AS
BEGIN
RETURN
(
SELECT COUNT(DISTINCT Num_Serie)
FROM dbo.dbo_Test
WHERE
-- Use the date part to compare
CONVERT(date, [datetimecolumn]) = CONVERT(date, #dateInsertion)
-- Then compare with hour
AND DATEPART(HOUR, [datetimecolumn]) = DATEPART(HOUR, #dateInsertion)
)
END

SQL convert string(date or text) to date

I have a database which has a column called stringNextDue that contains data like dates (UK format) and text (e.g "overdue", "completed")
I am trying to create a view that shows courses that are due within a month from now:
WHERE
CONVERT(DATETIME, mt.stringNextDue , 103) < DATEADD(MONTH, 1, GETDATE())
This throws an error:
Conversion failed when converting date and/or time from character string.
Which is probably due to the fact that stringNextDue may contain actual strings of text.
I tried using
WHERE
ISDATE(mt.NextDateString) = 1
AND CONVERT(DATETIME, mt.stringNextDue , 103) < DATEADD(MONTH, 1, GETDATE())
But ISDATE only accepts US date formats therefore ignoring a lot of actual dates as strings
Tried set dateformat 'dmy', which fixed the IsDate issue, but it cannot be used in views.
Any suggestions?
Server update is not an option
If you cannot use the new TRY_CONVERT you might use a function like this:
Attention: This will not catch a wrong date like 31.06.2016, you'd have to modify the BETWEEN 1 AND 31 if you need this...
Attention2: If your text might include characters forbidden in xml you should replace < with <, > with > and & with & ...
CREATE FUNCTION dbo.TestDate(#TestString VARCHAR(100))
RETURNS DATE
AS
BEGIN
DECLARE #x XML=CAST('<x>' + REPLACE(#TestString,'.','</x><x>') + '</x>' AS XML)
DECLARE #p1 VARCHAR(10) = #x.value('x[1]','varchar(10)');
DECLARE #p2 VARCHAR(10) = #x.value('x[2]','varchar(10)');
DECLARE #p3 VARCHAR(10) = #x.value('x[3]','varchar(10)');
IF LEN(#p1)=2 AND ISNUMERIC(#p1)=1 AND CAST(#p1 AS INT) BETWEEN 1 AND 31
AND LEN(#p2)=2 AND ISNUMERIC(#p2)=1 AND CAST(#p2 AS INT) BETWEEN 1 AND 12
AND LEN(#p3)=4 AND ISNUMERIC(#p3)=1 AND CAST(#p3 AS INT) BETWEEN 1900 AND 2100
RETURN CONVERT(DATETIME, #TestString , 103);
RETURN NULL;
END
GO
SELECT
dbo.TestDate('overdue') AS SureNoDate
,dbo.TestDate('01.04.2016') AS EuropeanDate
,dbo.TestDate('2016.04.01') AS WrongFormat
,dbo.TestDate('01.13.2016') AS BadDate;
GO
DROP FUNCTION dbo.TestDate;
The result
SureNoDate EuropeanDate WrongFormat BadDate
NULL 2016-04-01 NULL NULL
You might pass back a valid date (RETURN GETDATE() ?) instead of RETURN NULL for your comparisson outside. This depends on your needs...
It should be possible to replace WHERE clause using this:
SELECT *
FROM
-- sample data
(values('2015-01-01'),('01-01-2015'), ('x-x-x-x')) mt(NextDateString)
-- Replace WHERE statement with the following
CROSS APPLY
(
SELECT
RIGHT('0000'+PARSENAME(REPLACE(mt.NextDateString, '-', '.'), 1),4) yyy,
RIGHT('0000'+PARSENAME(REPLACE(mt.NextDateString, '-', '.'), 2),4) mmm,
RIGHT('0000'+PARSENAME(REPLACE(mt.NextDateString, '-', '.'), 3),4) ddd
) x
WHERE
x.yyy BETWEEN '1950' AND '2050'
AND x.mmm BETWEEN '0001' AND '0012'
AND x.ddd BETWEEN '0001' AND '0031'
AND ISDATE(mt.NextDateString) = 1
AND x.yyy+x.mmm+x.ddd < CONVERT(char(8), DATEADD(MONTH, 1, GETDATE()), 112)
Result:
NextDateString yyy mmm ddd
01-01-2015 2015 0001 0001
Thank you for suggestions,
I fixed it by setting language to British on user settings
EXEC sp_defaultlanguage 'username', 'british'

Defining type as a variable - DATEADD(type, value, date) - SQL [duplicate]

This question already has answers here:
SQL Dynamic DatePart when using DateDiff
(6 answers)
Closed 5 years ago.
I had to change the date in one of a column of my SQL table (datetime field), increment YEAR by 1. The simple way to perform this action would be (unless someone knows a better way) to update table and SET column with DATEADD()
-- sample 1
-- DATEADD(type, value, date)
SELECT DATEADD(YEAR, 1, GETDATE())
Since I'm a lazy programmer, I don't want to keep updating the tables every time we run in to such situation. So I decided to write a small script (function) which gets TYPE, VALUE from the user and perform the operation. But I ran into a situation where I can't use TYPE as a variable.
-- Sample 2
-- Error in the code
DECLARE #type VARCHAR(10) = 'YEAR'
,#increment INT = 1
SELECT DATEADD(#type, #increment, GETDATE())
I can write a case statement where based on the 'TYPE' value I can select the update statement.
-- Sample 3
DECLARE #type VARCHAR(10) = 'YEAR'
,#increment INT = 1
UPDATE Table_name
SET Column_date = CASE #type WHEN 'YEAR' THEN DATEADD(YEAR, #increment, Column_date)
WHEN 'MONTH' THEN DATEADD(MONTH, #increment, Column_date)
-- and so on
END
But is there a way to perform the action without the case statement, or can anyone make sample code '2' run?
P.S. Its more of a knowledge based question - Its not holding me back, I'm just curious to know if its possible.
Thanks! :)
You could just get all of the type values from the user and just update them all at once.. Just default to 0 the ones they don't want to change
DECLARE #Year INT = 0,
#Month INT = 0,
#Day INT = 0
UPDATE Table_name
SET Column_date = DATEADD(YEAR, #Year, DATEADD(Month, #Month, DATEADD(Day, #Day, Column_date)))
An alternative to already provided solution is to use a dynamic SQL:
Setup:
-- drop table dbo.DateTest
create table dbo.DateTest
(
TheDate DATE,
UpdatedDate DATE
)
TRUNCATE TABLE dbo.DateTest
GO
INSERT INTO dbo.DateTest (TheDate) VALUES ('20150203'), ('20150506'), ('20141231')
GO
Code:
select * from dbo.DateTest
DECLARE #type VARCHAR(8) = 'year' -- this must be a DATEADD recognizable token
DECLARE #increment INT = 2
DECLARE #Sql NVARCHAR(1000) = N'
UPDATE dbo.DateTest SET UpdatedDate = DATEADD(' + #type + ', #increment, TheDate)'
EXEC sp_executesql #Sql, N' #increment INT', #increment
select * from dbo.DateTest
I wish date part could be provided as parameter, but it does not seem to be possible.

Converting YYYYMM format to YYYY-MM-DD in SQL Server

I need to perform a query on a large table that has a datetime column that is indexed.
We need to query the data for a range from a month (at a minimum) to multiple months.
This query would be executed from Cognos TM1 and the input would be a period like YYYYMM. My question is - how to convert the YYYYMM input to a format that can be used to query that table (with the index being used).
Let's say if the input is
From Date: '201312'
To Date: '201312'
then, we need convert the same to 'between 01-12-2013 and 31-12-2013' in the query
Since we need this to be hooked up in Cognos TM1, so would not be able to write a procedure or declare variables (TM1 somehow does not like it).
Thanks in advance for your reply.
I would do something like this:
create procedure dbo.getDataForMonth
#yyyymm char(6) = null
as
--
-- use the current year/month if the year or month is invalid was omitted
--
set #yyyymm = case coalesce(#yyyymm,'')
when '' then convert(char(6),current_timestamp,112)
else #yyyymm
end
--
-- this should throw an exception if the date is invalid
--
declare #dtFrom date = convert(date,#yyyymm+'01') -- 1st of specified month
declare #dtThru date = dateadd(month,1,#dtFrom) -- 1st of next month
--
-- your Big Ugly Query Here
--
select *
from dbo.some_table t
where t.date_of_record >= #dtFrom
and t.date_of_record < #dtThru
--
-- That's about all there is to it.
--
return 0
go
Suppose you are getting this value of YYYYMM in a varchar variable #datefrom .
You can do something like
DECLARE #DateFrom VARCHAR(6) = '201201';
-- Append '01' to any passed string and it will get all
-- records starting from that month in that year
DECLARE #Date VARCHAR(8) = #DateFrom + '01'
-- in your query do something like
SELECT * FROM TableName WHERE DateTimeColumn >= #Date
Passing Datetime in a ansi-standard format i.e YYYYMMDD is a sargable expression and allows sql server to take advantage of indexes defined on that datetime column.
here is an article written by Rob Farley about SARGable functions in SQL Server.
Try this...
declare #startdate date,#endate date
select #startdate =convert(date,left('201312',4)+'-'+right('201312',2)+'-01')
select #endate= DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #startdate) + 1, 0))
select convert(date,#startdate,102) startdate,convert(date,#endate,102) endate
In the datasource of your TM1 Turbo Integrator process, you can use parameters in the SQL query. E.g. you could take this SQL query:
SELECT Col1, Col2
FROM Table
WHERE Col1 = 'Green'
AND Col2 < 30
In TM1, to parameterise this, you would create two parameters e.g. P1 and P2 and put them in the query:
SELECT Col1, Col2
FROM Table
WHERE Col1 = '?P1?'
AND Col2 < ?P2?

How to get date difference between two dates in same year with one date is from an input date not from the year

Well this is my case: I have an input date X (dd-mm-yyyy), and I want to count the number of days between it with the year part is changed into current year and today's date in SQL. I t comes with the following condition, after the year is changed temporarily: (Here's my current idea of the logic)
- If date X is earlier than today, then difference = datediff(X,now), with the X year is current year
- If date X is later than today, then difference = datediff(X,now), with the X year is one year before
Sample case:
1st case: The input date is 6-6-1990. Today (automatically generated) is 22-8-2011. Then the difference will be = datediff(6-6-2011,22-08-2011)
2nd case: The input date is 10-10-1990. Today (automatically generated) is 22-8-2011. Then the difference will be = datediff(10-10-2010,22-08-2011)
Any idea how to do this in SQL (in SQL Server)? Or is there any other more simple alternatives for this problem? I'd also like this to be done in the query and not using a stored procedure or function
Sorry if there's already a similar question, I just don't know the exact keyword for this problem :( if there's a question like this previously, feel free to direct me there.
Thanks in advance
Here is the implementation (if I understood the logic you need correctly):
USE YourDbName
GO
CREATE FUNCTION YearPartDiff (#date datetime)
RETURNS int
AS
BEGIN
DECLARE #dateCurrentYear datetime
SET #dateCurrentYear = DATEADD(year, YEAR(GETDATE()) - YEAR(#date), #date)
DECLARE #result int
IF #dateCurrentYear < GETDATE()
SET #result = ABS(DATEDIFF(day, #dateCurrentYear, GETDATE()))
ELSE
SET #result = ABS(DATEDIFF(day, DATEADD(year, -1, #dateCurrentYear), GETDATE()))
RETURN(#result)
END
GO
And the example of usage:
USE YourDbName
GO
DECLARE #someDate datetime
SET #someDate = '2011-06-06'
SELECT dbo.YearPartDiff(#someDate) /*returns 77*/
SET #someDate = '2010-10-10'
SELECT dbo.YearPartDiff(#someDate) /*returns 316*/
Basically, #Andrei's solution, but in a single statement:
SELECT
DayDiff = DATEDIFF(
DAY,
DATEADD(YEAR, CASE WHEN LastOcc > GETDATE() THEN -1 ELSE 0 END, LastOcc),
GETDATE()
)
FROM (
SELECT LastOcc = DATEADD(YEAR, YEAR(GETDATE()) - YEAR(#InputDate), #InputDate)
) s
This seems to do the job
SELECT DATEDIFF(DAY, CONVERT(DATETIME, N'2011-06-06'), CONVERT(DATETIME, N'2011-08-22'))
So the basic syntax is
SELECT DATEDIFF(DAY, CONVERT(DATETIME, N'yyyy-mm-dd'), CONVERT(DATETIME, N'yyyy-mm-dd '))
Alternatively, you can use GETDATE() instead of the string for today's date
I have used "SELECT DATEDIFF( D, "+myDate+", GETDATE())" in my code, on SQL Server 2005. It works for me. The value myDate of course would be the DateTime input value.
you should try this query:
create table #T (inp_date datetime)
insert #T values ('06-06-1990')
insert #T values ('08-22-1990')
insert #T values ('10-10-1990')
--select * from #T
select inp_date, GETDATE(),
CASE
WHEN DATEADD(yy,DATEDIFF(yy,inp_date,GETDATE()),inp_date) <= GETDATE()
THEN DATEDIFF(dd,DATEADD(yy,DATEDIFF(yy,inp_date,GETDATE()),inp_date),GETDATE())
ELSE DATEDIFF(dd,DATEADD(yy,DATEDIFF(yy,inp_date,GETDATE())-1,inp_date),GETDATE())
END
from #T