SQL Server COALESCE function with WHERE clause - sql-server

I want to concatenate my SQL Server table's column value in Varchar parameter. For that I am using the COALESCE function. But when I use it with Select statement with Where clause, I think it's not taking where clause conditions.
SELECT
#UserIds = COALESCE(#UserIds,'') + CONVERT(VARCHAR(MAX), UserID) +','
FROM vw_Users
WHERE GroupID = #GroupID
AND ISNULL(Active_yn, 'Y') = 'Y'
AND ISNULL(Delete_YN, 'N') = 'N'
So can anybody help me out on this?

If corecctly understood your problem, you can use CASE something like:
SELECT
CASE WHEN ISNULL(Active_yn, 'Y') = 'Y'
AND ISNULL(Delete_YN, 'N') = 'N'
AND GroupID = #GroupID THEN #UserIds = COALESCE(#UserIds,'') + CONVERT(VARCHAR(MAX), UserID) +','
ELSE Condition
END
FROM vw_Users
In this case It will concatenate only when all conditions meet, otherwise you can pass condition to ELSE.

Your query looks fine, where conditions are correct, just a small change in COALESCE function to concatenate "," only when needed. Here's a sample with data:
DECLARE #UserIds VARCHAR(100);
DECLARE #GroupID INT = 1;
WITH vw_Users (UserID, GroupID, Active_YN, Delete_YN) AS (
SELECT 1, 1, 'Y', 'N' UNION ALL -- valid value
SELECT 2, 1, 'Y', 'N' UNION ALL -- valid value
SELECT 3, 1, 'N', 'N' UNION ALL -- invalid value because Active_YN <> 'Y'
SELECT 4, 1, 'Y', 'Y' UNION ALL -- invalid value because Deleted_YN <> 'N'
SELECT 5, 2, 'Y', 'N' -- invalid value because GroupID <> 1
)
SELECT
#UserIds = COALESCE(#UserIds + ',','') + CONVERT(VARCHAR(MAX), UserID)
FROM vw_Users
WHERE GroupID = #GroupID
AND ISNULL(Active_yn, 'Y') = 'Y'
AND ISNULL(Delete_YN, 'N') = 'N'
SELECT #UserIds --> 1,2

You should try following,
Declare #cols nvarchar(max) = ''
SELECT #cols =
STUFF(( SELECT DISTINCT TOP 100 PERCENT
',' + CONVERT(VARCHAR(10), t2.UserID)
FROM vw_Users AS t2
WHERE GroupID = #GroupID
AND ISNULL(Active_yn, 'Y') = 'Y'
AND ISNULL(Delete_YN, 'N') = 'N'
FOR XML PATH('')
), 1, 1, '')
select #cols
Here is Sql fiddle

Related

Snowflake: Count number of NULL values in each column SQL

