DATE vs. DATETIME casting of invalid dates in SQL SERVER 2008 R2 - sql

So, while I recognize that date formatting etc. should be done in the presentation layer, I am interested to know if anyone has seen or recognized this difference (please try at home, if so inclined) I am a little baffled and mostly curious, the sample code first.
UPDATE: To clarify based on the initial responses, I am aware the date IS invalid or better "not safe", since the particular field that I am more generally concerned about comes from user input." That is, while I am aware that validation/formatting aren't SQL 2008 strong suits, it is at least curious to me that DATETIME is more forgiving and I am wondering as to cause to see how forgiving."
DECLARE #RawValue NVARCHAR(30), #Value DATETIME;
SET #RawValue = '01/20.1901'
SET #Value = CAST(#RawValue AS DATETIME)
PRINT #Value
This produces the correct result for my server settings: Jan 20 1901 12:00AM
However if the penultimate line is changed to (replacing DATETIME with DATE):
SET #Value = CAST(#RawValue AS DATE)
Msg 241, Level 16, State 1, Line 8
Conversion failed when converting date and/or time from character string.
Is there an explanation out there? To be clear it doesn't matter if I DECLARE #Value to be a DATE or DATETIME or even an NVARCHAR -- Same result. The error message seems to suggest that it is having trouble converting the date AND/OR time, why would DATETIME behave any differently?
Thanks,

It is worth mentioning that DATE and DATETIME are completely different datatypes. DATE is not simply DATETIME with the time removed. For example, CAST('17520910' AS DATE) works, while the similar conversion to DATETIME does not. For you history buffs, there was no such day in England or her colonies, the calendar skipped from September 2nd to September 14. Unlike DATETIME, DATE goes back to the year 1 without considering calendar system.
Another important difference is the lack of implicit type conversion to add a number of days directly to a date. If D is datetime, D+3 is the date three days hence. If D is DATE, then D+3 produces an error.
I am assuming that since new code for implicit conversion was created from scratch for DATE that Microsoft simply made it a tad more fastidious.

can you tried with
DECLARE #RawValue NVARCHAR(30), #Value DATE;
SET #RawValue = '01.20.1901' -- or '01/20/1901'
SET #Value = CAST(#RawValue AS DATE)
PRINT #Value

your format is incorrect
use
SET #RawValue = '01/20/1901'
or
SET #RawValue = '01.20.1901'
instead of
SET #RawValue = '01/20.1901'
The problem is in SQL Server engine for parsing the datetime and cast it to Date

Related

SQL Server: passing string from variable into date - Conversion failed when converting date and/or time from character string

