Remove a ROW of NULL entries SQl Server - sql

If I have a table which looks like this
Is there a way to remove all rows in a SQL tablw without knowing the column titles, if each item in that row is NULL?
Thanks!

Maybe there is another way but since you've tagged SqlCommand i would use ADO.NET. You can use dataReader.GetSchemaTable to retrieve column informations only:
// retrieve column names only
DataTable schema = null;
using (var con = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
using (var schemaCommand = new SqlCommand("SELECT * FROM dbo.TableName", con))
{
con.Open();
using (var reader = schemaCommand.ExecuteReader(CommandBehavior.SchemaOnly))
{
schema = reader.GetSchemaTable();
}
}
}
// build sql with parameters dynamically
var columnQuery = schema.AsEnumerable()
.Select(r => string.Format("{0} IS NULL", r.Field<String>("ColumnName")));
string where = string.Join(" AND ", columnQuery);
string sql = string.Format("DELETE FROM dbo.TableName WHERE {0}", where);
// here you are
Of course you need two queries, one to select the columns and one to delete the null-rows. So the best way would be to know the columns in the first place.

Here's a snippet that should allow you to first SEE what your delete statements will look like, and then run it again when you're ready to actually perform the deletions:
begin
set nocount on
create table #temp_sql_text (sql_text varchar(max))
declare #table_name varchar(max)
declare c cursor for
select name from sys.tables order by name
open c
fetch next from c into #table_name
while ##fetch_status = 0
begin
insert into #temp_sql_text
select 'delete from ' + #table_name + ' where ' + replace(stuff(ColumnList, 1, 3, ''), '|||', ' is null and ') + ' is null'
from (
select '|||' + '[' + c.name + ']'
from sys.tables t inner join sys.columns c on c.object_id = t.object_id
where t.name = #table_name
order by c.column_id
for xml path('')
) o (ColumnList)
fetch next from c into #table_name
end
close c
deallocate c
declare #sql_text varchar(max)
declare c2 cursor for
select sql_text from #temp_sql_text
open c2
fetch next from c2 into #sql_text
while ##fetch_status = 0
begin
--Suggest just printing out the delete queries as a test before executing them!
print #sql_text
--Uncomment the next line to also execute the delete queries
--exec sp_executesql #sql_text
fetch next from c2 into #sql_text
end
close c2
deallocate c2
drop table #temp_sql_text
set nocount off
end

try this: I just tried on SQL server 2008 R2. I am not good with how to place the code in answer. please bare it.
declare #tempvar as nvarchar(max)
set #tempvar='X'
select #tempvar=#tempvar+COLUMN_NAME+' is NULL AND ' from
DatabaseName.INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='Table_Name'
print #tempvar
Select #tempvar=CONCAT(#tempvar, 'X')
print #tempvar
Select #tempvar= REPLACE(#tempvar,'AND X','')
print #tempvar
Select #tempvar= REPLACE(#tempvar,'X',' ')
print #tempvar
declare #queryex as nvarchar(max)
set #queryex='select * from Table_Name where'+#tempvar
print #queryex
EXEC sp_executesql #queryex

If you know the number of columns in the table, you can do this:
delete from
tableToScrub
where
checksum(*) =
checksum(
cast(null as int),
cast(null as int)
-- Add one null to checksum per column
)
If you don't know the number of columns, you can do this:
with
nullset
as (
select
cast(null as int) as col1,
cast(null as int) as col2,
cast(null as int) as col3,
cast(null as int) as col4,
cast(null as int) as col5,
cast(null as int) as col6,
cast(null as int) as col7
-- add columns as necessary for your tables
)
,
nullchecksums
as (
select checksum(col1) as nullsum, 1 as columnCount from nullset union
select checksum(col1, col2), 2 from nullset union
select checksum(col1, col2, col3), 3 from nullset union
select checksum(col1, col2, col3, col4) , 4 from nullset union
select checksum(col1, col2, col3, col4, col5) , 5 from nullset union
select checksum(col1, col2, col3, col4, col5, col6) , 6 from nullset union
select checksum(col1, col2, col3, col4, col5, col6, col7) , 7 from nullset
-- add rows as necessary
)
delete from
tableToScrub
where
checksum(*) = (
select
nullsum
from
nullchecksums
where
columncount = (
select
count(1) as columnCount
from
sys.columns
where
object_id = object_id('SchemaName.TableToScrub')
)
)

