I was updating a stored procedure to change the reporting time to clients timezone. The stored procedure is comprised of a dynamic SQL, which contains the time #timeoffset parameter of smallint data type.
DECLARE #sql VARCHAR(MAX) =
N'SELECT DISTINCT cl.ClientId,
CASE WHEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)+', x.changedate) >= '''+ CONVERT(varchar, #start_date) +'''
AND DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)+', x.changedate) < '''+ CONVERT(varchar, #end_date_plus1) +'''
THEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)+',x.changedate)'
To change the time to the clients' time zone, I need to subtract the #timeoffset. Making it negative doesn't change the output.
Trying to add the (-) before the conversion, would raises an error as subtraction operator is invalid for varchar. Writing it
without the conversion raises an error
'Conversion failed when converting the nvarchar value to data type smallint.
Can anyone assist me with this please? Thanks.
You could try passing the negative number as another variable:
DECLARE #neg INT = -1;
DECLARE #sql VARCHAR(MAX) =
N'SELECT DISTINCT cl.ClientId,
CASE WHEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)*#neg+', x.changedate) >= '''+
CONVERT(varchar, #start_date) +'''
AND DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)*#neg+', x.changedate) < '''+
CONVERT(varchar, #end_date_plus1) +'''
THEN DATEADD(HOUR,'+CONVERT(CHAR(2),#timeoffset)*#neg+',x.changedate)'
I've noticed a few problems in the code you've posted:
You are declaring #Sql as varchar(max), but you are using the N prefix before setting it's value. You only need that when dealing with unicode data (nchar ,nvarcar). This is critical but you should be aware of this.
You are using convert without specifying the style parameter. It's not that bad when converting ints to strings, but it can cause unexpected behaviors when dealing with dates. Whenever you need to use string representation for date/datetime values, you should always use the ISO8601 standard, since it's guaranteed that Sql server will always convert it to date correctly, regardless of local settings. To convert a datetime value to ISO8061 standard, use style 126 in the convert statement.
You are using varchar without specifying the length. This is a bad habit since SQL Server have different default values to the length depending on context. It's 1 when declaring a variable, but 30 when used in cast and convert.
I've done some changes to your code, including changing the char(2) you've used to varchar(11) (because 11 chars will contain even the minimum value of the int data type which is -2147483648) for the #timeoffset parameter, and had no problems with it being negative.
Here is my test:
DECLARE #timeOffset int = -10,
#start_date datetime = getdate(),
#sql nvarchar(max)
SET #sql =
N'SELECT '''+ convert(char(23), #start_date, 126) +''' As GetDate,
DATEADD(HOUR, '+ CAST(#timeOffset as varchar(11)) +', '''+ convert(char(23), #start_date, 126) +''') As DateAdd';
SELECT #Sql
EXEC(#sql)
Results:
SELECT '2018-11-08T20:33:31.670' As GetDate,
DATEADD(HOUR, -10, '2018-11-08T20:33:31.670') As DateAdd
GetDate DateAdd
2018-11-08T20:33:31.670 08.11.2018 10:33:3
Related
In this below mentioned query, I am trying to use the paramter#PARAM_D inside the Execute section, but it throws the error:
Msg 402, Level 16, State 1, Line 5
The data types varchar and date are incompatible in the add operator.
What can be going wrong here? I am able to use other varchar parameters but not the date parameters only.
DECLARE #C VARCHAR(MAX)
DECLARE #PARAM_D DATE = '2021-07-31'
if 1=1
set #C = 'SELECT '+#PARAM_D +'as col1_X'
else
set #C = 'SELECT ''ABC'' as col1_x '
execute(#c)
print #PARAM_D
From + (Addition): "Cannot be used with date, time, datetime2, or datetimeoffset data types."
If you change the date value to a string you can concatenate it with the other strings:
set #C = 'select ''' + Cast( #Param_D as VarChar(30) ) + ''' as Col1_X;';
To make the actual value returned be a date data type you need to cast it back:
set #C = 'select Cast( ''' + Cast( #Param_D as VarChar(30) ) + ''' as Date ) as Col1_X;';
Aside: Although it isn't a problem here, you might want to read up on SQL Injection.
I'm receiving an error:
Conversion failed when converting the varchar value 'INSERT INTO TableRowCount (IntFieldID, DecimalField) SELECT 'to data type int"
Using the following code:
DECLARE #start INT -- #start is an INT
SET #start = 1 -- INT
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'INSERT INTO TableRowCount (IntFieldID, DecimalField)
SELECT ' + #start +', COUNT(*)
FROM dbo.somewhere' -- location is irrelevant
EXECUTE(#sql) -- this is where it fails
If I remove IntFieldID and the #start, it will work with an insert (though it defeats the purpose). I've tried including a SELECT CAST(' + #start + ' AS INT), which seems a little redundant since #start is an INT already (casting an INT as an INT), but that doesn't work either. I also tried beginning with an N' DYNAMIC-SQL, which didn't work, I tried using three ''' around everything (didnt' work), and in a few places that I read online, responses suggested putting the variable in the string, which generated the error:
Must declare scalar variable #start
(no surprise, as that didn't sound correct).
A better way than trying to concatenate an integer is to pass it in as a strongly-typed parameter:
DECLARE #start INT = 1;
DECLARE #sql NVARCHAR(MAX) = N'INSERT ...
SELECT #start, COUNT(*) FROM ' + #conn;
EXEC sp_executesql #sql, N'#start INT', #start;
You need to convert your #Start to a varchar.
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'INSERT INTO TableRowCount (IntFieldID, DecimalField)
SELECT ' + CAST(#start as nvarchar(20)) +', COUNT(*)
FROM ' + #conn
SQL Server implicitly converts between datatypes on concatenation or addition based on some fairly complex criteria. Suffice to say if you try to combine an int and a string it will always attempt to convert the string to an int unless you tell it otherwise explicitly.
Below is a conversion chart for your reference from MSDN.
I need to convert a SQL Server DATETIME value to FILETIME in a T-SQL SELECT statement (on SQL Server 2000). Is there a built-in function to do this? If not, can someone help me figure out how to implement this conversion routine as a UDF (or just plain Transact-SQL)? Here is what I know:
FILETIME is 64-bit value representing the number of 100-nanosecond intervals since
January 1, 1601 (UTC) (per MSDN: FILETIME Structure).
SQL Server base time starts on 1900-01-01 00:00:00 (per SELECT CAST(0 as DATETIME)).
I found several examples showing how to convert FILETIME values to T-SQL DATETIME (I'm not 100% sure they are accurate, though), but could not find anything about reverse conversion. Even the general idea (or algorithm) would help.
Okay, I think I was able to implement this myself. Here is the function:
IF EXISTS
(
SELECT 1
FROM sysobjects
WHERE id = OBJECT_ID('[dbo].[fnDateTimeToFileTime]')
AND type = 'FN'
)
BEGIN
DROP FUNCTION [dbo].[fnDateTimeToFileTime]
END
GO
-- Create function.
CREATE FUNCTION [dbo].[fnDateTimeToFileTime]
(
#DateTime AS DATETIME
)
RETURNS
BIGINT
BEGIN
IF #DateTime IS NULL
RETURN NULL
DECLARE #MsecBetween1601And1970 BIGINT
DECLARE #MsecBetween1970AndDate BIGINT
SET #MsecBetween1601And1970 = 11644473600000
SET #MsecBetween1970AndDate =
DATEDIFF(ss, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) *
CAST(1000 AS BIGINT)
RETURN (#MsecBetween1601And1970 + #MsecBetween1970AndDate) * CAST(10000 AS BIGINT)
END
GO
IF ##ERROR = 0
GRANT EXECUTE ON [dbo].[fnDateTimeToFileTime] TO Public
GO
It seems to be accurate up to 1 second, which is okay with me (I could not make it more accurate due to data overflow). I used the TimeAndDate web tool to calculate the durations between dates.
What do you think?
2 SQL Server time era starts on
1900-01-01 00:00:00 (per SELECT CAST(0
as DATETIME).
No, that is the base date, datetime starts at 1753
run this
select cast('17800122' as datetime)
output
1780-01-22 00:00:00.000
But this is still less than filetime so you need to add that...however remember the gregorian and Julian calendars (also the reason that datetime starts at 1753)
The accepted answer work well, but will crash for date above 19 January 2038. Either use
DATEDIFF_BIG instead of DATEDIFF if you are on SQL Server 2016 or above, or use the following correction
CREATE FUNCTION [dbo].[fnDateTimeToFileTime]
(
#DateTime AS DATETIME
)
RETURNS
BIGINT
BEGIN
IF #DateTime IS NULL
RETURN NULL
DECLARE #MsecBetween1601And1970 BIGINT
DECLARE #MsecBetween1970AndDate BIGINT
DECLARE #MaxNumberDayBeforeOverflowDateDiff int;
SET #MaxNumberDayBeforeOverflowDateDiff = 24855; --SELECT DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), CAST('2038-01-19 00:00:00' as DATETIME))
DECLARE #nbMaxDaysBetween1970AndDate int;
SET #nbMaxDaysBetween1970AndDate = DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) / #MaxNumberDayBeforeOverflowDateDiff;
DECLARE #moduloResteDay int
SET #moduloResteDay = DATEDIFF(day, CAST('1970-01-01 00:00:00' as DATETIME), #DateTime) % #MaxNumberDayBeforeOverflowDateDiff;
DECLARE #nbSecondBefore19700101And20380119 bigint = 2147472000;
SET #MsecBetween1601And1970 = 11644473600000;
DECLARE #DateTimeModulo datetime;
SET #DateTimeModulo = DATEADD(day, -#nbMaxDaysBetween1970AndDate * #MaxNumberDayBeforeOverflowDateDiff, #DateTime)
SET #MsecBetween1970AndDate = CAST(CAST(#nbMaxDaysBetween1970AndDate as bigint) * #nbSecondBefore19700101And20380119 +
DATEDIFF(ss, CAST('1970-01-01 00:00:00' as DATETIME), #DateTimeModulo) as bigint)*
CAST(1000 AS BIGINT)
RETURN (#MsecBetween1601And1970 + #MsecBetween1970AndDate) * CAST(10000 AS BIGINT)
END
Basically I want to use PRINT statement inside a user defined function to aide my debugging.
However I'm getting the following error;
Invalid use of side-effecting or time-dependent operator in 'PRINT'
within a function.
Can this not be done?
Anyway to aid my user defined function debugging?
Tip:
generate error.
declare #Day int, #Config_Node varchar(50)
set #Config_Node = 'value to trace'
set #Day = #Config_Node
You will get this message:
Conversion failed when converting the varchar value 'value to trace'
to data type int.
No, sorry. User-defined functions in SQL Server are really limited, because of a requirement that they be deterministic. No way round it, as far as I know.
Have you tried debugging the SQL code with Visual Studio?
I got around this by temporarily rewriting my function to something like this:
IF OBJECT_ID ('[dbo].[fx_dosomething]', 'TF') IS NOT NULL
drop function [dbo].[fx_dosomething];
GO
create FUNCTION dbo.fx_dosomething ( #x numeric )
returns #t table (debug varchar(100), x2 numeric)
as
begin
declare #debug varchar(100)
set #debug = 'printme';
declare #x2 numeric
set #x2 = 0.123456;
insert into #t values (#debug, #x2)
return
end
go
select * from fx_dosomething(0.1)
I have tended in the past to work on my functions in two stages. The first stage would be to treat them as fairly normal SQL queries and make sure that I am getting the right results out of it. After I am confident that it is performing as desired, then I would convert it into a UDF.
Use extended procedure xp_cmdshell to run a shell command. I used it to print output to a file:
exec xp_cmdshell 'echo "mytextoutput" >> c:\debuginfo.txt'
This creates the file debuginfo.txt if it does not exist. Then it adds the text "mytextoutput" (without quotation marks) to the file. Any call to the function will write an additional line.
You may need to enable this db-server property first (default = disabled), which I realize may not be to the liking of dba's for production environments though.
No, you can not.
You can call a function from a stored procedure and debug a stored procedure (this will step into the function)
On my opinion, whenever I want to print or debug a function. I will copy the content of it to run as a normal SQL script. For example
My function:
create or alter function func_do_something_with_string(#input nvarchar(max)) returns nvarchar(max)
as begin
-- some function logic content
declare #result nvarchar(max)
set #result = substring(#input , 1, 10)
-- or do something else
return #result
end
Then I just copy and run this out of the function to debug
declare #input nvarchar(max) = 'Some string'
-- some function logic content
declare #result nvarchar(max)
set #result = substring(#input , 1, 10)
-- this line is added to check while debugging
print #result
-- or do something else
-- print the final result
print #result
You can try returning the variable you wish to inspect.
E.g. I have this function:
--Contencates seperate date and time strings and converts to a datetime. Date should be in format 25.03.2012. Time as 9:18:25.
ALTER FUNCTION [dbo].[ufn_GetDateTime] (#date nvarchar(11), #time nvarchar(11))
RETURNS datetime
AS
BEGIN
--select dbo.ufn_GetDateTime('25.03.2012.', '9:18:25')
declare #datetime datetime
declare #day_part nvarchar(3)
declare #month_part nvarchar(3)
declare #year_part nvarchar(5)
declare #point_ix int
set #point_ix = charindex('.', #date)
set #day_part = substring(#date, 0, #point_ix)
set #date = substring(#date, #point_ix, len(#date) - #point_ix)
set #point_ix = charindex('.', #date)
set #month_part = substring(#date, 0, #point_ix)
set #date = substring(#date, #point_ix, len(#date) - #point_ix)
set #point_ix = charindex('.', #date)
set #year_part = substring(#date, 0, #point_ix)
set #datetime = #month_part + #day_part + #year_part + ' ' + #time
return #datetime
END
When I run it.. I get:
Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.
Arghh!!
So, what do I do?
ALTER FUNCTION [dbo].[ufn_GetDateTime] (#date nvarchar(11), #time nvarchar(11))
RETURNS nvarchar(22)
AS
BEGIN
--select dbo.ufn_GetDateTime('25.03.2012.', '9:18:25')
declare #day_part nvarchar(3)
declare #point_ix int
set #point_ix = charindex('.', #date)
set #day_part = substring(#date, 0, #point_ix)
return #day_part
END
And I get '25'. So, I am off by one and so I change to..
set #day_part = substring(#date, 0, #point_ix + 1)
Voila! Now it works :)
What's the equivalent of sortable datetime/timestamp varchar in PostgreSQL?
Console.WriteLine("{0:s}", DateTime.Now);
sample format output:
2000-05-04T15:30:59
This works, SELECT now::varchar, output is '2009-03-25', but only for date type, wondering what's the equivalent for timestamp.
Note, i know date is sortable in and of itself, I just encounter a DateTime incompatibility between .NET and Mono, so i'll just transport(Remoting) date/timestamp types as varchar, underlying database type is still proper date/timestamp field type. For those who encounter this same problem, the work-around is to cast the date to varchar when retrieving the data, and casting the varchar back to date when saving.
Basically you just use to_char function.
The problem with your example, is that while theoretically this should work:
select to_char( now(), 'YYYY-MM-DDTHH24:MI:SS' );
In reality - PostgreSQL cannot "understand" that T is separator and HH24 is next "tag", and prints it as:
2009-03-24THH24:32:45
But you can change it relatively simply to:
select translate( to_char( now(), 'YYYY-MM-DD HH24:MI:SS' ), ' ', 'T' );
which outputs requested format:
2009-03-24T21:33:21
Of course remembering always to use translate gets old pretty fast, so instead you can create your own function and use it instead:
create function custom_ts(timestamptz) returns text as $$
select translate( to_char( $1, 'YYYY-MM-DD HH24:MI:SS' ), ' ', 'T' );
$$ language sql;
# select custom_ts(now());
custom_ts
---------------------
2009-03-24T21:35:47
(1 row)
Or simply:
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD"T"HH24:MI:SS');