SQL Function help- Daylight Savings calculation - sql

All,
I wrote a function that essentially takes a timestamp and a shopper ID, and based on that shoppers zipcode work out the UTC offset and correct the time.
The problem I am having is: it's sloooow!
Can anyone see an easy way to speed it up ?
CREATE FUNCTION TimeModifier
(
-- Add the parameters for the function here
#InputDate datetime,#shopperid int
)
RETURNS datetime
AS
BEGIN
--Declare #shopperid int
--set #shopperid=25
-- Declare the return variable here
-- Declare the return variable here
DECLARE #Result datetime
Declare #zip nvarchar(10)
Declare #TimeofYear int
declare #Year int
declare #Winter int
declare #Summer int
select #year=datepart(yyyy,#InputDate)
--If 0 then its outside of the summer hours.
SELECT #timeofyear=count(*) FROM [d].[dbo].[DST-Dates] where #inputdate>=startdate and #inputdate<=enddate
select #zip=zip from d..shopper where shopperid=#shopperid
--Gets the UTC offset for winter and summer
select #winter=winter,#summer=summer FROM [MMD_Feed].[dbo].[ZipCodeZones] where zip=#zip
if(#TimeofYear=0)--IE is it Winter
set #Result=DATEADD(HH,#winter, #Inputdate)
else--Use summer offset
set #Result=DATEADD(HH,#summer, #Inputdate)
--select #Result
-- Return the result of the function
RETURN #Result
END
GO
Thanks
~J

Here is a total shot in the dark for converting this to an iTVF.
CREATE FUNCTION TimeModifier
(
-- Add the parameters for the function here
#InputDate datetime
, #shopperid int
)
RETURNS TABLE WITH SCHEMABINDING
AS RETURN
select DATEADD(Hour, case when t.TimeOfYear = 0 then z.winter else z.summer end, #InputDate) as MyResult
from d.dbo.shopper s
join [MMD_Feed].[dbo].[ZipCodeZones] z on s.zip = z.zip
cross apply
(
SELECT count(*) as TimeOfYear
FROM [d].[dbo].[DST-Dates]
where #inputdate >= startdate
and #inputdate <= enddate
) t
where s.shopperid = #shopperid

Related

Assigning variables to use in query

I am moving from Oracle to SQL Server and I am noticing differences regarding assigning variables in a query. I wonder if someone could write me a simple example of how I can do this in SSMS please?
In the example below I am looking to assign the variable #date1 at the beginning of the select statement so that I can simply change the date at the top instead of having to change it several times in the query where #date1 is used several times.
SELECT *
FROM table
where date = #date1
Thanks
Based on your example the syntax would be as follows:
DECLARE #date1 DATETIME
SET #date1 = '2017-01-01 00:00:00.000'
Then reference #date1 in your query as you have above.
More broadly, the syntax is:
DECLARE #<name of variable> <type>
SET #<name of variable> = <value>
-- Simple declares
DECLARE #Variable1 VARCHAR(100)
DECLARE #Variable2 DATE
DECLARE #VariableTable TABLE (
numberColumnName INT,
textColumnName VARCHAR(MAX))
-- Chained declares
DECLARE
#Variable3 VARCHAR(100),
#Variable4 INT
-- Declare with initiation
DECLARE #Variable5 INT = 150
DECLARE #Variable6 DATE = '2018-05-05' -- Implicit conversion (varchar to date)
DECLARE #Variable7 FLOAT = 1945.15 * 1648.12 / #Variable5 -- Expressions can be used
DECLARE #Variable8 INT = (SELECT COUNT(1) FROM sys.objects)
-- Chained declares with initiation
DECLARE
#Variable9 VARCHAR(100) = 'Afla',
#Variable10 INT = 9164 * #Variable5
-- Change variable values (without declaring)
SET #Variable1 = 'Some value'
SET #Variable2 = CONVERT(DATE, GETDATE())
For your example:
DECLARE #DateFilter DATE = '2018-05-16' -- Use ISO standard date format (yyyy-MM-dd) when you hard-code them as literals
SELECT
*
FROM
YourTable AS T
WHERE
T.DateToFilter >= #DateFilter
DECLARE #date1 DATE = '2018-04-11'
This code may be fine, but be aware of dates formats :date (Transact-SQL)
and the need of using either Date, Datetime, or Datetime2.

Handle Julian and Real Times in one query

Hi Below is some sample data
DECLARE #Time TABLE (
[Time] VARCHAR(250),
[Count] VARCHAR(250)
)
INSERT INTO #Time ([Time],[Count])
VALUES
('13:52','2'),
('13:53','2'),
('13:54','5'),
('13:55','3'),
('13:56','3'),
('13:57','1'),
('13:58','4'),
('13:59','1'),
('130','72'),
('1300','61'),
('1301','40'),
('1302','51'),
('1303','53'),
('1304','59'),
('1305','62'),
('1306','36'),
('1307','56'),
('1308','52')
Select * from #Time
Is there a way to handle both data types so that it outputs as a real time
I use a function that partly works but am getting and error each time:
Msg 8116, Level 16, State 1, Line 1
Argument data type varchar is invalid for argument 2 of dateadd function.
The function I use is as follows and wondered if this can be adopted/changed to account for the poor data eg both types of Times (Julian/Regular).
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
-- Add the parameters for the function here
#JulianTime INT
)
RETURNS DATETIME
AS
BEGIN
-- Declare the return variable here
DECLARE #Result DATETIME
-- Add the T-SQL statements to compute the return value here
IF #JulianTime >= 0 AND #JulianTime < 1440 -- this ensures that the result will be in the range of a datetime data type
SET #Result = DATEADD(MINUTE, #JulianTime, CAST('00:00' AS TIME))
ELSE
SET #Result = CAST('00:00' AS TIME)
-- Return the result of the function
RETURN #Result
END
GO
ADDITION:
The COMPLETE datetime function is here:
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianDateTimeToSQLDateTime] (
-- Add the parameters for the function here
#JulianDate INT,
#JulianTime INT = 0
)
RETURNS DATETIME
AS
BEGIN
-- Declare the return variable here
DECLARE #Result DATETIME
-- Add the T-SQL statements to compute the return value here
IF #JulianDate > 640333 -- this ensures that the result will be in the range of a datetime data type
BEGIN
SET #Result = DATEADD(DAY, (#JulianDate-429), CAST('Jan 1 0001' AS DATETIME2))
IF #JulianTime < 1440 AND #JulianTime >= 0 -- this ensures that the time is between 00:00 and 23:59
SET #Result = DATEADD(MINUTE, #JulianTime, #Result)
END
ELSE
SET #Result = 'Jan 1 1753'
-- Return the result of the function
RETURN #Result
END
GO
I am not sure what exactly are you trying to do from your give information. But I tried to modify the function to return time when I pass it JulianTime as a varchar. Here is the code -- (try to make changes accordingly though)
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
-- Add the parameters for the function here
#JulianTime VARCHAR(255)
)
RETURNS TIME(0)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result TIME(0), #HOUR VARCHAR(2), #MINUTES VARCHAR(2)
-- DECLARE #JulianTime VARCHAR(255)
-- SET #JulianTime = '13:02'
SET #HOUR = SUBSTRING(#JulianTime, 1, 2)
IF(LEN(#JulianTime) = 4)
SET #MINUTES = SUBSTRING(#JulianTime, 3, 2)
IF(LEN(#JulianTime) = 5)
SET #MINUTES = SUBSTRING(#JulianTime, 4, 2)
SET #Result = CONCAT(#HOUR, ':', #MINUTES)
-- PRINT #RESULT
-- Return the result of the function
RETURN #Result
END
GO
I've updated the FUNCTION.
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
#timeString varchar(250)
)
RETURNS TIME
AS
BEGIN
DECLARE #Result TIME ;
/* Ensure that we're working with a fixed date */
DECLARE #epoch datetime = '19000101' ;
/* Check the string for ':' */
IF CHARINDEX(':',#timeString) > 0
/* Can #timeString be parsed as a valid time? */
SET #Result = TRY_PARSE(#timeString AS time) ; /* Returns NULL if bad time string. */
ELSE
/* No ':' so check if valid julian time. */
IF TRY_CONVERT(int,#timeString) IS NOT NULL AND CONVERT(int,#timeString) BETWEEN 0 AND 1439
SET #Result = CAST( DATEADD(minute, CONVERT(int,#timeString), #epoch) AS time) ;
ELSE
SET #Result = NULL ;
RETURN #Result ;
END
http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=1ca82cd76b2932593262601b1742f602
This will only work if you're using something greater than SQL 2012+ because of TRY_PARSE and TRY_CONVERT. If you're lower than 2012, you can modify that bit to do what those functions essentially do.
Select
CASE WHEN CHARINDEX(':',[Time]) > 0 THEN CAST([Time] AS TIME) ELSE DATEADD(MINUTE, TRY_CAST([Time] AS INT), CAST('00:00' AS TIME)) END AS [RealTime],
*
from #Time
This appears to do the trick, thanks for the valuable input
For SQL 2008, use this function:
CREATE FUNCTION [dbo].[udf_TR_PROTOS_JulianTimeToSQLDateTime]
(
#timeString varchar(250)
)
RETURNS TIME
AS
BEGIN
DECLARE #Result time ;
/* Ensure that we're working with a fixed date */
DECLARE #epoch datetime = '19000101' ;
/* Check the string for ':' */
IF CHARINDEX(':',#timeString) > 0
IF ISDATE(#timeString) = 1
/* Is #timeString a valid time object? */
SET #Result = CAST(#timeString AS time) ;
ELSE
SET #Result = NULL ;
ELSE
/* No ':' so check if valid julian time. */
IF ISNUMERIC(#timeString) = 1 AND CONVERT(int,#timeString) BETWEEN 0 AND 1439
SET #Result = CAST( DATEADD(minute, CONVERT(int,#timeString), #epoch) AS time) ;
ELSE
SET #Result = NULL ;
RETURN #Result ;
END

convert date to spanish in sql

I am running a report with multiple fields in english. I have 2 date fields at the end of the report, one has to be in English and the other in Spanish. The format the date needs to be in is ie November 1, 2012. I need the spanish to be Novembre 1, 2012. I cannot get the last field of the report to produce in spanish. I am running in sql server 2005.
Maybe is cumbersome, but I don't see how to do it on an easier way.
First, create a function. This function will make use of system view sys.syslanguages to get the correct name of month in spanish. Parameters are a valid date and language (alias on sys.syslanguage view).
CREATE FUNCTION [dbo].[fn_GetMonthName] (
#Date DATETIME,
#Language NVARCHAR(100)
)
RETURNS NVARCHAR(400)
AS
BEGIN
DECLARE #i INT, #m INT,#mlist NVARCHAR(1000)
SET #m = MONTH(#Date)
SET #mlist = (SELECT months FROM sys.syslanguages WHERE ALIAS = #language)
SET #i = 1
WHILE(#i < #m)
BEGIN
SET #mlist = REPLACE(#mlist, SUBSTRING(#mlist,1,CHARINDEX(',',#mlist)) ,'')
SET #i = #i + 1
END
SET #mlist = (CASE CHARINDEX(',',#mlist) WHEN 0 THEN #mlist ELSE SUBSTRING(#mlist,0,CHARINDEX(',',#mlist) ) END )
RETURN #mlist
END
GO
Then call the function anywhere you need to:
SELECT CONVERT(VARCHAR(20), GETDATE(), 100) AS CurrentDate,
dbo.fn_GetMonthName (GETDATE(), 'Spanish') AS [Mes-Month]
Result:
CurrentDate Mes-Month
May 24 2013 12:02AM Mayo
Taken from Get Language specific Month Name from SQL
Take a look at: http://www.sqlservercurry.com/2010/11/change-default-language-for-sql-server.html
You can temporarily set the language to spanish, not sure how feasible this is for you. The other way to do it would be to write your own months function, and maybe pass it a 2nd parameter that then decides what the output would be.
This function will translate the month within a string it's based on the sys.syslanguages table.
i.e. SELECT dbo.fn_tranMonth(2,0,'1 déc. 2014 10:26:14 UTC+00:00')
Results:
1 Dec. 2014 10:26:14 UTC+00:00
CREATE FUNCTION [dbo].[Split] (#sep char(1), #s varchar(8000))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
GO
CREATE FUNCTION dbo.fn_tranMonth
(
#fromLan INT
,#toLan INT
,#string VARCHAR(MAX)
)
RETURNS
VARCHAR(50)
AS
BEGIN
DECLARE #TTTT AS TABLE(PK INT IDENTITY(1,1)
,fromMonth VARCHAR(50)
,toMonth VARCHAR(50)
)
DECLARE
#fromMonths VARCHAR(200)
,#toMonths VARCHAR(200)
,#fromMonth VARCHAR(20)
,#toMonth VARCHAR(20)
,#rowNum INT=12;
SELECT #fromMonths=shortmonths
FROM SYS.syslanguages
WHERE langid=#fromLan;
SELECT #toMonths=shortmonths
FROM sys.syslanguages
WHERE langid=#toLan;
INSERT #TTTT(fromMonth)
SELECT S
FROM dbo.Split(',',#fromMonths);
DECLARE #TTTT2 AS TABLE(PK INT IDENTITY(1,1)
,toMonth VARCHAR(50)
)
INSERT #TTTT2(toMonth)
SELECT S
FROM dbo.Split(',',#toMonths);
UPDATE #TTTT
SET toMonth=B.toMonth
FROM
#TTTT A
JOIN #TTTT2 B ON A.PK=B.PK;
DECLARE
#loopPos INT=0
,#returnMonth VARCHAR(50);
WHILE #loopPos<#rowNum
BEGIN
SET #loopPos+=1;
SELECT
#fromMonth=fromMonth
,#toMonth=toMonth
FROM #TTTT
WHERE PK=#loopPos;
SET #string=REPLACE(#string,#fromMonth,#toMonth);
END;
RETURN #string;
END
try this:
SELECT CONVERT(VARCHAR(10),GETDATE(), 103)
or
this code, return a VARCHAR(10) with date EN ESPAÑOL, leches.
IDEA (separator used: '-'):
Get format YYYY-MM-DD NVARCHAR(10).
Get format DD-MM-YYYY nvarchar(10)
Use the function
sample:
select dbo.date2txt(GETDATE ())
function to create:
create function [dbo].[AFecha] (
#fecha NVARCHAR(10)
)
returns NVARCHAR(10)
as
begin
Declare #r nvarchar(10)
if substring(#Fecha,3,1) = '-'
set #r = #Fecha
else
set #r = substring(#fecha,9,2)+'-'+substring(#fecha,6,2)+'-'+left(#fecha,4)
Return #r
End
go
create FUNCTION [dbo].[NTSDate](#fecha DateTime)
RETURNS nVarChar(10) AS
BEGIN
DECLARE #salida nVarChar(10)
set #salida = STR(year(#fecha),4,0) + '-' + RIGHT('0' + LTRIM(month(#fecha)),2) + '-' + RIGHT('0' + LTRIM(day(#fecha)),2)
return (#salida)
End
go
ALTER function [dbo].[Date2Txt](#Fecha as datetime) returns nvarchar(10)
as
begin
return dbo.afecha(dbo.NTSDate(#Fecha))
end
go
You can do the following:
FORMAT(GETDATE(), 'MMMM d,yyyy','es-US')
The last parameter for format datetime is an optional culture parameter that does exactly what you need when passed the right culture.
If you needed to localize to a different language you could find the string to do so from the link below.
https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-input-locales-for-windows-language-packs?view=windows-11

SQL Server FOR EACH Loop

I have the following SQL query:
DECLARE #MyVar datetime = '1/1/2010'
SELECT #MyVar
This naturally returns '1/1/2010'.
What I want to do is have a list of dates, say:
1/1/2010
2/1/2010
3/1/2010
4/1/2010
5/1/2010
Then i want to FOR EACH through the numbers and run the SQL Query.
Something like (pseudocode):
List = 1/1/2010,2/1/2010,3/1/2010,4/1/2010,5/1/2010
For each x in List
do
DECLARE #MyVar datetime = x
SELECT #MyVar
So this would return:-
1/1/2010
2/1/2010
3/1/2010
4/1/2010
5/1/2010
I want this to return the data as one resultset, not multiple resultsets, so I may need to use some kind of union at the end of the query, so each iteration of the loop unions onto the next.
edit
I have a large query that accepts a 'to date' parameter, I need to run it 24 times, each time with a specific to date which I need to be able to supply (these dates are going to be dynamic) I want to avoid repeating my query 24 times with union alls joining them as if I need to come back and add additional columns it would be very time consuming.
SQL is primarily a set-orientated language - it's generally a bad idea to use a loop in it.
In this case, a similar result could be achieved using a recursive CTE:
with cte as
(select 1 i union all
select i+1 i from cte where i < 5)
select dateadd(d, i-1, '2010-01-01') from cte
Here is an option with a table variable:
DECLARE #MyVar TABLE(Val DATETIME)
DECLARE #I INT, #StartDate DATETIME
SET #I = 1
SET #StartDate = '20100101'
WHILE #I <= 5
BEGIN
INSERT INTO #MyVar(Val)
VALUES(#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
SET #I = #I + 1
END
SELECT *
FROM #MyVar
You can do the same with a temp table:
CREATE TABLE #MyVar(Val DATETIME)
DECLARE #I INT, #StartDate DATETIME
SET #I = 1
SET #StartDate = '20100101'
WHILE #I <= 5
BEGIN
INSERT INTO #MyVar(Val)
VALUES(#StartDate)
SET #StartDate = DATEADD(DAY,1,#StartDate)
SET #I = #I + 1
END
SELECT *
FROM #MyVar
You should tell us what is your main goal, as was said by #JohnFx, this could probably be done another (more efficient) way.
You could use a variable table, like this:
declare #num int
set #num = 1
declare #results table ( val int )
while (#num < 6)
begin
insert into #results ( val ) values ( #num )
set #num = #num + 1
end
select val from #results
This kind of depends on what you want to do with the results. If you're just after the numbers, a set-based option would be a numbers table - which comes in handy for all sorts of things.
For MSSQL 2005+, you can use a recursive CTE to generate a numbers table inline:
;WITH Numbers (N) AS (
SELECT 1 UNION ALL
SELECT 1 + N FROM Numbers WHERE N < 500
)
SELECT N FROM Numbers
OPTION (MAXRECURSION 500)
declare #counter as int
set #counter = 0
declare #date as varchar(50)
set #date = cast(1+#counter as varchar)+'/01/2013'
while(#counter < 12)
begin
select cast(1+#counter as varchar)+'/01/2013' as date
set #counter = #counter + 1
end
Off course an old question. But I have a simple solution where no need of Looping, CTE, Table variables etc.
DECLARE #MyVar datetime = '1/1/2010'
SELECT #MyVar
SELECT DATEADD (DD,NUMBER,#MyVar)
FROM master.dbo.spt_values
WHERE TYPE='P' AND NUMBER BETWEEN 0 AND 4
ORDER BY NUMBER
Note : spt_values is a Mircrosoft's undocumented table. It has numbers for every type. Its not suggestible to use as it can be removed in any new versions of sql server without prior information, since it is undocumented. But we can use it as quick workaround in some scenario's like above.
[CREATE PROCEDURE [rat].[GetYear]
AS
BEGIN
-- variable for storing start date
Declare #StartYear as int
-- Variable for the End date
Declare #EndYear as int
-- Setting the value in strat Date
select #StartYear = Value from rat.Configuration where Name = 'REPORT_START_YEAR';
-- Setting the End date
select #EndYear = Value from rat.Configuration where Name = 'REPORT_END_YEAR';
-- Creating Tem table
with [Years] as
(
--Selecting the Year
select #StartYear [Year]
--doing Union
union all
-- doing the loop in Years table
select Year+1 Year from [Years] where Year < #EndYear
)
--Selecting the Year table
selec]

Update field by a function

I have the function which gets ID and returns date from table if it exists or returns current date if isn't:
CREATE FUNCTION [dbo].[CLOSEDATE] (#ID int)
RETURNS datetime
AS
BEGIN
DECLARE #closed int;
DECLARE #result datetime;
SELECT #result = created_on from dbo.statuses_history
WHERE journalized_id = #ID and new_status = 'Закрыто';
IF #result IS NULL
SELECT #result = GETDATE()
RETURN (DATEADD(dd, 0, DATEDIFF(dd, 0, #result)))
END;
The next queries return correct date from table:
select dbo.closedate(4170)
select dbo.closedate(id) from issues where id = 4170
And the next code update the record correctly (values from table):
DECLARE #d AS datetime
select #d = dbo.closedate(4170)
UPDATE issues SET created_on = #d WHERE issues.id = 4170
But I get current date in the field if I update the record:
UPDATE issues
SET created_on = dbo.CloseDate(id)
WHERE issues.id = 4170
It looks like the ID parameter doesn't pass to the function.
Your tests (that I missed on the first reading, sorry) are enough to make me very confused. It seems that your test results should not be possible.
My only suggestion would be to recode the function and see what happens...
CREATE FUNCTION [dbo].[CLOSEDATE] (#ID int)
RETURNS TABLE
AS
RETURN
SELECT
(DATEADD(dd, 0, DATEDIFF(dd, 0, ISNULL(MAX(created_on), GetDate())))) AS close_date
FROM
dbo.statuses_history
WHERE
journalized_id = #ID
AND new_status = 'Закрыто'
And then...
UPDATE
issues
SET
created_on = fn.close_date
FROM
issues
CROSS APPLY
dbo.CLOSEDATE(id) AS fn
WHERE
issues.id = 4170
Cross Apply is what you looking for I think.