I have created two functions to work with ISO 8601 dates:
CREATE FUNCTION IPUTILS_STR_TO_ISODATE (
#isostr VARCHAR(30))
RETURNS DATETIME
AS
BEGIN
RETURN CONVERT(DATETIME, #isostr, 126);
END;
GO
CREATE FUNCTION IPUTILS_ISODATE_TO_STR (
#date VARCHAR(30))
RETURNS VARCHAR(30)
AS
BEGIN
DECLARE #result VARCHAR(30);
SET #result = CONVERT(VARCHAR(30), #date, 126);
RETURN #result;
END;
GO
I don't get them working correct for some reason. If I do:
select dbo.IPUTILS_ISODATE_TO_STR(dbo.IPUTILS_STR_TO_ISODATE('1965-04-28T12:47:43'));
I get:
apr 28 1965 12:47PM
instead of:
1965-04-28T12:47:43
if I do:
select convert(VARCHAR(30), dbo.IPUTILS_STR_TO_ISODATE('1965-04-28T12:47:43'), 126);
I get:
1965-04-28T12:47:43
Is this a bug or am I doing something wrong?
Why are you not testing these functions individually first and then in combination? If you do test them individually you will likely see the problem ;-). Check the datatype of the #date input parameter on the IPUTILS_ISODATE_TO_STR function: it is VARCHAR(30) instead of DATETIME.
Having the incorrect datatype for the input parameter means that an implicit conversion from a real DATETIME into VARCHAR, but without a specified "style", is happening as the value comes into the function. This is the same as doing CONVERT(VARCHAR(30), #date). And the result of this implicit conversion (i.e. the value stored in #date) is being sent to the SET #result = CONVERT(VARCHAR(30), #date, 126); line.
Also, I would suggest not doing this in the first place (i.e. creating either of these functions) if they are going to be used in SELECT statements or WHERE clauses. Using the CONVERT() function in those places is repetitive, but also much faster. T-SQL scalar UDFs and Multiline TVFs do not perform well and you can slow down your queries by using them. In this particular case there is no real computation / formula being done so you aren't really gaining much outside of not needing to remember the "style" number. Also, T-SQL functions invalidate the query from getting a parallel execution plan. But if these are just being used in simple SET statements to manipulate a variable that is being used in a query, then that should be fine.
Related
I'm getting an error when I try to run a simple aggregating query.
SELECT MAX(CAST(someDate as datetime)) AS MAX_DT FROM #SomeTable WHERE ISDATE(someDate) = 1
ERROR: Conversion failed when converting date and/or time from character string.
Non-date entries should be removed by WHERE clause, but that doesn't seem to be happening. I can work around with an explicit CASE statement inside the MAX(), but I don't want to hack up the query if I can avoid it. If I use a lower COMPATIBILITY_LEVEL, it works fine. If I have fewer than 2^17 rows, it works fine.
-- SQLServer 15.0.4043.16
USE AdventureWorks;
GO
ALTER DATABASE AdventureWorks SET COMPATIBILITY_LEVEL = 150;
GO
-- delete temp table if exists
DROP TABLE IF EXISTS #SomeTable;
GO
-- create temp table
CREATE TABLE #SomeTable (
someDate varchar(20) DEFAULT GETDATE()
);
-- load data, need at least 2^17 rows with at least 1 bad date value
INSERT #SomeTable DEFAULT VALUES;
DECLARE #i int = 0;
WHILE #i < 17
BEGIN
INSERT INTO #SomeTable (someDate) SELECT someDate FROM #SomeTable
SET #i = #i + 1;
END
GO
-- create invalid date row
WITH cteUpdate AS (SELECT TOP 1 * FROM #SomeTable)
UPDATE cteUpdate SET someDate='NOT_A_DATE'
-- error query
SELECT MAX(CAST(someDate as datetime)) AS MAX_DT
FROM #SomeTable
WHERE ISDATE(someDate) = 1
--ERROR: Conversion failed when converting date and/or time from character string.
-- delete temp table if exists
DROP TABLE IF EXISTS #SomeTable;
GO
I would recommend try_cast() rather than isdate():
SELECT MAX(TRY_CAST(someDate as datetime)) AS MAX_DT
FROM #SomeTable
This is a much more reliable approach: instead of relying on some heuristic to guess whether the value is convertible to a datetime (as isdate() does), try_cast actually attempts to convert, and returns null if that fails - which aggregate function max() happily ignores.
try_cast() (and sister functions try_convert()) is a very handy functions, that many other databases are missing.
I actually just encountered the same issue (except I did cast to float). It seems that the SQL Server 2019 Optimizer sometimes (yes, it's not reliable) decides to execute the calculations in the SELECT part before it applies the WHERE.
If you set compatibility level to a lower version this also results in a different optimizer being used (it always uses the optimizer of the compatibility level). Older query optimizers seem to always execute the WHERE part first.
Seems lowering the compatibility level is already the best solution unless you want to replace all CAST with TRY_CAST (which would also mean you won't spot actual errors as easily, such as a faulty WHERE that causes your calculation to then return NULL instead of the correct value).
I am trying to combine a SmallDateTime field and a Time value (result of a scalar-valued function) into a DateTime and I keep getting the following error:
Conversion failed when converting date and/or time from character
string.
Here are the variables used throughout:
DECLARE #STARTDATETIME AS DATETIME
DECLARE #ENDDATETIME AS DATETIME
SELECT #STARTDATETIME = '8/29/2016 12:00:00'
SELECT #ENDDATETIME = '8/30/2016 12:00:00'
Column definitions:
FT_START_DATE SmallDateTime
FT_END_DATE SmallDateTime
FT_START_TIME Int
FT_END_TIME Int
The date fields do not contain timestamps. The time fields are basically 24 hour time without the colon dividers. (Example: 142350 = 14:23:50)
Here's the function that is called in my queries:
USE [PWIN171]
GO
/****** Object: UserDefinedFunction [dbo].[dbo.IPC_Convert_Time] Script Date: 9/13/2016 4:50:49 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[dbo.IPC_Convert_Time]
(
#time int
)
RETURNS time
AS
BEGIN
DECLARE #Result time
SELECT #Result = CONVERT(time
, STUFF(
STUFF(
RIGHT('000000' + CONVERT(varchar(6), #time), 6)
, 5, 0, ':')
, 3, 0, ':')
)
RETURN #Result
END
Example 1 - Fails:
This is what I'm after in general.
SELECT * FROM FT WITH (NOLOCK)
WHERE
CAST(FT_END_DATE AS DATETIME) + DBO.[dbo.IPC_Convert_Time](FT_END_TIME) BETWEEN #STARTDATETIME AND #ENDDATETIME;
Example 2 - Works:
This one runs, but it won't get records from 8/29 because the end dates will be before 12:00:00 on 8/29.
SELECT * FROM FT WITH (NOLOCK)
WHERE
FT_END_DATE BETWEEN #STARTDATETIME AND #ENDDATETIME
AND CAST(FT_END_DATE AS DATETIME) + DBO.[dbo.IPC_Convert_Time](FT_END_TIME) BETWEEN #STARTDATETIME AND #ENDDATETIME;
I suppose I could do one where I split apart my paramters and check that the end time is between the time portion of the parameters as well, but that seems to be a step in the wrong direction. The error seems to only appear when there is no other usage of FT_START_DATE or FT_END_DATE in the where clause.
The time converting function works fine in every scenario I have created. I have even tried Example 2 with parameters that would give it 100% overlap with the data covered by Example 1 in case there was bad data causing the error, but it runs fine.
I also don't know exactly where the error is occurring, because it only references the line the select statement begins on, and not the actual location in the code.
Why does it behave like this?
UPDATE:
TIMEFROMPARTS is not available because this is on SQL Server 2008
If I understand this correctly, this can be done much simpler:
Try this:
DECLARE #d DATE=GETDATE();
DECLARE #t TIME=GETDATE();
SELECT #d;
SELECT #t;
SELECT CAST(#d AS datetime)+CAST(#t AS datetime);
A pure date and a pure time can simply be added to combine them...
UPDATE Read your question again...
Try this
SELECT FT_END_DATE
,FT_END_TIME
,CAST(FT_END_DATE AS DATETIME) + DBO.[dbo.IPC_Convert_Time](FT_END_TIME) AS CombinedTime
,*
FROM FT
to see if your attempt is doing the right thing.
If yes, it might help to create a CTE and do the filter on the named column.
Sometimes the engine does not work the order you would expect this.
As CTEs are fully inlined it is quite possible, that this will not help...
SQL Server is well knwon for bringing up such errors, because a type check happens before a conversion took place...
It might be an idea to use the given SELECT with INTO #tbl to push the result set into a new table and do your logic from there...
I'm doing a search on a large table of about 10 million rows. I want to specify a start and end date and return all records in the table created between those dates.
It's a straight-forward query:
declare #StartDateTime datetime = '2016-06-21',
#EndDateTime datetime = '2016-06-22';
select *
FROM Archive.dbo.Order O WITH (NOLOCK)
where O.Created >= #StartDateTime
AND O.Created < #EndDateTime;
Created is a DATETIME column which has a non-clustered index.
This query took about 15 seconds to complete.
However, if I modify the query slightly, as follows, it takes only 1 second to return the same result:
declare #StartDateTime datetime = '2016-06-21',
#EndDateTime datetime = '2016-06-22';
select *
FROM Archive.dbo.Order O WITH (NOLOCK)
where O.Created >= '2016-06-21'
AND O.Created < #EndDateTime;
The only change is replacing the #StartDateTime search predicate with a string literal. Looking at the execution plan, when I used #StartDateTime it did an index scan but when I used a string literal it did an index seek and was 15 times faster.
Does anyone know why using the string literal is so much faster?
I would have thought doing a comparison between a DATETIME column and a DATETIME variable would be quicker than comparing the column to a string representation of a date. I've tried dropping and recreating the index on the Created column and it made no difference. I notice I get similar results on the production system as I do on the test system so the weird behaviour doesn't seem specific to a particular database or SQL Server instance.
All variables have instances that they are recognized.
In OOP languages, we usually distinguish between static/constant variables from temporary variables by using keywords, or when a variable is called into a function where inside that instance the variable is treated as a constant if the function transforms that variable, such like the following in C++:
void string MyFunction(string& name)
//technically, `&` calls the actual location of the variable
//instead of using a logical representation. The concept is the same.
In SQL Server, the Standard chose to implement it a bit differently. There are no constant data types, so instead we use literals which are either
object names (which have similar precedence in the call as system keywords)
names with an object deliminator (including ', [])
or strings with a deliminator CHAR(39) (').
This is the reason you noticed that the two queries produce different results, because those variables are not constants to the Optimizer, which means SQL Server will already have chosen it's execution path beforehand.
If you have SSMS installed, include the Actual Execution Plan (CTRL + M), and notice in the select statement what the Estimated Rows are. This is the highlight of the execution plan. The greater difference between the Estimated and Actual rows, the more likely your query can use optimization. In your example, SQL Server had to guess how many rows, and ended up overshooting the results, losing efficiency.
The solution is one and the same, but you can still encapsulate everything if you wanted to. We use the AdventureWorks2012 for this example:
1) Declare the Variable in the Procedure
CREATE PROC dbo.TEST1 (#NameStyle INT, #FirstName VARCHAR(50) )
AS
BEGIN
SELECT *
FROM Person.PErson
WHERE FirstName = #FirstName
AND NameStyle = #NameStyle; --namestyle is 0
END
2) Pass the variable into Dynamic SQL
CREATE PROC dbo.TEST2 (#NameStyle INT)
AS
BEGIN
DECLARE #Name NVARCHAR(50) = N'Ken';
DECLARE #String NVARCHAR(MAX)
SET #String =
N'SELECT *
FROM Person.PErson
WHERE FirstName = #Other
AND NameStyle = #NameStyle';
EXEC sp_executesql #String
, N'#Other VARCHAR(50), #NameStyle INT'
, #Other = #Name
, #NameStyle = #NameStyle
END
Both plans will produce the same results. I could have used EXEC by itself, but sp_executesql can cache the entire select statement (plus, its more SQL Injection safe)
Notice how in both cases the level of the instance allowed SQL Server to transform the variable into a constant value (meaning it entered the object with a set value), and then the Optimizer was capable of choosing the most efficient execution plan available.
-- Remove Procs
DROP PROC dbo.TEST1
DROP PROC dbo.TEST2
A great article was highlighted in the comment section of the OP, but you can see it here: Optimizing Variables and Parameters - SQLMAG
i have the ff stored procedure
create procedure InsertDetails(
#name varchar(50),
#lastname varchar(50),
#starttime time(7),
#endtime time(7)
)
AS
BEGIN
insert into table (
name,
lastname,
starttime,
endtime
)
values(
#name,
#lastname ,
#starttime,
#endtime
)END
when the stored procedure is executed i enter the following parameters, however i am not sure for the starttime and end time as to what is the format to enter the time in. i tried
08:00:00 and 08-00-00
but i get an error for both Incorrect syntax near ':'.
can you tell me the format its expecting
There's no syntax for specifying time literals1 - you'll need to give a character literal and have the system convert them to time for you.2
So, it would be '08:00:00', for example.
Of course, if you're executing this stored procedure from some other language, then you should see if there are appropriate bindings to allow you to pass the data across as a time parameter (or local language equivalent) rather than passing it as a string at all.
1Bizarrely, T-SQL calls these Constants, but since just about every other language uses the term literals, I choose to use that term instead.
2You don't have to have the system perform the conversion. You may perform an explicit conversion if you want to, but I usually find that this doesn't add anything to the query, except noise.
I got Why you get this error because you did;t mention it into single quote ' '
you are Doing this exec Like exec InsertDetails 's','s',08:00:00,08:00:00
Try to exec Like exec InsertDetails 's','s','08:00:00','08:00:00'
Error Demo
Working Demo
I cannot store the date data type variables using stored procedure. My code is:
ALTER PROCEDURE [dbo].[Access1Register]
-- Add the parameters for the stored procedure here
#MobileNumber int,
#CitizenName varchar(50),
#Dob char(8),
#VerificationCode int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
select CAST(#dob As DATE)
Insert Into Access1 (MobileNo,CitizenName,Dob,VerificationCode)
values(#MobileNumber,#CitizenName,#Dob,#VerificationCode)
go
If I exec this procedure it is executing, but there is an error occured in the date type variable. It's raising the error as invalid item '-'.
It depends on how you pass in the #Dob values.
Your best bet is to use the ISO8601 format - 'YYYYMMDD' - since that will always convert to DATE properly - regardless of your language and regional settings on your SQL Server machine.
It depends on the date format that you pass.
If it is 'mm-dd-yy' you can use CONVERT(DATE, #Dob, 110), if it is 'dd-mm-yy' then CONVERT(DATE, #Dob, 105).
In which format you are passing the #Dob values? And what error you are getting exactly?
If you will pass the #Dob values in the format of mm-dd-yy, it should work correctly and no errors will be there.