I'm looking to take a variable (in this case, a 4-digit year value) and pass it into a query where we're searching for rows based on a date, yet SQL Server seems to be having trouble taking that 4-digit year and putting it into the date field.
Here's an example:
DECLARE #yearstart AS VARCHAR(10) = '2022'
DECLARE #yearend AS VARCHAR(10) = '2023'
....
WHERE date_of_service BETWEEN '#yearstart-10-01' AND '#yearend-09-30'
SQL Server throws an error:
Conversion failed when converting date and/or time from character string.
Can anyone assist? Thank you in advance.
I was expecting 2022 to be passed in as plaintext to the query and just fill in the #yearstart and #yearend with 2022 and 2023 respectively.
Use numeric parameters and DATEFROMPARTS instead :
DECLARE #yearstart int = 2022
DECLARE #yearend int= 2023
....
where date_of_service
between DATEFROMPARTS(#yearstart,10,1) and DATEFROMPARTS(#yearend,9,30)
Parameters aren't format placeholders. Their values never become part of the query itself. The server compiles the SQL query into a reusable execution plan with parameters and only then executes it passing the parameter values to it. Next time the same query is encountered the server reuses the already compiled execution plan.

Date cast to int - why is it wrong?

I've got a question. Why does date CAST to int doesn't work while varchar to int works fine? e.g.
If we declare a variable and run this query it won't work,
declare #dateb datetime
set #dateb = getdate()
select cast(#dateb as int)
while this query will work fine. Why?
select cast(convert(varchar(8),#dateb,112) as int)
date should be 8 numbers.
Your issue comes from a misunderstanding about how SQL Server stores date information. It isn't stored as a readable date, but rather as an integer, or series of integers, that the engine uses to calculate the date in question, and then display in a human friendly way.
The CONVERT function you used above creates a text representation of the date as you're expecting to see it, then converts that representation to an integer (also as you're expecting to see it). The straight conversion from a date data type shows you the integer that SQL Server actually uses to store "today", but that's not what you're expecting.
There's a really good article about it on Robert Sheldon's blog if you're interested in going into more depth.

Casting 0 as DATE and DATETIME

I just happened to stumble upon this and couldn't find any technical explanation:
In SQL Server 2014:
SELECT CAST('' AS DATETIME);
1900-01-01 00:00:00.000
SELECT CAST(0 AS DATETIME);
1900-01-01 00:00:00.000
SELECT CAST('' AS DATE);
1900-01-01
SELECT CAST(CAST(0 AS DATETIME) AS DATE);
1900-01-01
SELECT CAST(0 AS DATE);
Msg 529, Level 16, State 2, Line 4
Explicit conversion from data type int to date is not allowed.
Why does the CAST from an empty string to DATE and DATETIME work, but from 0 it only works as DATETIME?
I'm interested in a technical explanation
I think it's a matter of what Microsoft chose to support. More or less it comes down to the fact that data conversions to datetime are allowed from numeric values, while date does not allow conversions from numeric values. Within SQL server, it must be converting the 0 first to a numeric value like 0.000000000 and then to the datetime equivalent of that number. My example shows that it's possible to convert the current date into a numeric value and then back to a datetime. However, converting to a date throws an error. I would guess to avoid rounding issues that you may have if you tried to convert the value 0.5 to a date. Would program the SQL engine to implicitly convert 0.5 to the date 1900-01-01 or 1900-01-02 in that case. You'd have to make arbitrary decisions on what date should be returned in that case.
This works:
--produces a numeric value like 42746.97660799
select cast(getdate() as numeric(18,8))
select cast(42746.97660799 as datetime)
While this throws the same error you received:
select cast(42746.97660799 as date)
Msg 529, Level 16, State 2, Line 5
Explicit conversion from data type numeric to date is not allowed.
Your first question, why you can't cast 0 to Date, it is an illegal cast from Int to Date (see the matrix in the linked doc)
I found your question after finding similar code and wondered why cast(0 as datetime) yielded 1900-01-01. I wanted to know whether this was a "standard" that I could trust or undefined "it works that way until we change it." I've even found other SO posts that suggest the Epoch is 1900, but unable to find any official documentation on this.
While reading the Cast/Convert documentation there is a reference to what Cast will do if the Year or the Time is missing (albeit in the varchar conversion section). When undefined, Date defaults to 1900-01-01 and Time to 00:00:00. (see subscript 6 in the linked doc). There's sample code in the article that casts a Time to Datetime and 1900-01-01 is generated for the date portion.
This (thankfully?!) yields 0 (whereas min-date of 1753 yields a large negative number)
select cast(cast('1900-01-01' as datetime) as Int)
Microsoft documentation titled "CAST and CONVERT (Transact-SQL)"
https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver15
It's maybe from timestamp behavioral.
You can cast 0 to datetime then cast to date.
Also this is a rule from Microsoft. An not a national rule.
I'm not sure there is much of clean technical explanation other than what SQL Server supports. SQL server ranks all of its data types Data Type Precendence. The implicit conversions have evolved over time. There is a Microsoft Blog by Craig Friedman that explains the logic and evolution pretty well More on Implicit Conversions. What #hamid_reza hobab indicates is essentially true. SQL server is supporting a conversion of an int to a datetime. Datetime is a higher precendence then date, so transitively datetime can be converted to the date. SQL server is not supporting an implicit or explicit conversion of int to date as indicated in the support matrix posted in the comment by #dfundako

Casting varchar as date

I think I've read just about every thread on this topic and none of them are solving my problem.
First of all, the database I'm working with has all date fields set to a varchar data type, which drives me insane because I constantly have to cast or convert whenever I'm working with any queries involving dates.
The query I'm working with at the moment is supposed to pull a list of medical patients who all have a certain diagnosis, but that diagnosis has to have been given before a certain date. Here's what I've got.
SELECT DISTINCT
pd.PatientID,
pd.PatientName,
pmh.DateOfOnset
pmh.DiagnosisCode
FROM PatientDemographic pd JOIN PatientMedicalHistory pmh ON pd.PatientID = pmh.PatientID
WHERE pmh.DiagnosisCode = '401.1'
AND CAST(pmh.DateOfOnset as Date) > CAST('12/31/2014' as Date)
I'm getting an error that says "Conversion failed when converting date and/or time from character string." It's telling me the error is on Line 1 though (the SELECT DISTINCT line) so that's not really helpful and I'm not sure what I need to fix.
As I mentioned, the DateOfOnset field has a varchar data type, and I need to find any of those dates that came before the end of December, 2014. I've tried to remedy this error by trying different combinations of the CAST statements -- I even tried including a cast on the date field in the SELECT statement, and I'm still getting the same error.
I was working on another query earlier that required me to find all patient appointments from within a certain time frame, and for that query, I had my WHERE clause set up like:
WHERE Cast(VisitDate as Date) BETWEEN CAST('01/01/2014' as Date) AND CAST('12/01/2014' as Date)
...and it worked perfectly fine. Since I've got my current query set up virtually the same way, I'm not sure why I'm getting that conversion error.
You have wrong dateformat:
SET DATEFORMAT dmy;
SELECT CAST('12/31/2014' as Date);
--Conversion failed when converting date and/or time from character string.
You could set it to mdy before executing your query.
SET DATEFORMAT mdy;
SELECT CAST('12/31/2014' as Date);
LiveDemo
or use CONVERT with style 101:
SET DATEFORMAT dmy;
SELECT CONVERT(DATE,'12/31/2014',101)
If you really need to store DATE as VARCHAR use at least culture independent type like ISO-8601.
SET DATEFORMAT dmy;
SELECT CAST('20140201' as Date);
-- 01.02.2014
SET DATEFORMAT mdy;
SELECT CAST('20140201' as Date)
-- 01.02.2014
It sounds like SQL is not able to convert the stored strings into dates. This would explain why CAST(pmh.DateOfOnset as Date) fails where Cast(VisitDate as Date) does not--the latter might not have any mis-formatted dates.
Best-case solution is to convert your table columns to the proper datatypes. Second-best case, add columns containing the proper datatypes, and convert the data over; you'd have to fix any existing bad data, as well as convert data on the fly as it's loaded (yuck). Another option, add a calculated column, though you'll have problems with the afore-mentioned invalid dates. (What version of SQL do you have? Later versions have the isdate function, which might help here.)
If modifying tables is not an option, you're probably stuck writing queries that have to assume some of the data is invalid (bad format).

SQL Server date formatting from string

We've recently migrated our database to a different server and since this I think the date format querying has changed somehow.
Previously we could use the following..
SELECT * FROM table WHERE date > 'YYYY-MM-DD'
However now we have to use..
SELECT * FROM table WHERE date > 'YYYY-DD-MM'
Can someone tell me what I need to change to get back to the previous version?
Try this one -
Query:
SET DATEFORMAT ymd
Read current settings:
DBCC USEROPTIONS
Output:
Set Option Value
-------------------------- -----------------
...
language us_english
dateformat ymd
...
You are right, the date format is different between the servers.
Lots of people fall into the trap of assuming that if you specify a date literal as 'YYYY-MM-DD', it will be interpreted as that regardless of the current date format. This is incorrect. SQL Server sees the 4 digits at the start of the string and correctly deduces that they represent the year. However, it then uses the current date format to tell which way round the month and day are. If you are in the UK, for example, this puts you in an awkward situation because you need a date format of DMY to interpret a date literal like 'DD-MM-YYYY', but a date format of MDY to interpret a date literal like 'YYYY-MM-DD'.
You have several options:
SET DATEFORMAT YMD, and don't let users enter dates any other way.
Use the ODBC date literal syntax {d'YYYY-MM-DD'}. This will be parsed correctly regardless of the current date format. CONVERT(DATE, 'YYYY-MM-DD', 120) has the same effect.
Remove all literal values from your queries and use parameters instead. This is by far the best alternative, and I strongly recommend it.
is you use different formats for the string then you can avoid this behaviour.
There are 2 iso formats that are always specific -- sql server will always parse them in the same way regardless of the server date format setting.
These are:
1) Short form : YYYYMMDD. Example '20120301' -- 1st March 2012
2) Long Form : YYYY-MM-DDTHH:MM:SS.msms'. Example '2012-03-01T12:13:00.000Z' -- 1st March 2012 at 13 minutes past 12 (PM)
In the long form the miliseconds is optional -- this is a perfectly acceptable ISO datetime '2012-03-01T12:13:00Z'
The Z at the end is time zone information. SQL Server doesn't actually require this. (though other products are a bit more exacting)
Try this for example:
DECLARE #foo DATETIME
SET DATEFORMAT DMY
-- this will be the 3rd of january in DMY
SET #foo = '2012-03-01'
SELECT 'DMY: Not ISO', #foo
SET #foo = '20120301'
SELECT 'DMY: ISO', #foo
SET DATEFORMAT MDY
-- this will be the 1st of March in MDY
SET #foo = '2012-03-01'
SELECT 'MDY: not ISO', #foo
SET #foo = '20120301'
SELECT 'MDY: ISO', #foo
When you use text to enter dates you should always try to use one of the two ISO standards. It just makes things much more deterministic.
Short format (SQL Server)
http://msdn.microsoft.com/en-US/library/ms187085(v=sql.90).aspx
ISO 8601 Format (SQL Server)
http://msdn.microsoft.com/en-us/library/ms190977(v=sql.90).aspx
It's a matter of language/culture
Set Language 'us_english'