Related

Split string into words in columns

I am looking to split a string into words in columns in SQL Server 2014. I have found a few solutions but all of them are giving the results in rows. How can I break the below string into columns?
"First Second Third Fourth Fifth"
You can use XML and grab the elements by their position:
DECLARE #YourString VARCHAR(100)='First Second Third Fourth Fifth';
WITH StringAsXML AS
(
SELECT CAST('<x>' + REPLACE((SELECT #YourString AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML) TheXml
)
SELECT TheXml.value('x[1]/text()[1]','nvarchar(max)') AS FirstElement
,TheXml.value('x[2]/text()[1]','nvarchar(max)') AS SecondElement
,TheXml.value('x[3]/text()[1]','nvarchar(max)') AS ThirdElement
,TheXml.value('x[4]/text()[1]','nvarchar(max)') AS FourthElement
,TheXml.value('x[5]/text()[1]','nvarchar(max)') AS FifthElement
FROM StringAsXML;
Remark
You can use PIVOT, conditional aggregation, FROM(VALUES()) or the above. but any of these approaches will need a known set of columns (a known count of elements or at least a maximum count of elements).
If you cannot rely on such a knowledge, you can use dynamically created SQL. This would mean to create one of the working statements on string base and use EXEC for a dynamic execution.
UPDATE: A dynamic approach
This approach will deal with a variable number of elements
DECLARE #YourString VARCHAR(100)='First Second Third Fourth Fifth';
DECLARE #Delimiter CHAR(1)=' ';
DECLARE #countElements INT = LEN(#YourString)-LEN(REPLACE(#YourString,#Delimiter,''));
DECLARE #Statement VARCHAR(MAX)=
'WITH StringAsXML AS
(
SELECT CAST(''<x>'' + REPLACE((SELECT ''ReplaceYourString'' AS [*] FOR XML PATH('''')),'' '',''</x><x>'') + ''</x>'' AS XML) TheXml
)
SELECT ReplaceColumnList
FROM StringAsXML;';
DECLARE #columnList VARCHAR(MAX);
WITH cte AS
(
SELECT 1 AS ElementCounter
,CAST('TheXml.value(''x[1]/text()[1]'',''nvarchar(max)'') AS Element_01' AS VARCHAR(MAX)) AS ColStatement
UNION ALL
SELECT cte.ElementCounter+1
,cte.ColStatement + CAST(',TheXml.value(''x[' + CAST(cte.ElementCounter+1 AS VARCHAR(10)) + ']/text()[1]'',''nvarchar(max)'') AS Element_' + REPLACE(STR(cte.ElementCounter + 1,2),' ','0') AS VARCHAR(MAX))
FROM cte
WHERE cte.ElementCounter <= #countElements
)
SELECT #columnList=(SELECT TOP 1 cte.ColStatement FROM cte ORDER BY cte.ElementCounter DESC)
--replace the string you want to split
SET #Statement = REPLACE(#Statement,'ReplaceYourString',#YourString);
--replace the columnList
SET #Statement = REPLACE(#Statement,'ReplaceColumnList',#columnList);
EXEC(#Statement);
UPDATE 2: The smallest fully inlined and position-safe splitter I know of
Try this out:
DECLARE #inp VARCHAR(200) = 'First Second Third Fourth Fifth';
DECLARE #dlmt VARCHAR(100)=' ';
;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(#dlmt, #inp, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(#inp, i+1, IIF(j>0, j, LEN(#inp)+1)-i-1) s FROM a WHERE i >= 0)
SELECT * FROM b;
And just to get it complete: The above tiny splitter combined with PIVOT:
;WITH
a AS (SELECT n=0, i=-1, j=0 UNION ALL SELECT n+1, j, CHARINDEX(#dlmt, #inp, j+1) FROM a WHERE j > i),
b AS (SELECT n, SUBSTRING(#inp, i+1, IIF(j>0, j, LEN(#inp)+1)-i-1) s FROM a WHERE i >= 0)
SELECT p.*
FROM b
PIVOT(MAX(s) FOR n IN([1],[2],[3],[4],[5])) p;
You can use a SQL split string function to seperate the string into words and using the order of the word in the original string, you can use CASE statements like a PIVOT query and display as columns
Here is a sample
declare #string varchar(max) = 'First Second Third Fourth Fifth'
;with cte as (
select
case when id = 1 then val end as Col1,
case when id = 2 then val end as Col2,
case when id = 3 then val end as Col3,
case when id = 4 then val end as Col4,
case when id = 5 then val end as Col5
from dbo.split( #string,' ')
)
select
max(Col1) as Col1,
max(Col2) as Col2,
max(Col3) as Col3,
max(Col4) as Col4,
max(Col5) as Col5
from cte
If you cannot create a UDF, you can use the logic in your SQL code as follows
Please note that if you have your data in a database table column, you can simply replace column content in the first SQL CTE expression
declare #string varchar(max) = 'First Second Third Fourth Fifth'
;with cte1 as (
select convert(xml, N'<root><r>' + replace(#string,' ','</r><r>') + '</r></root>') as rawdata
), cte2 as (
select
ROW_NUMBER() over (order by getdate()) as id,
r.value('.','varchar(max)') as val
from cte1
cross apply rawdata.nodes('//root/r') as records(r)
)
select
max(Col1) as Col1,
max(Col2) as Col2,
max(Col3) as Col3,
max(Col4) as Col4,
max(Col5) as Col5
from (
select
case when id = 1 then val end as Col1,
case when id = 2 then val end as Col2,
case when id = 3 then val end as Col3,
case when id = 4 then val end as Col4,
case when id = 5 then val end as Col5
from cte2
) t
You may use parsename function as :
create table tab ( str varchar(100));
insert into tab values('First Second Third Fourth Fifth');
with t as
(
select replace(str,' ','.') as str
from tab
)
Select substring(str,1,charindex('.',str)-1) as col_first,
parsename(substring(str,charindex('.',str)+1,len(str)),4) as col_second,
parsename(substring(str,charindex('.',str)+1,len(str)),3) as col_third,
parsename(substring(str,charindex('.',str)+1,len(str)),2) as col_fourth,
parsename(substring(str,charindex('.',str)+1,len(str)),1) as col_fifth
from t;
col_first col_second col_third col_fourth col_fifth
--------- ---------- --------- ---------- ---------
First Second Third Fourth Fifth
P.S. firstly, need to split the main string into the parts with at most 3 three dot(.) character(otherwise the function doesn't work). It's a restriction for parsename.
Rextester Demo

MSSQL select lowest but not NULL/zero value from 25 columns

I have 25 (numeric) columns in one table in MSSQL and I need select lowest value, but not NULL or 0 value.
Columns are named like "%_price" (aaa_price, bbb_price, ccc_price...).
Some columns contains 0 or NULL value.
Example:
table (aaa_price, bbb_price, ccc_price, ddd_price, eee_price, fff_price)
value (NULL, 0, 324.23, 162.50, NULL, 1729.72 )
Right result:
162.50
I can use some "brute force" method like:
SELECT CASE
WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
WHEN Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
WHEN Col3 <= Col4 AND Col3 <= Col5 THEN Col3
WHEN Col4 <= Col5 THEN Col4
ELSE Col5
END AS [Min Value] FROM [Your Table]
But its insane with 25 columns... is there any better solution?
Thank You!
Cross apply can be good option in this case:
select
*
from
myTable
cross apply (select
minVal = min(val)
from (
values (aaa_price),(bbb_price),(...)
) t(val) where val > 0
) q
Edit:
You have to use dynamic SQL if you want to get column names from INFORMATION_SCHEMA.COLUMNS table.
declare #sql varchar(8000)
declare #cols varchar(8000)
select #cols =
stuff((
SELECT
',(' + COLUMN_NAME + ')'
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'mytable'
AND TABLE_SCHEMA='dbo'
AND COLUMN_NAME LIKE '%price'
for xml path('')
), 1, 1, '')
set #sql = 'select
*
from
mytable
cross apply (select
minVal = min(val)
from (
values ' + #cols + '
) t(val) where val > 0
) q'
exec (#sql)
You can create a dynamic SQL statement and execute it in the following form
declare #tablename sysname = 'MultipleNumericColumns'
declare #sql nvarchar(max)
select #sql = isnull(#sql + ' union all ','') + '
SELECT ' + name + ' as colname from ' + #tablename
from sys.all_columns
where
object_id = OBJECT_ID(#tablename)
set #sql = '
select min(colname)
from (
' + #sql + '
) t
where colname > 0'
EXECUTE(#sql)
You can realize that first I get the column names from system view
You can exclude columns that you don't want or use a pattern like name like '%price% etc at this step
Then I build a dynamic SQL query into a string variable as sql command
Please note that I use WHERE clause for greater than 0, etc
Final step is execution with EXECUTE command
Use UNPIVOT
SELECT Min(VALUE)
FROM (
SELECT Col1, Col2, Col3...
FROM YourTable
) t
UNPIVOT (VALUE FOR ITEM IN (Col1, Col2, Col3...)) u
WHERE VALUE != 0

One view from two tables with identical column names

We have two tables that we need to merge into a singular view. Normally I'd individually select columns to avoid this issue, however in this case the two tables are a combined 800 columns.
The only identical columns are the identifier columns. Unfortunately these cannot be changed as they are used by a 3rd party tool to sync table
Table A
GUID
Name
Address
...
Table B
GUID
Cell
Fax
Home2
...
Are good examples, just assume each table has 400 odd columns.
Obviously the traditional
SELECT a.*, b.* from table_a a, table_b a where a.guid = b.guid
Fails miserably. Is there any easy way to create the view without having to list out 799 individual column names? I was thinking perhaps a one off function to create the view but so far I'm hitting a wall.
You can use dynamic sql as a solution.
CREATE TABLE test1 (id INT, col1 NVARCHAR(50), col2 NVARCHAR(50))
GO
CREATE TABLE test2(id INT, col1 NVARCHAR(50), col2 NVARCHAR(50))
GO
DECLARE #sql NVARCHAR(max) = ''
; WITH cte AS (
SELECT
CASE WHEN TABLE_NAME = 'test1' THEN TABLE_NAME + '.' + COLUMN_NAME + ' AS ' + + COLUMN_NAME + 't1' ELSE TABLE_NAME + '.' + COLUMN_NAME + ' AS ' + + COLUMN_NAME + 't2' END AS a, 1 AS ID
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN ('test1', 'test2')
)
SELECT #sql =
'CREATE VIEW myview as
select ' + (
SELECT
STUFF(
(
SELECT ', '+ [A]
FROM cte
WHERE ID = results.ID
FOR XML PATH(''), TYPE
).value('(./text())[1]','VARCHAR(MAX)')
,1,2,''
) AS NameValues
FROM cte results
GROUP BY ID
) + ' from test1 join test2 on test1.id = test2.id'
PRINT #sql
--EXEC (#sql)
The result is
CREATE VIEW myview
AS
SELECT test1.id AS idt1 ,
test1.col1 AS col1t1 ,
test1.col2 AS col2t1 ,
test2.id AS idt2 ,
test2.col1 AS col1t2 ,
test2.col2 AS col2t2
FROM test1
JOIN test2 ON test1.id = test2.id

string or binary data would be truncated. in sql server

How to overcome this?
I have insert statements for large tables. there were lot of columns in those tables.
Is there any simple way to find the column which is causing above error?
The simplest way is to increase the length of the column which is giving you the error "string or binary data would be truncated. in sql server".
Now to find which column is giving that error you can check How to find what column caused the String or binary data would be truncated message
First thing we need to know is what the columns are in the table that are (n)chars or (n)varchar
select column_name
from information_schema.columns
where table_name = 'temp'
and data_type in('varchar','char','nvarchar','nchar')
Output:
column_name
————–
Col1
Col2
Col3
Col4
Col5
That was easy, now we want to know the max length of the data in each column
declare #sql varchar(8000)
select #sql = 'select 0 as _col0 ,'
select #sql += 'max(len( ' + column_name+ ')) AS ' + column_name + ','
from information_schema.columns
where table_name = 'temp'
and data_type in('varchar','char','nvarchar','nchar')
select #sql = left(#sql,len(#sql) -1)
select #sql +=' into MaxLengths from temp'
--select #sql -debugging so simple, a caveman can do it
exec (#sql)
That code basically creates and runs the following
select 0 as _col0 ,
max(len( Col1)) AS Col1,
max(len( Col2)) AS Col2,
max(len( Col3)) AS Col3,
max(len( Col4)) AS Col4,
max(len( Col5)) AS Col5
into MaxLengths
from temp
If we now look in the MaxLengths table we will see the following
select * from MaxLengths
_col0 Col1 Col2 Col3 Col4 Col5
---------------------------------------------------
0 13 20 6 4 15
Next to figure out is what the max length of the column itself is in the table that we want to insert into
Run the following query
select character_maximum_length,column_name
from information_schema.columns
where table_name = 'TestTrunc'
and data_type in('varchar','char','nvarchar','nchar')
Result
character_maximum_length column_name
--------------------------------------------
10 Col1
15 Col2
20 Col3
3 Col4
10 Col5
We will again do this dynamically and insert the values into another table
declare #sql varchar(8000)
select #sql = 'select 0 as _col0, '
select #sql += '' + convert(varchar(20),character_maximum_length)+ ' AS ' + column_name + ','
from information_schema.columns
where table_name = 'TestTrunc'
and data_type in('varchar','char','nvarchar','nchar')
select #sql = left(#sql,len(#sql) -1)
select #sql +=' into TempTrunc '
--select #sql -debugging so simple, a caveman can do it
exec (#sql)
Now we can see what we have in the two tables
select 'TempTrunc' as TableNAme,* from TempTrunc
union all
select 'MaxLengths' as TableNAme,* from MaxLengths
TableNAme _col0 Col1 Col2 Col3 Col4 Col5
-------------------------------------------------------------
TempTrunc 0 10 15 20 3 10
MaxLengths 0 13 20 6 4 15
As you can see, all columns except for Col3 will cause the truncation problem
Of course we want to do something like this, it will tell us which columns have truncation problems
select case when t.col1 > tt.col1 then 'truncation' else 'no truncation' end as Col1,
case when t.col2 > tt.col2 then 'truncation' else 'no truncation' end as Col2,
case when t.col3 > tt.col3 then 'truncation' else 'no truncation' end as Col3,
case when t.col4 > tt.col4 then 'truncation' else 'no truncation' end as Col4,
case when t.col5 > tt.col5 then 'truncation' else 'no truncation' end as Col5
from MaxLengths t
join TempTrunc tt on t._col0 = tt._col0
Col1 Col2 Col3 Col4 Col5
------------------------------------------------------------------------------------
truncation truncation no truncation truncation truncation
Source
I believe you already know that this is related with inserting a string value into a column with less size
You can query table columns from know table names and compare them.
select
OBJECT_NAME(object_id),
name,
max_length,
precision,
scale
from sys.columns where object_id in (
select object_id from sys.tables where name in ('tbl','Emp')
)

SQL: count number of distinct values in every column

I need a query that will return a table where each column is the count of distinct values in the columns of another table.
I know how to count the distinct values in one column:
select count(distinct columnA) from table1;
I suppose that I could just make this a really long select clause:
select count(distinct columnA), count(distinct columnB), ... from table1;
but that isn't very elegant and it's hardcoded. I'd prefer something more flexible.
This code should give you all the columns in 'table1' with the respective distinct value count for each one as data.
DECLARE #TableName VarChar (Max) = 'table1'
DECLARE #SqlString VarChar (Max)
set #SqlString = (
SELECT DISTINCT
'SELECT ' +
RIGHT (ColumnList, LEN (ColumnList)-1) +
' FROM ' + Table_Name
FROM INFORMATION_SCHEMA.COLUMNS COL1
CROSS AppLy (
SELECT ', COUNT (DISTINCT [' + COLUMN_NAME + ']) AS ' + '''' + COLUMN_NAME + ''''
FROM INFORMATION_SCHEMA.COLUMNS COL2
WHERE COL1.TABLE_NAME = COL2.TABLE_NAME
FOR XML PATH ('')
) TableColumns (ColumnList)
WHERE
1=1 AND
COL1.TABLE_NAME = #TableName
)
EXECUTE (#SqlString)
try this (sql server 2005 syntax):
DECLARE #YourTable table (col1 varchar(5)
,col2 int
,col3 datetime
,col4 char(3)
)
insert into #YourTable values ('abcdf',123,'1/1/2009','aaa')
insert into #YourTable values ('aaaaa',456,'1/2/2009','bbb')
insert into #YourTable values ('bbbbb',789,'1/3/2009','aaa')
insert into #YourTable values ('ccccc',789,'1/4/2009','bbb')
insert into #YourTable values ('aaaaa',789,'1/5/2009','aaa')
insert into #YourTable values ('abcdf',789,'1/6/2009','aaa')
;with RankedYourTable AS
(
SELECT
ROW_NUMBER() OVER(PARTITION by col1 order by col1) AS col1Rank
,ROW_NUMBER() OVER(PARTITION by col2 order by col2) AS col2Rank
,ROW_NUMBER() OVER(PARTITION by col3 order by col3) AS col3Rank
,ROW_NUMBER() OVER(PARTITION by col4 order by col4) AS col4Rank
FROM #YourTable
)
SELECT
SUM(CASE WHEN col1Rank=1 THEN 1 ELSE 0 END) AS col1DistinctCount
,SUM(CASE WHEN col2Rank=1 THEN 1 ELSE 0 END) AS col2DistinctCount
,SUM(CASE WHEN col3Rank=1 THEN 1 ELSE 0 END) AS col3DistinctCount
,SUM(CASE WHEN col4Rank=1 THEN 1 ELSE 0 END) AS col4DistinctCount
FROM RankedYourTable
OUTPUT:
col1DistinctCount col2DistinctCount col3DistinctCount col4DistinctCount
----------------- ----------------- ----------------- -----------------
4 3 6 2
(1 row(s) affected)
and it's hardcoded.
It is not hardcoding to provide a field list for a sql statement. It's common and acceptable practice.
This won't necessarily be possible for every field in a table. For example, you can't do a DISTINCT against a SQL Server ntext or image field unless you cast them to other data types and lose some precision.
I appreciate all of the responses. I think the solution that will work best for me in this situation (counting the number of distinct values in each column of a table from an external program that has no knowledge of the table except its name) is as follows:
Run "describe table1" and pull out the column names from the result.
Loop through the column names and create the query to count the distinct values in each column. The query will look something like "select count(distinct columnA), count(distinct columnB), ... from table1".
Raj More's answer works well if you don't need to consider null as a value as count(distinct...) does not count null.
Here is a modification to count values including null by converting values to a string and replacing null with "NULL AS SOME IMPOSSIBLE STRING":
DECLARE #TableName VarChar (1024) = 'tableName'
DECLARE #SqlString VarChar (Max)
set #SqlString = (
SELECT DISTINCT
'SELECT ' +
RIGHT (ColumnList, LEN (ColumnList)-1) +
' FROM ' + Table_Name
FROM INFORMATION_SCHEMA.COLUMNS COL1
CROSS AppLy (
SELECT ', COUNT (DISTINCT coalesce(cast([' + COLUMN_NAME + '] as varchar),
''NULL AS SOME IMPOSSIBLE STRING'')) AS ' + '''' + COLUMN_NAME + ''''
FROM INFORMATION_SCHEMA.COLUMNS COL2
WHERE COL1.TABLE_NAME = COL2.TABLE_NAME
FOR XML PATH ('')
) TableColumns (ColumnList)
WHERE
COL1.TABLE_NAME = #TableName
)
EXECUTE (#SqlString)
DISTINCT is evil. Do COUNT/GROUP BY