VARCHAR vs VARCHAR(X) - sql

This results in 'B':
DECLARE #NAME VARCHAR(20)=' s'
IF (#NAME IS NULL OR #NAME='')
SELECT 'A'
ELSE
SELECT 'B'
Whereas, this results in 'A'.
DECLARE #NAME VARCHAR=' s'
IF (#NAME IS NULL OR #NAME='')
SELECT 'A'
ELSE
SELECT 'B'
The only difference is VARCHAR(20) vs VARCHAR.
What is the reason of this odd behaviour?

Sql Server defaults a VARCHAR of unspecified length to a length of 1. And when taken in conjunction with Microsoft's interpretation of ANSI/ISO SQL-92 (ref here) which results in padding compared strings to equal length during equality comparisons, resulting in ' ' being = to '', hence the non-intuitive 'A' in the second test.

VARCHAR needs a size to be specified when there is no size specified it assume that the declaration is of one character length.
To confirm this, you can check the length of the variables.
Since, your if condition is checking for '' it satisfies the condition.
DECLARE #NAME VARCHAR=' s'
select datalength(#name)
returns 1
DECLARE #NAME VARCHAR(20)=' s'
select datalength(#name)
returns 2

Related

TSQL CTE error ''Types don't match between the anchor and the recursive part"

Would someone help me understand the details of the error below..? This is for SQL Server 2008.
I did fix it myself, and found many search hits which show the same fix, but none explain WHY this happens in a CTE.
Types don't match between the anchor and the recursive part in column "txt" of recursive query "CTE".
Here is an example where I resolved the issue with CAST, but why does it work?
WITH CTE(n, txt) AS
(
--SELECT 1, '1' --This does not work.
--SELECT 1, CAST('1' AS varchar) --This does not work.
--SELECT 1, CAST('1' AS varchar(1000)) --This does not work.
SELECT
1,
CAST('1' AS varchar(max)) --This works. Why?
UNION ALL
SELECT
n+1,
txt + ', ' + CAST(n+1 AS varchar) --Why is (max) NOT needed?
FROM
CTE
WHERE
n < 10
)
SELECT *
FROM CTE
I assume there are default variable types at play which I do not understand, such as:
what is the type for something like SELECT 'Hello world! ?
what is the type for the string concatenation operator SELECT 'A' + 'B' ?
what is the type for math such as SELECT n+1 ?
The info you want is all in the documentation:
When concatenating two char, varchar, binary, or varbinary expressions, the length of the resulting expression is the sum of the lengths of the two source expressions, up to 8,000 bytes.
snip ...
When comparing two expressions of the same data type but different lengths by using UNION, EXCEPT, or INTERSECT, the resulting length is the longer of the two expressions.
The precision and scale of the numeric data types besides decimal are fixed. When an arithmetic operator has two expressions of the same type, the result has the same data type with the precision and scale defined for that type.
However, a recursive CTE is not the same as a normal UNION ALL:
The data type of a column in the recursive member must be the same as the data type of the corresponding column in the anchor member.
So in answer to your questions:
'Hello world!' has the data type varchar(12) by default.
'A' + 'B' has the data type varchar(2) because that is the sum length of the two data types being summed (the actual value is not relevant).
n+1 is still an int
In a recursive CTE, the data type must match exactly, so '1' is a varchar(1). If you specify varchar without a length in a CAST then you get varchar(30), so txt + ', ' + CAST(n+1 AS varchar) is varchar(33).
When you cast the anchor part to varchar(max), that automatically means the recursive part will be varchar(max) also. You don't need to cast to max, you could also cast the recursive part directly to varchar(30) for example:
WITH CTE(n, txt) AS
(
--SELECT 1, '1' --This does not work.
SELECT 1, CAST('1' AS varchar(30)) --This does work.
--SELECT 1, CAST('1' AS varchar(1000)) --This does not work.
UNION ALL
SELECT
n+1,
CAST(CONCAT(txt, ', ', n+1) AS varchar(30))
FROM
CTE
WHERE
n < 10
)
SELECT *
FROM CTE
db<>fiddle
If you place the query into a string then you can get the result set data types like with the query :
DECLARE #query nvarchar(max) = 'SELECT * FROM table_name';
EXEC sp_describe_first_result_set #query, NULL, 0;

Data type error when passing dynamic column names into SQL Server

I've run into an issue while executing a stored procedure from VBA: I want to pass in a column name as a string for the parameter, and then use a case statement to select the actual column name in the data.
This query works fine when the column name (#FACTOR) i'm passing through is an integer, but not when it's a varchar. I get a conversion error:
Error converting data type nvarchar to float.
Here's my code:
WITH T0 AS (
SELECT DISTINCT
CASE #FACTOR
WHEN 'DRVREC' THEN DRIVINGRECORD --OK
WHEN 'POAGE' THEN POAGE
WHEN 'ANNUALKM' THEN AMC_VH_ANNL_KM
WHEN 'DAILYKM' THEN AMC_VH_KM_TO_WRK
WHEN 'RATETERR' THEN AMC_VH_RATE_TERR --OK
WHEN 'BROKERNAME' THEN MASTERBROKER_NAME
WHEN 'DRVCLASS' THEN DRIVINGCLASS -- OK
WHEN 'VEHAGE' THEN VEH_AGE -- OK
WHEN 'YEARSLIC' THEN YRSLICENSE
WHEN 'COVERAGECODE' THEN COVERAGECODE
ELSE NULL END AS FACTOR FROM DBO.Automation_Data
),
...
...
Or perhaps the example below is more concise:
DECLARE #FACTOR varchar(50)
SELECT #FACTOR = 'NOT_A_VARCHAR'
SELECT CASE #FACTOR
WHEN 'A_VARCHAR' THEN COLUMNNAME1
WHEN 'NOT_A_VARCHAR' THEN COLUMNNAME2
ELSE NULL END AS FACTOR FROM dbo.myTable
^ This would work, but if #FACTOR = 'A_VARCHAR' then i get the error.
Thanks in advance!
UPDATE **********************************:
It appears to be an issue with the case statement itself?
When I only have the varchar option in my case statement, the query runs. When I un-comment the second part of the case statement I get the error.
DECLARE #FACTOR varchar(50)
SELECT #FACTOR = 'A_VARCHAR'
SELECT CASE #FACTOR
WHEN 'A_VARCHAR' THEN COLUMNNAME1
--WHEN 'NOT_A_VARCHAR' THEN COLUMNNAME2 ELSE NULL
END AS FACTOR FROM dbo.myTable
When you are selecting from multiple columns as a single column like you are doing, SQL returns the result as the highest precedence type. Same goes with coalesce etc. when a single result is to be returned from multiple data types.
If you try the code below for example, 3rd select will return the error you're getting, as it tries to convert abc to int (higher precedence). If you set #V to '123', error will go away, as the convert from '123' to int/float works. When you check the 'BaseType' of the result, you can see it shows the highest precedence data type of the mixed types.
DECLARE #F int = 1 --if you use float here error message will show ...'abc' to data type float.
DECLARE #V varchar(5) = 'abc'
DECLARE #O varchar = '1'
SELECT CASE WHEN #O = '1' THEN #F ELSE #V END --no error
SELECT SQL_VARIANT_PROPERTY((SELECT CASE WHEN #O = '1' THEN #F ELSE #V END), 'BaseType') --int/float
SET #O = '2'
SELECT CASE WHEN #O = '1' THEN #F ELSE #V END --error: Conversion failed when converting the varchar value 'abc' to data type int.
When you converted all your selects to nvarchar, nvarchar became the highest precedence data type, so it worked. But if you know some of your columns are float and some of them nvarchar, you only need to convert float columns to nvarchar. So this will work as well:
SET #O = '2'
SELECT CASE WHEN #O = '1' THEN CONVERT(NVARCHAR(5), #F) ELSE #V END
See SQL Data Type Precedence

SQL Server compare varchar field with binary(16)

I have 2 tables - Table A with primary key column of type binary(16) and another table B with foreign key referring to the same column but with data type as varchar(50). So table A has values like 0x0007914BFFEC4603A6900045492EFA1A and table B has the same value stored as 0007914BFFEC4603A6900045492EFA1A.
How do i compare these 2 columns, which would give me
0007914BFFEC4603A6900045492EFA1A = 0x0007914BFFEC4603A6900045492EFA1A
You will need to convert the binary(16) to a string. A sample of how to do this can be found in the question below. This question converts a varbinary to a string, but the same technique can be used for a binary column or variable:
SQL Server converting varbinary to string
Example code for how to do this is below:
declare #bin binary(16), #str varchar(50)
set #bin = 0x0007914BFFEC4603A6900045492EFA1A
set #str = '0007914BFFEC4603A6900045492EFA1A'
select #bin as'binary(16)', #str as 'varchar(50)'
-- the binary value is not equal to the string value
-- this statement returns 'binary value is not equal to string'
if #bin = #str select 'binary value is equal to string'
else select 'binary value is not equal to string'
declare #binstr varchar(50)
select #binstr = convert(varchar(50), #bin, 2)
select #binstr
-- the converted string value matches the other string
-- the result of this statement is "converted string is equal"
if #binstr = #str select 'converted string is equal'
else select 'converted string is NOT equal'
To use this in a join, you can include the conversion in the ON clause of the inner join or in a WHERE clause:
select *
from TableA
inner join TableB
on TableB.char_fk = convert(varchar(50), TableA.bin_pk, 2)
UPDATE
For SQL Server 2005, you can use an XML approach shown by Peter Larsson here:
-- Prepare value
DECLARE #bin VARBINARY(MAX)
SET #bin = 0x5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8
-- Display the results
SELECT #bin AS OriginalValue,
CAST('' AS XML).value('xs:hexBinary(sql:variable("#bin"))', 'VARCHAR(MAX)') AS ConvertedString
You can also use the undocumented function sys.fn_varbintohexstr, but as this post on dba.stackexchange.com explains, there are several reasons why you should avoid it.
CONVERT with style 2 to get a binary representation of the hexadecimal string;
... where TableA.bin_pk = CONVERT(VARBINARY, TableB.char_fk, 2)
The correct aproach is to set both fields in the same datatype. in order to to do this create a new table say temp and use select into and convert:
select field1,...,convert(varchar(50),varbinary(16),fieldToConvert)...,fieldN
into myNewTable
Found the answer. I need to use
master.dbo.fn_varbintohexstr (#source)
which converts a varbinary to varchar, and then works perfectly well for comparison in my scenario.

Repeating characters in T-SQL LIKE condition

Problem:
Limit the value of a VARCHAR variable (or a column) to digits and ASCI characters, but allow variable length.
This script will not yield required result:
declare #var as VARCHAR (150)
select #var = '123ABC'
if (#var LIKE '[a-zA-Z0-9]{0,150}')
print 'OK'
else
print 'Not OK'
Anyone have idea how to do this?
You can do this with the not carat ^, and a NOT LIKE expression.
So you say, where not like not non-alphanumeric ;) This works for standard numbers & characters:
declare #var as VARCHAR (150)
select #var = '123ABC'
if (#var NOT LIKE '%[^a-zA-Z0-9]%')
print 'OK'
else
print 'Not OK'
Edit: Thanks Martin for the collation hint, if you want the characters like ý treated as non-alphanumeric add in the COLLATE as below
declare #var as VARCHAR (150)
select #var = '123ABCý'
if (#var NOT LIKE '%[^a-zA-Z0-9]%' COLLATE Latin1_General_BIN )
print 'OK'
else
print 'Not OK'
Will this help
Declare #t table (Alphanumeric VARCHAR(100))
Insert Into #t
Select '123ABCD' Union All Select 'ABC' Union All
Select '123' Union All Select '123ABCý' Union All
Select 'a-z123' Union All Select 'abc123' Union All
Select 'a1b2c3d4'
SELECT Alphanumeric
FROM #t
WHERE Alphanumeric LIKE '%[a-zA-Z0-9]%'
AND ( Alphanumeric NOT LIKE '%[^0-9a-zA-Z]%' COLLATE Latin1_General_BIN)
AND LEN(Alphanumeric)> 6 -- display records having more than a length of 6
//Result
Alphanumeric
123ABCD
a1b2c3d4
N.B.~ Used Martin's collation hint..Thanks
T-SQL doesn't support RegEx.
You can use SQL CLR to run such expression though.
Also try the LEN function:
if (LEN(#var) <= 150)
print 'OK'
else
print 'Not OK'
T-SQL doesn’t support regex, closest you can get is the PATINDEX function that you can use to match specific characters, but you can’t specify the count.
You can try combining it with the LEN function to check the length.
See this page for a few examples of PATINDEX.

SQL for nvarchar 0 = '' & = 0?

I was searching for integers in a nvarchar column. I noticed that if the row contains '' or 0 it is picked up if I search using just 0.
I'm assuming there is some implicit conversion happening which is saying that 0 is equal to ''. Why does it assign two values?
Here is a test:
--0 Test
create table #0Test (Test nvarchar(20))
GO
insert INTO #0Test (Test)
SELECT ''
UNION ALL
SELECT 0
UNION ALL
SELECT ''
Select *
from #0Test
Select *
from #0Test
Where test = 0
SELECT *
from #0Test
Where test = '0'
SELECT *
from #0Test
Where test = ''
drop table #0Test
The behavior you see is the one describe din the product documentation. The rules of Data Type Precedence specify that int has higher precedence than nvarchar therefore the operation has to occur as an int type:
When an operator combines two expressions of different data types, the
rules for data type precedence specify that the data type with the
lower precedence is converted to the data type with the higher
precedence
Therefore your query is actually as follow:
Select *
from #0Test
Where cast(test as int) = 0;
and the empty string N'' yields the value 0 when cast to int:
select cast(N'' as int)
-----------
0
(1 row(s) affected)
Therefore the expected result is the one you see, the rows with an empty string qualify for the predicate test = 0. Further proof that you should never mix types freely. For a more detailed discussion of the topic, see How Data Access Code Affects Database Performance.
You are implicitly converting the field to int with your UNION statement.
Two empty strings and the integer 0 will result in an int field. This is BEFORE you insert into the nvarchar field, so the data type in the temp table is irrelevant.
Try changing the second select in the UNION to:
SELECT '0'
And you will get the expected result.