How does T-SQL implicit conversion / overload resolution work? - sql-server

I've been toying around with T-SQL in an attempt to understand the implicit conversion and overload resolution rules, but somehow it seems to work a bit strange...
Context:
Data type conversion: https://msdn.microsoft.com/en-us/library/ms191530.aspx
Data type precedence: https://msdn.microsoft.com/en-us/library/ms190309.aspx
Abs: https://msdn.microsoft.com/en-us/library/ms189800.aspx
Basically the latter tells you that abs will work on int, float, decimal, etc. So let's see how that works:
declare #foo2 sql_variant;
set #foo2 = abs(4);
select sql_variant_property(#foo2, 'BaseType')
-- result: int. OK, apparently we have an int overload. As expected.
declare #foo2 sql_variant;
set #foo2 = abs(cast(4.0 as float));
select sql_variant_property(#foo2, 'BaseType')
-- result: float. OK, apparently we have a float overload. As expected.
Now, according to the implicit type conversion table, we are allowed to implicitly convert stuff. We're going to check this by converting a varbinary to int, which should happen according to the type precedence rules:
declare #foo varbinary(4);
set #foo = cast(4 as varbinary(4));
select #foo + 2;
-- result: int. OK, as expected.
From this result I would expect the following to work as well:
declare #foo varbinary(4);
set #foo = cast(4 as varbinary(4));
select abs(#foo);
-- result: error: Operand type clash: varbinary is incompatible with float
Stuff I don't understand here (the question):
Why does the implicit conversion pick the 'float' overload of the 'abs'? Is this just a random overload? Or perhaps the overload that's highest in the precedence list (which happens to be 'float')?
Why isn't implicit conversion applied from varbinary -> int? After all, it's a perfectly valid conversion.

From MSDN for Abs:
Arguments
numeric_expression
Is an expression of the exact numeric or approximate numeric data type category.
I guess that calling Abs( varbinary ) would try to convert the varbinary to an exact numeric or approximate numeric data type.
Float is at the top of Data Type Precedence for these types so there is the problem I guess.
UPDATE with my logic
declare #foo varbinary(4);
Declare a varbinary variable.
set #foo = cast( 4 as varbinary(4));
Set to this variable a value by performing an explicit cast from int to varbinary which is perfectly doable.
select sql_variant_property(#foo, 'BaseType')
This shows that the variable is of type varbinary.
select abs(#foo);
Try to run abs on varbinary.
The error message is clear:
Operand type clash: varbinary is incompatible with float
So my guess is that abs is trying to implicit convert varbinary to the first of the Data Type Precedence for exact numeric or approximate numeric data types which is float.
This conversion fails according to CAST and CONVERT chart.

I hope I've got this right! Comments appreciated.
You've found the conversion and precedence MSDN pages so let's take your example apart.
declare #foo varbinary(4);
set #foo = cast(4 as varbinary(4));
Now, what type is foo?
SELECT SQL_VARIANT_PROPERTY(#foo,'BaseType')
--------
varbinary
Good so far so good. But what's 'in' #foo ?
SELECT #foo
----------
0x00000004
Now, ABS() accepts a numeric - a floating point number:
What does 4.00 look like compared to 4? Let's try:
SELECT CONVERT(VARBINARY(MAX), 4.00) ,CONVERT(VARBINARY(MAX), 4);
------------------ -----------------------
0x0302000190010000 0x00000004
See the internal representation of 4.00 is different to 4? It's storing precision, scale and the value whereas your 'int in a varbinary' isn't.
And that's why the conversion fails. ABS() accepts a numeric, your 'wrong format' varbinary cannot be coerced to a numeric as it's not one, it's some other representation - we know it represents an int but SQL Server doesn't.
If you want to add a floating point number to an int that's 'inside' a varbinary, you have to do something like
select CONVERT(INT, #foo) + 2.0
Back to your example:
declare #foo varbinary(max);
set #foo = cast(4.00 as varbinary(max));
SELECT ABS(#foo+2.0)
----------
6.0
4.00 is converted into a varbinary in the correct internal format, and happily coerced into a numeric by the ABS() call. Happy days.

Related

T-SQL Scientific notation field conversion

I load excel file into sql as varchar(max) and got that Scientific e value which now I try to convert into numeric as I need to do compare that value, and here I'm running into problem.
This is main question: How and to what type I can convert this to compare with whole integer value ?
On the pic You can see how this seen in Excel, even formatted to text it somehow still loaded into varchar(max) not like char string. This can be seen from my test code.
DECLARE #C VARCHAR(MAX) = '1.1001562717e+011', #Nc VARCHAR(MAX) = '110015627174';
SELECT #c, LEN(#c) LenC ,
ISNUMERIC(#c) NumYN
---, CAST(#c AS DECIMAL(38,2)) cDec ---CAST(#c AS NUMERIC) cNum --, CAST(#c AS BIGINT) cInt
WHERE #c LIKE '%[^0-9]%'
AND ISNUMERIC(#c) = 1
To start, ISNUMERIC is a terrible function, it does not give good results; it is often wrong. If you try ISNUMERIC('1.1001562717e+011') you'll notice that you get the value 1, however, CONVERT(numeric(13,1),'1.1001562717e+011') will produce an error. A far better function is TRY_CONVERT (or TRY_CAST), which returns NULL if the conversion fails for the specific data type: TRY_CONVERT(numeric(13,1),'1.1001562717e+011').
Being specific on the data type is actually important here, as ISNUMERIC could be (incorrectly) suggesting that the value could be converted to at least 1 of the numeric data types; but that doesn't mean all of them. For scientific data types the only data type you can convert to is a float/real:
SELECT TRY_CONVERT(numeric(13,1),'1.1001562717e+011') AS Numeric,
TRY_CONVERT(bigint,'1.1001562717e+011') AS int,
TRY_CONVERT(float,'1.1001562717e+011') AS float,
TRY_CONVERT(money,'1.1001562717e+011') AS money;
Notice that only float has a value here. As you want a numeric as the final value, then you'll need to CONVERT the value twice:
CONVERT(numeric(13,1),TRY_CONVERT(float,'1.1001562717e+011'))

How to convert datetime to int in sql server2012?

Here is my question:
This is a table named HB06,the data type of "WRTime" is datatime.I want to convert all WRTime to int. For example 2012-11-09 10:52:38.000 will be converted to 20121109105238.
Thank you!
You can't. The value is too large for the int data type. But it does fit into a bigint.
What you can do is to convert it to a string with the desired format, and then cast that ti the bigint type. Using the FORMAT function is IMO more straight forward:
DECLARE #a datetime = '20120304 23:34:12'
SELECT #a
SELECT CAST(FORMAT(#a, 'yyyyMMddhhmmss') AS bigint)
The alternative is to use CONVERT function, which uses less CPU. But there's not direct style that matches that format, so you would then REPLACE() various "litter" characters with nothing. I wouldn't bother with the CONVERT() option unless you work over large data sets.
select
cast(replace(replace(replace(convert(varchar(19), WRTime, 121),':',''),'-',''),' ','') as bigint)
FROM HB06
You can try with below one
select concat(convert(varchar,WRTime,112),datepart(HH,WRTime),
datepart(MINUTE,WRTime),datepart(SS,WRTime)) from HB06
Use below Convert function:
SELECT CONVERT(VARCHAR(100),WRTime,112)+REPLACE(CONVERT(VARCHAR(100),WRTime,108),':','')
FROM HB06
SQL Version 2012 or higher you can use the FORMAT function to get just year and month, then cast it as an int.
On versions prior to 2012 you can do the formatting with the convert function, then cast as int.
declare #WRTime datetime
set #WRTime = '2012-11-09 10:52:38.000'
select cast(format(#WRTime,'yyyyMM') as int) --2012 or higher
OR You can use:
SELECT YEAR(#WRTime)*100 + MONTH(#WRTime);
2012-11-09 10:52:38.000 will be converted to 20121109105238??
Int can't convert this so use "BIGINT"
declare #WRTime datetime
set #WRTime = '2012-11-09 10:52:38.000'
select cast(format(#WRTime,'yyyyMMddHHmmssfff') as bigint) --2012 or higher

Cast giving an error

Why is this simple code giving me an error:
Conversion failed when converting the varchar value '3.0' to data type
int.
select cast(value as INT)
from CV3BasicObservation (nolock)
where value >= 110
SQL Server doesn't want to convert a number string that looks like a decimal to integer because you could lose precision. You could trick it with the round function:
select cast(round(value,0) as INT)
from CV3BasicObservation (nolock)
where cast(round(value,0) as INT) >= 110
NOTE: You have to do it to all instances of the field value where you are explicitly converting it to int or where it is implicitly converting it for comparison an int type value.

'<number>D' causes CAST to error

I'm finding this to be truly bizarre behaviour.
Here is my T-SQL:
declare #testText nvarchar(1000);
set #testText = '17D4,A,1';
select txt_value from fn_ParseText2Table (#testText , ',' )
where fn_ParseText2Table is a function that parses the text into a table where you can get the txt, int, and floating point values, if they work.
The 17D4 is a product code that I'm trying to extract within a larger query, and all other 3817 records work fine.
select (
select txt_value
from fn_ParseText2Table(t.primaryKeyValues , ',' ) as pk
where position = 1) as product_NBR
from database.dbo.tablesToParse as t
where t.tableName = 'ProductData'
I found the function here.
What I've found is that if the string starts with some numbers (I've tested anywhere from 1-4 ) followed by 'D', it fails with the 'Error converting data type varchar to numeric.' message.
All other combinations of text work. Lower case d is fine. C is fine, E, F, etc. So '17F5,A,1' is fine. Also 'asdf 17D5,A,1' is fine. '1D,A,1' is not fine.
I'm very confused. Is there a special escape character in T-SQL for 'D'?
Update:
I should clarify that the error occurs inside fn_ParseText2Table()
Update 2
It's SQL server 10 - 64 bit, running on a windows 2008 server.
As well, I've tested this in a sql mgr query window:
declare #testText nvarchar(1000);
set #testText = '17D4';
select isnumeric( #testText )
The IsNumeric() call returns 1, which is why the fn_ParseText2Table() function tries to cast it to an in and it fails. I could add an extra check to that function to lower the text first and see if that's also numeric.
I figured it was related to floating-point literals but I was surprised it wouldn't cast to a numeric type. I guess it only works when float is the target type. You'll find the explanation here:
http://www.sqlservercentral.com/Forums/Topic202581-8-1.aspx
This behavior doesn't match up with other SQL Server literals, ie, constants:
select 1d -- literal 1 with d treated as the column alias
select 1e -- literal 1.0 as a float
select cast('1d' as float), cast('1e' as float) -- neither of these will cast without an exponent value
select cast('1d0' as float), cast('1e0' as float) -- these work though

Format string in SQL Server 2005 from numeric value

How I can format string with D in start and leading zeros for digits with length of less than four. E.g:
D1000 for 1000
D0100 for 100
I have tried to work with casting and stuff function, but it didn't work as I expected.
SELECT STUFF('D0000', LEN(#OperatingEndProc) - 2, 4, CAST((CAST(SUBSTRING(#OperatingEndProc, 2, 4) AS INT) + 1) AS VARCHAR(10)));
adding 10000 to the value will cause the number to have have extra zeros first, then casting it as varchar and only using the last 4 will ignore the added 10000. This require that all numbers are between 0 and 9999
declare #value int = 100
select 'D' + right(cast(#value + 10000 as varchar(5)), 4)
This illustration board can come in handy when you wanna get the proper casting practices..
This shows all explicit and implicit data type conversions that are
allowed for SQL Server system-supplied data types. These include xml,
bigint, and sql_variant. There is no implicit conversion on assignment
from the sql_variant data type, but there is implicit conversion to
sql_variant
You can download it here http://www.microsoft.com/en-us/download/details.aspx?id=35834

Resources