Using ISNULL when adding NULL to varchar - sql

Whilst experimenting with MSSQL I came across some behaviour I cannot explain.
I was looking at what happens to a NULL value when it is added to a varchar, and when I do the following query:
SELECT
ISNULL(NULL + ' ', 'test')
I get the result 'te'. Similarly if I change the word test for any other word I only get the first two letters. If I increase the number of spaces in the + ' ' I get extra letters in my result (so NULL + '[two spaces]' gives me 'tes'). Any ideas what is going on?
If I declare a variable and set it to NULL e.g.
DECLARE #testnull AS varchar(32)
SET #testnull = NULL
SELECT
ISNULL(#testnull + ' ', 'test')
then I get the result 'test' (as I would expect).

Use COALESCE. ISNULL takes the first NON-NULL data type, and in this case because it hasn't been declared, you get a VARCHAR(1) for the NULL and then it becomes a VARCHAR(2) when you add the space (this is still NULL when evaluated but SQL Server makes an assumption before that step). In some cases the default for varchar without length is 1, and in others it is 30. I'll let you guess which one is being used here.
SELECT
ISNULL(NULL + ' ', 'test'), COALESCE(NULL + ' ', 'test');
Results:
---- ----
te test
You can see this explicitly by:
SELECT
x = ISNULL(NULL + ' ', 'test')
INTO #a1;
SELECT
x = COALESCE(NULL + ' ', 'test')
INTO #a2;
SELECT LEFT(t.name, 3), TYPE_NAME(c.user_type_id), max_length
FROM tempdb.sys.columns AS c
INNER JOIN tempdb.sys.tables AS t
ON c.[object_id] = t.[object_id]
WHERE t.name LIKE '#a[1-2]%';
Results:
--- ------- ----
#a1 varchar 2
#a2 varchar 4
In almost all cases, I prefer COALESCE over ISNULL. I explain why (and where the exceptions exist) in this tip:
Deciding between COALESCE and ISNULL in SQL Server

Related

SQL Server - COALESCE WHEN NOTHING RETURNS , GET DEFAULT VALUE

I'm trying to use Coalesce function in SQL Server to concatente multiple names. But when the conditon in the query returns no rows or nothing, I need to return a default value. I tried some condition using case statement but I can't figure it out what I missed.
declare #Names varchar(max) = '',
#Key varchar(max) = 'ABC'
select #Names = COALESCE(#Names, '') + isnull(T0.A, #Key) + ', '
from TData P
left join TNames T0 on T0.C + '\' + T0.D = P.#Key
where OBID=581464
and ID < 1432081
select #Names
You can do it with 2 minor changes to your current code, but I suspect this is an XYProblem, and you might benefit more from editing your question to include sample data and desired results (so perhaps we can suggest a better solution).
Anyway, what I had in mind is this:
declare #Names varchar(max), -- remove the = '', so that #Names starts as null
#Key varchar(max) = 'ABC'
select #Names = COALESCE(#Names, '') + isnull(T0.A, #Key) + ', '
from TData P
left join TNames T0 on T0.C + '\' + T0.D = P.#Key -- ' (This is just to fix the coding colors)
where OBID=581464
and ID < 1432081
select COALESCE(#Names, 'default value') -- since #Names started as null, and the query did not return any results, it's still null...

CHARINDEX issue with NULL CHAR variable

DECLARE #MyChar CHAR = NULL
SELECT CHARINDEX(' ', ISNULL(NULL, '')),
CHARINDEX(' ', ISNULL(#MyChar, '')),
CHARINDEX(' ', ISNULL(CONVERT(VARCHAR, #MyChar), ''))
The above query returns the values 0, 1 and 0, in that order.
This result should be 0, 0 and 0. Is this an issue with MS SQL or there is some functionality here which I haven't understood?
I belive this will answer the question:
DECLARE #MyChar CHAR = NULL
SELECT CHARINDEX(' ', ISNULL(NULL, '')) a,
CHARINDEX(' ', ISNULL(#MyChar, '')) b,
CHARINDEX(' ', ISNULL(CONVERT(VARCHAR, #MyChar), '')) c
Results:
a b c
----------- ----------- -----------
0 1 0
Testing the values:
SELECT '|' + #MyChar + '|' a,
'|' + ISNULL(#MyChar, '') + '|' b,
'|' + ISNULL(CONVERT(VARCHAR, #MyChar), '') + '|' c
Results:
a b c
---- ---- --------------------------------
NULL | | ||
The ISNULL method returns the data type of the first argument it receives. since char has a minimum length of 1, and will pad the value with trailing spaces if needed, the result of ISNULL(#MyChar, '') is a string with a single space, hence the 1 you get in your result.
Let's try to understand the second query in two parts.
First part: SELECT ISNULL(#MyChar, '')
As per MSDN regarding ISNULL function:
Data type determination of the resulting expression is determined based on the data type of the first parameter.
So your first parameter #MyChar which is of Char and its value is NULL and when you use it in ISNULL function, second parameter which is '' (blank) will implicitly converted to CHAR like this -
SELECT CAST('' AS CHAR)
When you execute this query it'll give you whitespace.
Now when you execute your actual query with CharIndex
SELECT CHARINDEX(' ', ISNULL(#MyChar, '')
You'll get 1
Since you have defined the variable as CHAR and given the value as NULL hence it would occupy some space(2 bytes). So if the variable is a fixed width column and if you are trying to store NULL value in it then it will occupy the same amount of space as any other. From here:
There is a misconception that if we have the NULL values in a table it
doesn't occupy storage space. The fact is, a NULL value occupies space
– 2 bytes
If you change the datatype to varchar(1) and then provide the value as NULL you will find that you are getting the result as 0,0,0. So in case of variable width provided to the variable the NULL takes no space.
A good read article: How does SQL Server really store NULL-s

How Can we use ISNULL to all Column Names in SQL Server 2008?

I have a question
I tried to google it but looks like they don't like *
I'm using SQL Server 2008.
I have the following database table:
P_Id ProductName UnitPrice UnitsInStock UnitsOnOrder
------------------------------------------------------------------------
1 Jarlsberg 10.45 16 15
2 Mascarpone Null 23 NULL
3 Gorgonzola 15.67 9 20
If I need to replace the null with a string I know I do :
SELECT ISNULL(UnitsOnOrder,'No Data') FROM tbl
Questions
How can I use ISNULL() with multi column names ?
is it possible to use it with *
Like
SELECT ISNULL(* , 'NO data') FROM tbl
I think this will be tricky because of the datatype, you can't pass string to INT datatype so how can I fix this too
Update
Okay if i use ISNULL() with a datatype of int it will return 0
which will be a value to me , how can i pass empty string instead ?
You can use ISNULL multiple times in the same SQL statement for different columns, but you must write it separately for each column:
SELECT
ISNULL(ProductName, 'No Data') AS ProductName,
ISNULL(CAST(UnitPrice AS NVARCHAR), 'No Data') AS UnitPrice,
ISNULL(CAST(UnitsInStock AS NVARCHAR), 'No Data') AS UnitsInStock,
ISNULL(CAST(UnitsOnOrder AS NVARCHAR), 'No Data') AS UnitsOnOrder
FROM tbl
If you are building a dynamic SQL query, you could theoretically gather a list of columns in the table and generate a query with ISNULL on each one. For example:
DECLARE #SQL nvarchar(max)
SET #SQL = 'SELECT '
SELECT #SQL = #SQL + 'ISNULL(CAST([' + sc.name + '] AS NVARCHAR), ''No Data'') AS [' + sc.name + '],'
FROM sys.objects so
INNER JOIN sys.columns sc ON sc.object_id = so.object_id
WHERE so.name = 'tbl'
-- Remove the trailing comma
SELECT #SQL = LEFT(#SQL, LEN(#SQL) - 1) + ' FROM tbl'
EXEC sp_sqlexec #SQL
This code has problems when converting some column types like timestamps to an nvarchar, but it illustrates the technique.
Note that if you had another column that should be returned if a value is null, you could use the COALESCE expression like this:
SELECT COALESCE(ProductName, P_Id) AS Product...
Try this...
ISNULL (COALESCE (column1, column2), 'No Data')
You would need to include all column names though, you can't use *
COALESCE returns the first non-null value in its argument list so if they are all null it will return null

SQL Server count number of distinct values in each column of a table

I have a table with ~150 columns. I'd like to find the count(distinct(colName)) for each column but am wondering if there's a way to do so without actually typing out each column name.
Ideally I would use count(distinct(*)) but this doesn't work.
Any other suggestions?
EDIT:
if this is my table:
id col1 col2 col3 ...
01 10001 west north
02 10001 west south
03 10002 east south
04 10002 west north
05 10001 east south
06 10003 west north
I'm looking for this output
count(distinct(id)) count(distinct(col1)) count(distinct(col2)) count(distinct(col3))
6 3 2 2
You can do this:
DECLARE #query varchar(max)
SELECT #query =
'SELECT ' + SUBSTRING((SELECT ',' +'COUNT(DISTINCT(' + column_name + '))
As ' + column_name + ' '
FROM information_schema.columns
WHERE
table_name = 'table_name'
for xml path('')),2,200000) + 'FROM table_name'
PRINT(#query)
Use the below script to build T-SQL query that will return a distinct count of each column in a table. Replace #Table value with your table name.
DECLARE #Table SYSNAME = 'TableName';
-- REVERSE and STUFF used to remove trailing UNION in string
SELECT REVERSE(STUFF(REVERSE((SELECT 'SELECT ''' + name
+ ''' AS [Column], COUNT(DISTINCT('
+ QUOTENAME(name) + ')) AS [Count] FROM '
+ QUOTENAME(#Table) + ' UNION '
-- get column name from sys.columns
FROM sys.columns
WHERE object_id = Object_id(#Table)
-- concatenate result strings with FOR XML PATH
FOR XML PATH (''))), 1, 7, ';'));
Expanded answer from Bryan. His great answer lists the fields alphabetically.
This is no problem if you have a dozen fields or so. If you have 150 fields, like OP stated, this keeps the fields in their table's order.
I modified his query in order to examine a 213 column (vendor's) table and wanted to post for future reference.
DECLARE #Table SYSNAME = 'Your table name; without schema; no square brackets';
-- REVERSE and STUFF used to remove trailing UNION in string
SELECT REVERSE(STUFF(REVERSE((SELECT 'SELECT '
+ CAST(column_id AS VarChar(4)) + ' AS [column_id],' -- extra column
+ '''' + name
+ ''' AS [Column], COUNT(DISTINCT('
+ QUOTENAME(name) + ')) AS [Count] FROM '
+ QUOTENAME(#Table) + ' UNION '
-- get column name from sys.columns
FROM sys.columns
WHERE system_type_id NOT IN (34,240) AND object_id = Object_id(#Table)
ORDER BY column_id -- keeps columns in table order
-- concatenate result strings with FOR XML PATH
FOR XML PATH (''))), 1, 7, ';'));
I decided to not edit Bryan's answer because often people won't need the extra column.
(The ORDER BY has no effect if you don't add the column_id column. I believe this is because only the outermost ORDER BY is guarenteed to order the final output; I'd love to have a msft reference that confirms this)
EDIT: Using function Count with field types Image and Geography throws error.
Added "system_type_id NOT IN (34,240)".
I don't believe this is possible with just MySQL.
I would think your going to have to use a server side language to get the results you want.
Use "DESC TABLE" as your first query and then for each "field" row, compile your query.
Ignore this, wrong system tag :)
This should do:
select count(*) from (select distinct * from myTable) as t
Here is SQL Fiddle test.
create table Data
(
Id int,
Data varchar(50)
)
insert into Data
select 1, 'ABC'
union all
select 1, 'ABC'
select count(*)
from (select distinct * from Data) as t

What is happening in this T-SQL code? (Concatenting the results of a SELECT statement)

I'm just starting to learn T-SQL and could use some help in understanding what's going on in a particular block of code. I modified some code in an answer I received in a previous question, and here is the code in question:
DECLARE #column_list AS varchar(max)
SELECT #column_list = COALESCE(#column_list, ',') +
'SUM(Case When Sku2=' + CONVERT(varchar, Sku2) +
' Then Quantity Else 0 End) As [' +
CONVERT(varchar, Sku2) + ' - ' +
Convert(varchar,Description) +'],'
FROM OrderDetailDeliveryReview
Inner Join InvMast on SKU2 = SKU and LocationTypeID=4
GROUP BY Sku2 , Description
ORDER BY Sku2
Set #column_list = Left(#column_list,Len(#column_list)-1)
Select #column_list
----------------------------------------
1 row is returned:
,SUM(Case When Sku2=157 Then Quantity Else 0 End) As [157 -..., SUM(Case ...
The T-SQL code does exactly what I want, which is to make a single result based on the results of a query, which will then be used in another query.
However, I can't figure out how the SELECT #column_list =... statement is putting multiple values into a single string of characters by being inside a SELECT statement. Without the assignment to #column_list, the SELECT statement would simply return multiple rows. How is it that by having the variable within the SELECT statement that the results get "flattened" down into one value? How should I read this T-SQL to properly understand what's going on?
In SQL Server:
SELECT #var = #var + col
FROM TABLE
actually concatenates the values. It's a quirks mode (and I am unable at this time to find a reference to the documentation of feature - which has been used for years in the SQL Server community). If #var is NULL at the start (i.e. an uninitialized value), then you need a COALESCE or ISNULL (and you'll often use a separator):
SELECT #var = ISNULL(#var, '') + col + '|'
FROM TABLE
or this to make a comma-separated list, and then remove only the leading comma:
SELECT #var = ISNULL(#var, '') + ',' + col
FROM TABLE
SET #var = STUFF(#var, 1, 1, '')
or (courtesy of KM, relying on NULL + ',' yielding NULL to eliminate the need for STUFF for the first item in the list):
SELECT #var = ISNULL(#var + ',', '') + col
FROM TABLE
or this to make a list with a leading, separated and trailing comma:
SELECT #var = ISNULL(#var, ',') + col + ','
FROM TABLE
You will want to look into the COALESCE function. A good article describing what is happening can be seen here.