What is the Snowflake SQL version of this SQL Server solution that counts NULL values in each column of a table? The SQL executes via the SQL Server EXEC function. I would prefer to not use a stored procedure because of access restrictions but such a solution would be good to have for the future.
I would prefer the solution display the column name in one column and the number of NULL values in another a la:
COLUMN_NAME NULLVALUES
HATS 325
SHOES 0
DOGS 9998
The dynamic SQL (SQL built at runtime) below is the best I have been able to do so far. The name of the column containing the column names will unfortunately be the first column in the table. Ideally, the SQL would execute automatically but experiments with BEGIN and END as well as EXECUTE IMMEDIATE did not work.
-- SUMMARY: Count NULL values for every column of the specified table.
-- This SQL dynamically builds a query that counts the NULL values in every column of a table.
-- One copies and pastes the code in the resulting SQLROW column (can double-click to open query window then copy to clipboard)
USE DATABASE YOUR_DATABASE
-- What database contains the table of interest?
SET DBName = 'YOUR_DATABASE';
-- What is the schema of the table?
SET SchemaName = 'YOUR_SCHEMA';
--What is the table name?
SET TableName = 'YOUR_TABLE';
SELECT ($DBName || '.' || $SchemaName || '.' || $TableName) as FullTablePath;
WITH SQLText AS (
SELECT
ROW_NUMBER() OVER (ORDER BY c.column_name) AS RowNum,
'SELECT ' || '''' || c.column_name || '''' ||
', SUM(CASE WHEN ' || c.column_name || ' IS NULL THEN 1 ELSE 0 END) AS NullValues
FROM ' || $DBName || '.' || $SchemaName || '.' || $TableName AS SQLRow
FROM
information_schema.tables t
INNER JOIN information_schema.columns c
ON c.table_name = t.table_name and c.table_schema = t.table_schema
WHERE
t.table_name = $TableName and t.table_schema = $SchemaName),
Recur AS (
SELECT
RowNum,
TO_VARCHAR(SQLRow) AS SQLRow
FROM
SQLText
WHERE
RowNum = 1
UNION ALL
SELECT
t.RowNum,
r.SQLRow || ' UNION ALL ' || t.SQLRow
FROM
SQLText t
INNER JOIN Recur r ON t.RowNum = r.RowNum + 1
),
no_dupes as (
select * from Recur where RowNum = (SELECT MAX(RowNum) FROM Recur)
)
select SQLRow from no_dupes
some setup:
create table test.test.some_nulls(a string, b int, c boolean) as
select * from values
('a', 1, true),
('b', 2, false),
('c', 3, null),
('d', null, true),
('e', null, false),
('f', null, null),
(null, 7, true),
('h', 8, false),
(null, 9, null),
('j', null, true),
(null, null, false),
('l', null, null);
and:
SET DBName = 'TEST';
SET SchemaName = 'TEST';
SET TableName = upper('some_nulls');
this is the sql we are wanting to have run in the end:
select count(*)-count(a) as a,
count(*)-count(b) as b,
count(*)-count(c) as c
from TEST.TEST.some_nulls;
A
B
C
3
6
4
and this SQL builds that SQL, via LISTAGG:
select 'select '
|| listagg('count(*)-count('||column_name||') as ' || column_name, ', ') WITHIN GROUP (order by ordinal_position )
|| ' from '|| $DBName ||'.'|| $SchemaName ||'.'|| $TableName as sql
from information_schema.columns
where table_name = $TableName;
SQL
select count(*)-count(A) as A, count(*)-count(B) as B, count(*)-count(C) as C from TEST.TEST.SOME_NULLS
thus this SQL builds, and runs the second SQL and then runs the results which is the first SQL, via RESULTSETs, CURSORS, Snowflake Scripting
declare
res resultset;
select_statement text;
begin
select_statement := 'select ''select '' '||
' || listagg(''count(*)-count(''||column_name||'') as '' || column_name, '', '') WITHIN GROUP (order by ordinal_position ) ' ||
' || '' from '|| $DBName ||'.'|| $SchemaName ||'.'|| $TableName || ''' as sql' ||
' from '||$DBName||'.information_schema.columns '||
' where table_name = ''' || $TableName || '''';
res := (execute immediate :select_statement);
let cur1 cursor for res;
for record in cur1 do
res := (execute immediate record.sql);
break;
end for;
return table(res);
end;
and gives:
A
B
C
3
6
4

Is there a script that normalizes a poorly-created database table?

I have my database structured like this: Image1 but I would like it in this format: Image2. Is there some PostgreSQL script available that basically converts these formats?
If it's small, then you can write this query:
select serial_number, number1 from bad_table
union all
select serial_number, number2 from bad_table
union all
select serial_number, number3 from bad_table
;
If you do not want empty values copied, then add a not null restriction:
select serial_number, number1 from bad_table where number1 is not null
union all
select serial_number, number2 from bad_table where number2 is not null
union all
select serial_number, number3 from bad_table where number3 is not null
;
If it's bigger than this, then you can construct this query from information_schema.columns.
with queries as (
select concat('select serial_number, ',
column_name,
' from bad_table where ',
column_name,
' is not null') as query
from information_schema.columns
where table_name = 'bad_table'
and column_name like 'number%'
), asarray as (
select array_agg(query) as qs
from queries
)
select array_to_string(qs, ' union all ')
from asarray;
This might need some simplification, but it is a start:
with tname as ( select 'bad_table' as t ), stemmed_columns as (
select column_name::text as column_name,
regexp_replace(column_name, '\d+$', '') as rootname,
regexp_replace(column_name, '^\D+', '') as ind
from information_schema.columns
cross join tname
where table_name = tname.t
), col_arrays as (
select array_to_string(
array_agg(column_name)
filter (where ind = ''),
', '
) as leadcols,
array_agg(column_name order by column_name)
filter (where ind != '')
as icols,
array_agg(distinct rootname order by rootname)
filter (where ind != '')
as stems
from stemmed_columns
), index_limits as (
select distinct ind::int as indint
from stemmed_columns
where ind != ''
), blowout as (
select l.indint,
array_agg(
case
when (s.stem||l.indint::text) = any(a.icols) then s.stem||l.indint::text
else 'null'
end || ' as '|| s.stem order by s.stem) as srcname,
array_agg(
case
when (s.stem||l.indint::text) = any(a.icols) then s.stem||l.indint::text
else 'null'
end order by s.stem) as srcnameonly
from index_limits l
cross join col_arrays a
cross join (select unnest(stems) as stem from col_arrays) s
group by l.indint
), qlines as (
select concat(
'select ', a.leadcols, ', ',
array_to_string(b.srcname, ', '),
' from ',
tname.t,
' where coalesce(',
array_to_string(b.srcnameonly, ', '),
') is not null'
) as qline
from blowout b
cross join col_arrays a
cross join tname
)
select array_to_string(array_agg(qline), ' union all ') as query
from qlines;

Compare Identical Columns of 2 Different Table for Mismatch Data

I have the following query to check if usernames are the exact same between to tables
SELECT
--Username
a.username as 'TABLE_1_USERNAME',
b.username as 'TABLE_2_USERNAME',
CASE
WHEN a.username IS NULL AND b.username IS NULL THEN 'True'
WHEN a.username = b.username THEN 'True'
ELSE 'False'
END AS 'Is Equal?'
FROM User a
JOIN User_Group b ON a.id = b.id
This works great to tell me if usernames differ for any reason, but what I'd like to do is recursively compare each column between the User table and the User_Group Table (without having to write each one out) - both of the table's column names are identical. Is this possible in SQL Server?
Leaving out casing checks and trimming for brevity
Try this. Except shows any rows in the two queries that don't fully match
SELECT * FROM User_Group
EXCEPT
SELECT * FROM User
Here's a dynamic sql method for doing literally what you're asking for:
DECLARE #cols TABLE (id int identity(1,1), colname varchar(25))
INSERT INTO #cols (colname) select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'User' AND COLUMN_NAME <> 'id'
declare #sql nvarchar(max)
select #sql = 'SELECT a.id, '
declare #maxval int
select #maxval = max(id) from #cols
declare #c int = 1
declare #curcol varchar(25)
while #c <= #maxval
begin
if #c > 1
select #sql += ', '
select #curcol = colname from #cols where id = #c
select #sql += 'a.' + #curcol + ' as ''Table_1_' + #curcol + ''', b.' + #curcol + ' as ''Table_2_' + #curcol + ''',
CASE
WHEN a.' + #curcol + ' IS NULL AND b.' + #curcol + ' IS NULL THEN ''True''
WHEN a.' + #curcol + ' = b.' + #curcol + ' THEN ''True''
ELSE ''False''
END AS ''Is Equal?'''
set #c = #c + 1
end
select #sql += 'FROM [User] a
INNER JOIN User_Group b ON a.id = b.id'
exec sp_executesql #sql
With the following test data:
create table [User] (id int, username varchar(10), fname varchar(25), lname varchar(25))
create table [User_Group] (id int, username varchar(10), fname varchar(25), lname varchar(25))
insert into [User] values (1, 'user1', 'fname1', 'lname1')
insert into [User_Group] values (1, 'user1', 'fname1', 'lname1')
insert into [User] values (2, 'user2', 'fname2', 'lname2')
insert into [User_Group] values (2, 'user2', 'fnameasdf', 'lnamesdfg')
It generates this output:
id Table_1_username Table_2_username Is Equal? Table_1_fname Table_2_fname Is Equal? Table_1_lname Table_2_lname Is Equal?
1 user1 user1 True fname1 fname1 True lname1 lname1 True
2 user2 user2 True fname2 fnameasdf False lname2 lnamesdfg False
Here's the query it generates:
SELECT a.id, a.username as 'Table_1_username', b.username as 'Table_2_username',
CASE
WHEN a.username IS NULL AND b.username IS NULL THEN 'True'
WHEN a.username = b.username THEN 'True'
ELSE 'False'
END AS 'Is Equal?', a.fname as 'Table_1_fname', b.fname as 'Table_2_fname',
CASE
WHEN a.fname IS NULL AND b.fname IS NULL THEN 'True'
WHEN a.fname = b.fname THEN 'True'
ELSE 'False'
END AS 'Is Equal?', a.lname as 'Table_1_lname', b.lname as 'Table_2_lname',
CASE
WHEN a.lname IS NULL AND b.lname IS NULL THEN 'True'
WHEN a.lname = b.lname THEN 'True'
ELSE 'False'
END AS 'Is Equal?'FROM [User] a
INNER JOIN User_Group b ON a.id = b.id

T-SQL: List multiple rows for a shared value in one row

I have a table in SQL Server that looks something like this:
What I need is this:
I tried playing around with Pivot a little bit, but of course that won't work because Pivot isn't trying to flip multiple instances of the same field, it's aggregating over multiple instance of the same field.
Does anyone have any ideas of how I can do this in T-SQL?
You can do this easily using dynamic PIVOT. This is full working example:
IF OBJECT_ID('[dbo].[DataSource]') IS NOT NULL
BEGIN;
DROP TABLE [dbo].[DataSource];
END;
CREATE TABLE [dbo].[DataSource]
(
[ID] SMALLINT
,[Value] CHAR(1)
);
INSERT INTO [dbo].[DataSource] ([ID], [Value])
VALUES (1, 'A')
,(1, 'B')
,(1, 'C')
,(2, 'A')
,(2, 'B')
,(3, 'C')
,(4, 'A')
,(4, 'B')
,(4, 'C');
DECLARE #MaxValue INT;
WITH DataSource AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY [ID] ORDER BY [Value]) AS [ValueID]
FROM [dbo].[DataSource]
)
SELECT #MaxValue = MAX([ValueID])
FROM DataSource;
DECLARE #DynamicSQLStatement NVARCHAR(MAX)
,#DynamicSQLPIVOTColumns NVARCHAR(MAX);
SELECT #DynamicSQLPIVOTColumns = STUFF
(
(
SELECT DISTINCT ',[Value' + CAST([number] AS VARCHAR(4)) + ']'
FROM master..[spt_values]
WHERE [number] BETWEEN 1 AND #MaxValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
SET #DynamicSQLStatement = N'
SELECT [ID], ' + #DynamicSQLPIVOTColumns + '
FROM
(
SELECT *
,''Value'' + CAST(ROW_NUMBER() OVER (PARTITION BY [ID] ORDER BY [Value]) AS VARCHAR(12)) AS [ColumnName]
FROM [dbo].[DataSource]
) DS
PIVOT
(
MAX([value]) FOR [ColumnName] IN (' + #DynamicSQLPIVOTColumns + ')
) PVT;';
EXEC sp_executesql #DynamicSQLStatement;
and the result is:
Something wrong? The value C for the ID = 3 is in the first column, not in the last. That's because I am not aware how you are defining which value is in which column and I am using ROW_NUMBER to create such mapping. I guess in your real data you have a way of doing this.
So, let's say you have additional table like this to define this ordering:
IF OBJECT_ID('[dbo].[DataSourceOrdering]') IS NOT NULL
BEGIN;
DROP TABLE [dbo].[DataSourceOrdering];
END;
CREATE TABLE [dbo].[DataSourceOrdering]
(
[OrderID] SMALLINT
,[Value] CHAR(1)
);
INSERT INTO [dbo].[DataSourceOrdering] ([OrderID], [Value])
VALUES (1, 'A')
,(2, 'B')
,(3, 'C');
Then instead using ROW_NUMBER to defined the ordering we are going to use this table:
DECLARE #MaxValue INT;
WITH DataSource AS
(
SELECT DSO.[OrderID]
FROM [dbo].[DataSource] DS
INNER JOIN [dbo].[DataSourceOrdering] DSO
ON DS.[Value] = DSO.[Value]
)
SELECT #MaxValue = MAX([OrderID])
FROM DataSource;
DECLARE #DynamicSQLStatement NVARCHAR(MAX)
,#DynamicSQLPIVOTColumns NVARCHAR(MAX);
SELECT #DynamicSQLPIVOTColumns = STUFF
(
(
SELECT DISTINCT ',[Value' + CAST([number] AS VARCHAR(4)) + ']'
FROM master..[spt_values]
WHERE [number] BETWEEN 1 AND #MaxValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
SET #DynamicSQLStatement = N'
SELECT [ID], ' + #DynamicSQLPIVOTColumns + '
FROM
(
SELECT DS.*
,''Value'' + CAST(DSO.[OrderID] AS VARCHAR(12)) AS [ColumnName]
FROM [dbo].[DataSource] DS
INNER JOIN [dbo].[DataSourceOrdering] DSO
ON DS.[Value] = DSO.[Value]
) DS
PIVOT
(
MAX([value]) FOR [ColumnName] IN (' + #DynamicSQLPIVOTColumns + ')
) PVT;';
EXEC sp_executesql #DynamicSQLStatement;
Something like this?
WITH
t AS (
SELECT 1 AS ID, 'A' AS Value
UNION ALL SELECT 1, 'B'
UNION ALL SELECT 1, 'C'
UNION ALL SELECT 2, 'A'
UNION ALL SELECT 2, 'B'
UNION ALL SELECT 3, 'C'
UNION ALL SELECT 4, 'A'
UNION ALL SELECT 4, 'B'
UNION ALL SELECT 4, 'C'
)
SELECT ID,
MAX(CASE WHEN Value = 'A' THEN Value ELSE NULL END) AS Value1,
MAX(CASE WHEN Value = 'B' THEN Value ELSE NULL END) AS Value2,
MAX(CASE WHEN Value = 'C' THEN Value ELSE NULL END) AS Value3
FROM t
GROUP BY ID

dynamic column name in where clause

declare #temp varchar(20)
declare #name varchar(20)
set #name = 'John'
set #temp = 'e'
select * from TableA
where case when #temp = 'e' then [em_name]
case when #temp = 'c' then [company_name]
end
= #name
This query is giving me error(A non-Boolean expression in where clause).
Please explain what is wrong in this query and how can i achieve this without dynamic sql.
so when i give #temp = 'C' then it should search for [company_name] = #name. and have a long list of #temp values(employee name, company name, city name, state name, supervisor name etc).
There is a syntax error in the snip you posted:
where case when #temp = 'e' then [em_name]
case when #temp = 'c' then [company_name]
end
= #name
Should be:
WHERE CASE WHEN #temp = 'e' THEN em_name
WHEN #temp = 'c' THEN company_name
END = #name
Note there is now only one CASE keyword. Here's the more common syntax (for completeness):
DECLARE #temp VARCHAR(20)
DECLARE #name VARCHAR(20)
SET #name = 'John'
SET #temp = 'e'
SELECT *
FROM TableA
WHERE (#temp = 'e' AND [em_name] = #name)
OR (#temp = 'c' AND [company_name] = #name)
SELECT *
FROM TableA
WHERE (#temp = 'e' AND [em_name] = #name) OR (#temp = 'c' AND [company_name] = #name)

Resources