Spool Results from Cursor Into Table - sql

I have a cursor that I'm using to find NULL columns in a database. I'm using this to eliminate NULL Columns from an upload of this data to Salesforce using dbAMP. I'd like to modify this to spool the results into a Table and include the Table Name and Column name.
declare #col varchar(255), #cmd varchar(max)
DECLARE getinfo cursor for
SELECT c.name FROM sys.tables t JOIN sys.columns c ON t.Object_ID =
c.Object_ID
WHERE t.Name = 'Account'
OPEN getinfo
FETCH NEXT FROM getinfo into #col
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #cmd = 'IF NOT EXISTS (SELECT top 1 * FROM Account WHERE [' + #col +
'] IS NOT NULL) BEGIN print ''' + #col + ''' end'
EXEC(#cmd)
FETCH NEXT FROM getinfo into #col
END
CLOSE getinfo
DEALLOCATE getinfo
I've have not had any success in modifying this cursor to put results in a table. Any guidance would be appreciated.

Make the Print a Select then Insert into (tbl with same column definition).
Create a table with the same columns in the same order.
Then put an Insert into yourtable(your columns in the same order as output from the exec().
Any change in table columns in the future may break this. The table and the query should have the same columns. If you are cautious and control the order of columns in the select and insert, it shouldn't matter about the table column order, but it is still good practice imho.
Example (insert into table with dynamic sql)
if object_id('dbo.ColumnMatch','U') is not null drop table dbo.ColumnMatch;
create table dbo.ColumnMatch (
id int identity(1,1) not null primary key
,column_name varchar(512)
);
declare #col varchar(256) = 'This Column Name'
declare #s varchar(max) = 'select ''' + #col + '''';
insert into ColumnMatch (column_name)
exec(#s);
select * from ColumnMatch;
Not Print but select and fix the Insert Into statement. :)
if object_id('dbo.ColumnMatch','U') is not null drop table dbo.ColumnMatch;
create table dbo.ColumnMatch (
id int identity(1,1) not null primary key
,column_name varchar(512)
);
declare #col varchar(255), #cmd varchar(max)
DECLARE getinfo cursor for
SELECT c.name FROM sys.tables t JOIN sys.columns c ON t.Object_ID =
c.Object_ID
WHERE t.Name = 'Account'
OPEN getinfo
FETCH NEXT FROM getinfo into #col
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #cmd = 'IF NOT EXISTS (SELECT top 1 * FROM Account WHERE [' + #col +
'] IS NOT NULL) BEGIN select ''' + #col + ''' column_name end'
Insert into ColumnMatch (column_name)
EXEC(#cmd)
FETCH NEXT FROM getinfo into #col
END
CLOSE getinfo
DEALLOCATE getinfo
select * from ColumnMatch;

Related

SQL query to find all references of a particular column in a database?

I need to write some SQL to find all references of a particular column in a database. The column that I'm trying to find references to exists in a different databases. I've found a few examples of finding references of a column that exist in the same database:
In SQL Server, how can I find everywhere a column is referenced?
But I'm having problems figuring out how to do this for a column that exists in a different database. Can you provide the SQL for this? For example purposes, let's refer to the external column I'm trying to find as:
MyExternalDB.MyExternalSchema.MyExternalTable.MyExternalColumn
Ok, just run this and make sure you set your ColumnName variable
USE [master];
GO
IF OBJECT_ID('tempdb..#columns') IS NOT NULL
DROP TABLE #columns;
GO
CREATE TABLE #columns
( databaseName nvarchar(MAX),
columnid int,
columnName nvarchar(MAX),
objectid int,
objectName nvarchar(MAX));
DECLARE #databaseName sysname;
DECLARE #columnName nvarchar(MAX) = 'ColumnName';
DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC FOR
SELECT [name]
FROM [sys].[databases]
WHERE [state] = 0
AND [name] NOT IN ( 'tempdb', 'master', 'msdb', 'model' );
OPEN cur;
FETCH NEXT FROM cur
INTO #databaseName;
WHILE ( ##FETCH_STATUS != -1 )
BEGIN;
IF ( ##FETCH_STATUS != -2 )
BEGIN;
DECLARE #statement nvarchar(MAX);
SET #statement =N'Use '+ #databaseName +
N';
if EXISTS (SELECT name FROM sys.[columns] WHERE name = ''' + #columnName + ''')
BEGIN;
INSERT [#columns] ( [databaseName], [columnid], [columnName], [objectid], [objectName] )
SELECT ''' + #databaseName + N''',
c.[column_id],
c.[name],
o.[object_id],
o.[name]
FROM sys.[columns] c
INNER JOIN sys.[objects] o
ON [o].[object_id] = [c].[object_id]
WHERE c.[name] = ''' + #columnName + ''';
END;';
EXEC [sys].[sp_executesql] #stmt = #statement;
END;
FETCH NEXT FROM cur
INTO #databaseName;
END;
CLOSE cur;
DEALLOCATE cur;
SELECT * FROM [#columns];

SQL: Select columns WITHOUT NULL values only in a table

This question is the exact opposite of SQL: Select columns with NULL values only.
Given a table with 1024 columns, how to find all columns WITHOUT null values?
Input:a table with 1024 columns
Output:col1_name(no null values) col2_name(no null values)...
If you want to avoid using a CURSOR, this method will simply list out the column names of any columns that have no NULL values in them anywhere in the table... just set the #TableName at the top:
DECLARE #tableName sysname;
DECLARE #sql nvarchar(max);
SET #sql = N'';
SET #tableName = N'Reports_table';
SELECT #sql += 'SELECT CASE WHEN EXISTS (SELECT 1 FROM ' + #tableName + ' WHERE '+ COLUMN_NAME + ' IS NULL) THEN NULL ELSE ''' + COLUMN_NAME +
''' END AS ColumnsWithNoNulls UNION ALL '
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #tableName
SELECT #sql = SUBSTRING(#sql, 0, LEN(#sql) - 10);
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results;
CREATE TABLE #Results (ColumnsWithNoNulls sysname NULL);
INSERT INTO #Results EXEC(#sql);
SELECT * FROM #Results WHERE ColumnsWithNoNulls IS NOT NULL
As a bonus, the results are in a temp table, #Results, so you can query to get any information you want... counts, etc.
i modified the Select columns with NULL values only.
to work for your case :
SET ANSI_WARNINGS OFF
declare #col varchar(255), #cmd varchar(max)
DECLARE getinfo cursor for
SELECT c.name FROM sys.tables t JOIN sys.columns c ON t.Object_ID = c.Object_ID
WHERE t.Name = 'Reports_table'
OPEN getinfo
FETCH NEXT FROM getinfo into #col
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #cmd = 'IF (SELECT sum(iif([' + #col + '] is null,1,null)) FROM Reports_table) is null BEGIN print ''' + #col + ''' end'
exec(#cmd)
FETCH NEXT FROM getinfo into #col
END
CLOSE getinfo
DEALLOCATE getinfo
SET ANSI_WARNINGS on

SQL Cursor to use Table and Field Names from Temp Table

I'll preface this by letting you all know that I promised myself a few years ago never to use a cursor in SQL where it's not needed. Unfortunately I think I may have to use one in my current situation but it's been so long that I'm struggling to remember the correct syntax.
Basically, I've got a problem with CONVERT_IMPLICIT happening in queries because I have data types that are different for the same field in different tables so I'd like to eventually convert these to int. But to do this I need to check whether all data can be converted to int or not to see how big the job is.
I've got the query below which gives me a list of all tables in the database that contain the relevant field in a list;
IF OBJECT_ID('tempdb..#BaseData') IS NOT NULL DROP TABLE #BaseData
GO
CREATE TABLE #BaseData (Table_Name varchar(100), Field_Name varchar(100), Data_Type_Desc varchar(20), Data_Max_Length int, Convertible bit)
DECLARE #FieldName varchar(20); SET #FieldName = 'TestFieldName'
INSERT INTO #BaseData (Table_Name, Field_Name, Data_Type_Desc, Data_Max_Length)
SELECT
o.name ,c.name ,t.name ,t.max_length
FROM sys.columns c
JOIN sys.types t
ON c.user_type_id = t.user_type_id
JOIN sys.objects o
ON c.object_id = o.object_id
WHERE c.name LIKE '%' + #FieldName + '%'
AND o.type_desc = 'USER_TABLE'
Which gives results like this;
Table_Name Field_Name Data_Type_Desc Data_Max_Length Convertible
Table1 TestFieldName varchar 8000 NULL
Table2 TestFieldName nvarchar 8000 NULL
Table3 TestFieldName int 4 NULL
Table4 TestFieldName varchar 8000 NULL
Table5 TestFieldName varchar 8000 NULL
What I'd like to do is to check if all data in the relevant table & field can be converted to an int and update the 'convertible' field (1 if there's data that can't be converted, 0 if the data is fine). I've got the following calculation which works perfectly fine;
'SELECT
CASE
WHEN COUNT(' + #FieldName + ') - SUM(ISNUMERIC(' + #FieldName + ')) > 0
THEN 1
ELSE 0
END
FROM ' + #TableName
And gives the result that I'm after. But I'm struggling to get to the correct syntax to create the cursor which will look at each row in my temp table and run this SQL accordingly. It then needs to update the final column of the temp table with the result of the query (1 or 0).
This will have to be run on a couple of hundred databases which is why I need this list to be dynamic, there may well be custom tables in some databases (in fact, it's pretty likely).
If anybody could give any guidance it would be greatly appreciated.
Thanks
I made a couple of changes to your original query but here is something that should work. I have done similar things in the past :-)
Changes:
Added schema to the source table - my test database had matches in multiple schemas
Changed datatypes to sysname, smallint to match table definitions or names could get truncated
IF OBJECT_ID('tempdb..#BaseData') IS NOT NULL DROP TABLE #BaseData;
GO
CREATE TABLE #BaseData (Schema_Name sysname, Table_Name sysname, Field_Name sysname, Data_Type_Desc sysname, Data_Max_Length smallint, Convertible bit);
DECLARE #FieldName varchar(20); SET #FieldName = 'TestFieldName';
INSERT INTO #BaseData (Schema_Name, Table_Name, Field_Name, Data_Type_Desc, Data_Max_Length)
SELECT
s.name, o.name ,c.name ,t.name ,t.max_length
FROM sys.columns c
JOIN sys.types t
ON c.user_type_id = t.user_type_id
JOIN sys.objects o
ON c.object_id = o.object_id
JOIN sys.schemas s ON o.schema_id=s.schema_id
WHERE c.name LIKE '%' + #FieldName + '%'
AND o.type_desc = 'USER_TABLE';
--select * from #BaseData;
DECLARE #sName sysname,
#tName sysname,
#fName sysname,
#sql VARCHAR(MAX);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
SELECT Schema_Name,
Table_Name,
Field_Name
FROM #BaseData;
OPEN c;
FETCH NEXT FROM c INTO #sName, #tName, #fName;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'UPDATE #BaseData SET Convertible =
(SELECT
CASE
WHEN COUNT(' + #fName + ') - SUM(ISNUMERIC(' + #fName + ')) > 0
THEN 1
ELSE 0
END Convertible
FROM ' + #sName + '.' + #tName + ')
FROM #BaseData WHERE Schema_Name = ''' + #sName + ''' AND Table_Name = ''' + #tName + ''' AND Field_Name = ''' + #fName + '''';
--select #sql;
EXEC(#sql);
FETCH NEXT FROM c INTO #sName, #tName, #fName;
END
CLOSE c;
DEALLOCATE c;
select *
from #BaseData;
If I understand your question, I would do something like this to identify those records that do not cast as an [int].
You didn't state which version of SQL Server you're using; TRY_CAST and TRY_CONVERT are 2012 or later.
DECLARE #test AS TABLE ( [field] [sysname] );
INSERT INTO #test
( [field] )
VALUES ( N'1' ),
( N'a' );
SELECT [field]
FROM #test
WHERE TRY_CAST([field] AS [INT]) IS NULL;
-- this is the basic sql syntax for a cursor
CURSOR (https://msdn.microsoft.com/en-us/library/ms180169.aspx)
DECLARE #parameter [sysname];
BEGIN
DECLARE [field_cursor] CURSOR
FOR
SELECT [value]
FROM [<schema>].[<table>];
OPEN [field_cursor];
FETCH NEXT FROM [field_cursor] INTO #parameter;
WHILE ##FETCH_STATUS = 0
BEGIN
-- do something really interesting here
FETCH NEXT FROM [field_cursor] INTO #parameter;
END;
CLOSE [field_cursor];
DEALLOCATE [field_cursor];
END;
I wasn't able to test this but it should do what you're looking for. Just plop this in after you create your temp table:
DECLARE #tName VARCHAR(20),
#fName VARCHAR(20),
#dType VARCHAR(20),
#dLength INT,
#sql VARCHAR(MAX);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
SELECT Table_Name,
Field_Name,
Data_Type_Desc,
Data_Max_Length
FROM #BaseData;
OPEN c;
FETCH NEXT FROM c INTO #tName, #fName, #dType, #dLength;
WHILE ##FETCH_STATUS = 0
BEGIN
IF((COUNT(#FieldName) - SUM(ISNUMERIC(#FieldName))) > 0)
BEGIN
SET #sql = 'UPDATE ' + #tName + ' SET Convertible = 1 WHERE Table_Name = ''' + #tName + '''';
END
ELSE
BEGIN
SET #sql = 'UPDATE ' + #tName + ' SET Convertible = 0 WHERE Table_Name = ''' + #tName + '''';
END
EXEC(#sql);
FETCH NEXT FROM c INTO #TableName, #FieldName, #DataType, #DataLength;
END
CLOSE c;
DEALLOCATE c;

Find all database tables where a common column is set to a value

All database tables have a UserId field of [uniqueidentifier] type.
I need to query the entire database and get the list of tables that have UserId set to a specific value.
Right now I achieved this by using cursor and the results are horrible and are difficult to read. How can I improve this query to retrieve back a clear list with tables and count of record that have UserId set to a specific value, instead of using this:
DECLARE #TableName VARCHAR(127);
DECLARE #Value VARCHAR(512);
DECLARE #SqlCommand varchar(1000)
--Use cursor to loop through database tables that contain UserId column
DECLARE db_cursor CURSOR FOR
SELECT t.name AS TableName
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name = 'UserId';
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #TableName;
WHILE ##FETCH_STATUS = 0
BEGIN
--Check if the next table has any UserId matching the where clause
EXEC('SELECT COUNT(UserId) , ''' + #TableName + ''' FROM ' + #TableName + ' WHERE UserId = ''FF13ACCA-022C-4296-AB3D-A35700E35BB3''');
FETCH NEXT FROM db_cursor INTO #TableName;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
You made all the difficult part, just put the value in a temp table and select them once you've finished.
DECLARE #TableName VARCHAR(127);
DECLARE #Value VARCHAR(512);
DECLARE #SqlCommand varchar(1000)
--Creta temp table
CREATE TABLE #Results (Number int, Tablename sysname)
--Use cursor to loop through database tables that contain UserId column
DECLARE db_cursor CURSOR FOR
SELECT t.name AS TableName
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name = 'UserId';
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #TableName;
WHILE ##FETCH_STATUS = 0
BEGIN
--Check if the next table has any UserId matching the where clause
EXEC('INSERT INTO #Results (Number, ''' + #TableName + ''') SELECT COUNT(UserId) , ''' + #TableName + ''' FROM ' + #TableName + ' WHERE UserId = ''FF13ACCA-022C-4296-AB3D-A35700E35BB3''');
FETCH NEXT FROM db_cursor INTO #TableName;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
SELECT * FROM #Results
DROP TABLE #Results
I cannot test it but this should be the way

How to detect and remove a column that contains only null values?

In my table table1 there are 6 columns Locations,a,b,c,d,e.
Locations [a] [b] [c] [d] [e]
[1] 10.00 Null Null 20.00 Null
[2] Null 30.00 Null Null Null
i need the result like
Locations [a] [b] [d]
[1] 10.00 Null 20.00
[2] Null 30.00 Null
My question is how to detect and delete column that contains all null values using sql query.
Is it possible?
If yes then please help and give sample.
Here is a fast (and ugly) stored proc that takes the name of the table and print (or drop if you want it to) the fields that are full of nulls.
ALTER procedure mysp_DropEmptyColumns
#tableName nvarchar(max)
as begin
declare #FieldName nvarchar(max)
declare #SQL nvarchar(max)
declare #CountDef nvarchar(max)
declare #FieldCount int
declare fieldNames cursor local fast_forward for
select c.name
from syscolumns c
inner join sysobjects o on c.id=o.id
where o.xtype='U'
and o.Name=#tableName
open fieldNames
fetch next from fieldNames into #FieldName
while (##fetch_status=0)
begin
set #SQL=N'select #Count=count(*) from "'+#TableName+'" where "'+#FieldName+'" is not null'
SET #CountDef = N'#Count int output';
exec sp_executeSQL #SQL, #CountDef, #Count = #FieldCount output
if (#FieldCount=0)
begin
set #SQL = 'alter table '+#TableName+' drop column '+#FieldName
/* exec sp_executeSQL #SQL */
print #SQL
end
fetch next from fieldNames into #FieldName
end
close fieldNames
end
This uses a cursor, and is a bit slow and convoluted, but I suspect that this is a kind of procedure that you'll be running often
How to detect whether a given column has only the NULL value:
SELECT 1 -- no GROUP BY therefore use a literal
FROM Locations
HAVING COUNT(a) = 0
AND COUNT(*) > 0;
The resultset will either consist of zero rows (column a has a non-NULL value) or one row (column a has only the NULL value). FWIW this code is Standard SQL-92.
SQL is more about working on rows rather than columns.
If you're talking about deleting rows where c is null, use:
delete from table1 where c is null
If you're talking about dropping a column when all rows have null for that column, I would just find a time where you could lock out the DB from users and execute one of:
select c from table1 group by c
select distinct c from table1
select count(c) from table1 where c is not null
Then, if you only get back just NULL (or 0 for that last one), weave your magic (the SQL Server command may be different):
alter table table1 drop column c
Do this for whatever columns you want.
You really need to be careful if you're deleting columns. Even though they may be full of nulls, there may be SQL queries out there that use that column. Dropping the column will break those queries.
SELECT * FROM table1 WHERE c IS NOT NULL -- or also SELECT COUNT(*)
To detect if indeed this column has no values at all.
ALTER TABLE table1 DROP COLUMN c
is the query to remove the column if it is deemed desirable.
Try this stored procedure with your table name as input.
alter proc USP_DropEmptyColumns
#TableName varchar(255)
as
begin
Declare #col varchar(255), #cmd varchar(max)
DECLARE getinfo cursor for
SELECT c.name FROM sys.tables t JOIN sys.columns c ON t.Object_ID = c.Object_ID
WHERE t.Name = #TableName
OPEN getinfo
FETCH NEXT FROM getinfo into #col
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #cmd = 'IF NOT EXISTS (SELECT top 1 * FROM [' + #TableName + '] WHERE [' + #col + '] IS NOT NULL)
BEGIN
ALTER TABLE [' + #TableName + '] DROP Column [' + #col + ']
end'
EXEC(#cmd)
FETCH NEXT FROM getinfo into #col
END
CLOSE getinfo
DEALLOCATE getinfo
end
PROC PRINT DATA=TABLE1;RUN;
PROC TRANSPOSE DATA=TABLE1 OUT=TRANS1;VAR A B C D E;RUN;
DATA TRANS2;SET TRANS1;IF COL1 = . AND COL2 = . THEN DELETE;RUN;
PROC TRANSPOSE DATA=TRANS2 OUT=TABLE2 (DROP=_NAME_);VAR COL1-COL2;RUN;
PROC PRINT DATA=TABLE2;RUN;
If you want to perform the stored proc on all the tables in your database.
DECLARE #table_name AS VARCHAR(128);
DECLARE table_cursor CURSOR FOR
SELECT name FROM sys.tables;
OPEN table_cursor;
FETCH NEXT FROM table_cursor INTO #table_name;
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC USP_DropEmptyColumns #table_name;
FETCH NEXT FROM table_cursor INTO #table_name;
END;
CLOSE table_cursor;
DEALLOCATE table_cursor;