Dynamic update statement with variable column names - sql

We're looking to do an update in several SQL Server databases to change all NULL values in a certain table to be empty strings instead of NULL. We're potentially going to be doing this across hundreds of databases. The table name will always be the same, but the column names are variable based on how the front-end application is configured (don't judge... I didn't create this system).
Is there a way to do an update on all of these columns without knowing the column names ahead of time?

You can pass the name of the column in dynamic sql:
declare #sql nvarchar (1000);
set #sql = N'update table set ' + #column_name + '= ''''';
exec sp_executesql #sql;

You can look in the sys.columns table and join on the table name or object_id.
DECLARE #OBJ_ID INT
SELECT #OBJ_ID = OBJECT_ID
FROM SYS.tables
WHERE name = 'YOURTABLE'
SELECT * FROM SYS.columns
WHERE OBJECT_ID = #OBJ_ID
You could use the name field from the sys.columns query as a basis to perform the update on.

Assuming you want all columns of varchar/char types only (or change the type filter to whatever you need):
DECLARE #tableName varchar(10)
SET #tableName = 'yourtablenamehere'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = '''' WHERE ' + c.name + ' IS NULL ;'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE t.name = #tableName AND y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
EXEC (#sql)

This can be achieved with cursors. You first select the column names like #Darren mentioned, then you Set a Cursor with those values and loop:
Open oColumnsCursor
Fetch Next From oColumnscursor
Into #ColumnName
While ##FETCH_STATUS=0
Begin
Set #oQuery = 'Update [DB]..[Table] Set [' + #ColumnName + '] = ''NewValue'' Where [' + #ColumnName + '] = ''OldValue'''
Execute(#oQuery)
Fetch Next From oColumnscursor Into #ColumnName
Set #oCount = #oCount + 1
End
Close oColumnsCursor;
Deallocate oColumnsCursor;

This will work when you know the Table Name:
DECLARE #tableName varchar(10)
SET #tableName = 'Customers'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = ISNULL('+ c.name +','''');'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
AND t.name = #tableName;
EXEC(#sql);
And this will iterate all Tables and all Columns in a Db:
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + t.name + ' SET ' + c.name + ' = ISNULL('+ c.name +','''');'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE y.name IN ('varchar', 'nvarchar', 'char', 'nchar');
EXEC(#sql);

Below is the procedure.
ALTER PROCEDURE [dbo].[util_db_updateRow]
#colval_name NVARCHAR (30), -- column and values e.g. tax='5.50'
#idf_name NVARCHAR (300), -- column name
#idn_name NVARCHAR (300), -- column value
#tbl_name NVARCHAR (100) -- table name
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
-- construct SQL
SET #sql = 'UPDATE ' + #tbl_name + ' SET ' + #colval_name +
' WHERE ' + #idf_name + '=' + #idn_name;
-- execute the SQL
EXEC sp_executesql #sql
SET NOCOUNT OFF
RETURN
END

Below is the stored procedure where you can pass Schema Name, Table Name and list of column names separted by comma.It works only in Sql Server 2016 or higher.
CREATE OR ALTER PROCEDURE UpdateData
(#SchemaName NVARCHAR(Max),#TableName NVARCHAR(MAX),#ColumnNames NVARCHAR(MAX))
AS
BEGIN
DECLARE #DynamicSql NVARCHAR(MAX);
SET #DynamicSql = 'UPDATE ' +'[' +#SchemaName+'].' + '[' +#TableName+']' +' SET ' + STUFF((SELECT ', [' + C.name + '] = ' + '''NEW_VALUE'''
FROM sys.columns C
INNER JOIN sys.tables T ON T.object_id = C.object_id
INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
WHERE
T.name = #TableName
AND S.Name = #SchemaName
AND [C].[name] in (SELECT VALUE FROM string_split(#ColumnNames,','))
FOR XML PATH('')), 1,1, '')
print #DynamicSql;
EXEC (#DynamicSql);
END

Related

How to drop default constraint based on column name

I was looking this article
and I have just a question how to add IFEXISTS() in the select part:
I want to run #command only if there some rows. I think it will throw an error if I dont check select part?
declare #table_name nvarchar(256)
declare #Command nvarchar(max) = ''
set #table_name = N'ATableName'
select #Command = #Command + 'ALTER TABLE ' + #table_name + ' drop constraint ' + d.name + CHAR(10)+ CHAR(13)
from sys.tables t
join sys.default_constraints d on d.parent_object_id = t.object_id
join sys.columns c on c.object_id = t.object_id
and c.column_id = d.parent_column_id
where t.name = #table_name and c.name in ('column1','column2','column3')
--print #Command
execute (#Command)

SQL Server how to find specific data across entire database

I have this script which finds all tables that contain data in column RGNCODE. I'm trying to amend this to find tables that contain a specific value for rgncode across all tables. So RGNCODE = 'UK'. Unsure where to add this in?
SELECT
sys.columns.name AS ColumnName,
tables.name AS TableName
FROM sys.columns
JOIN sys.tables ON sys.columns.object_id = tables.object_id
WHERE
sys.columns.name = 'rgncode' and
tables.name in (
SELECT [TableName] = so.name
FROM sysobjects so, sysindexes si
WHERE so.xtype = 'U' AND si.id = so.id
GROUP BY so.name
HAVING max(si.rows) > 0
)
A dynamic statement, using system catalog views, is an option here:
DECLARE #stm nvarchar(max) = N''
SELECT #stm = #stm +
CASE WHEN #stm = N'' THEN '' ELSE N'UNION ALL ' END +
N'SELECT ''' +
QUOTENAME(sch.[name]) + '.' + QUOTENAME(tab.[name]) + ''' AS TableName FROM ' +
QUOTENAME(sch.[name]) + '.' + QUOTENAME(tab.[name]) +
N' WHERE RGNCODE = ''UK'' HAVING COUNT(*) > 0 '
FROM sys.columns col
JOIN sys.tables tab ON col.object_id = tab.object_id
JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
WHERE col.[name] = 'RGNCODE'
PRINT #stm
EXEC sp_executesql #stm
I think the following cursor will help you:
CREATE TABLE ##DataTable
(TblName VARCHAR(100),RowNum INT)
GO
DECLARE #TableName AS VARCHAR(200)
DECLARE #SQLText AS nVARCHAR(MAX)
DECLARE #TableCount AS INT
DECLARE TableDect CURSOR
FOR
SELECT
tables.name AS TableName
FROM
sys.columns
JOIN sys.tables ON
sys.columns.object_id = tables.object_id
WHERE
sys.columns.name = 'rgncode' and tables.name in (
SELECT
[TableName] = so.name
FROM
sysobjects so,
sysindexes si
WHERE
so.xtype = 'U'
AND
si.id = so.id
GROUP BY
so.name
HAVING max(si.rows) >0 )
OPEN TableDect
FETCH NEXT FROM TableDect INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQLText = N'SELECT #NumberOUT=COUNT(*) FROM ' + QUOTENAME(#TableName) + ' WHERE rgncode=''UK'''
SET #TableCount = 0
EXECUTE sp_executesql #SQLText ,N'#NumberOUT nvarchar(25) OUTPUT' ,#NumberOUT=#TableCount OUTPUT
INSERT INTO ##DataTable values(#TableName,#TableCount)
FETCH NEXT FROM TableDect INTO #TableName
END
CLOSE TableDect;
DEALLOCATE TableDect
SELECT * FROM ##DataTable

Global Update (All Rows) On One SQL Table With The Same Criteria

IS there a way to run this across every column in a table:
UPDATE dbo.stage_a
SET Statement_Name= NULL
WHERE Statement_Name='""';
I am trying to tidy up after a data import.
Dynamic query plus Information_schema.columns. Try this.
DECLARE #cols NVARCHAR(max)='UPDATE dbo.stage_a set '
SELECT #cols += COLUMN_NAME + '=case when ' + COLUMN_NAME
+ ' = '""' then null else '+COLUMN_NAME+' end,'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'stage_a'
AND TABLE_SCHEMA = 'dbo'
SELECT #cols = LEFT(#cols, Len(#cols) - 1)
PRINT #cols
EXEC Sp_executesql #cols
UPDATE Customers
SET ContactName='Alfred Schmidt', City='Hamburg'
WHERE CustomerName='Alfreds Futterkiste';
Like this example you should specify all coloumns.(separated by commas)
If you want to replace all coloumns with NULL why don't you delete the rows:
DELETE FROM Customers
WHERE CustomerName='Alfreds Futterkiste'
You meed to first get the all columns list by using the INFORMATION_SCHEMA.COLUMNS
DECLARE #tableName varchar(10)
SET #tableName = 'mm'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = NULL WHERE ' + c.name + '= ''';'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE t.name = #tableName AND y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
EXEC (#sql)
Query is not tested may need to tweak as per your need

Search table columns for values over a certain length

So I have a database with many tables that have a column that contains a GL Account value (for financial purposes). The column name varies by table (i.e. in one table the column is called "gldebitaccount" and in another table it's called "glcreditaccount"). I was able to find all combinations of table / column pairs using the following query:
SELECT c.name AS ColName, t.name AS TableName
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE '%gl%acc%'
This query returns close to 100 pairs of tables/columns. I am trying to find any value in any of those table/column pairs that exceeds 25 chars in length. For an individual table/column, I'd typically use:
SELECT *
FROM tableName
WHERE LEN(columnName)>25
I want to avoid having to run that query 100 times with each pair. Is there any way I can do a "for each" (which I know is frowned upon in SQL since everything should be set-based). I've done sub-SELECT statements before, but not any that involved change the table in the FROM clause. Any ideas or help would be greatly appreciated!
Thanks in advance!
As the previous answer, the solution will need dynamic SQL. Here is a way that uses both dynamic SQL and cursors, and you can expect slow performance, so use at your own risk:
DECLARE #TableName NVARCHAR(128), #ColumnName NVARCHAR(128)
DECLARE #Query NVARCHAR(4000)
DECLARE CC CURSOR LOCAL FAST_FORWARD FOR
SELECT QUOTENAME(t.name), QUOTENAME(c.name)
FROM sys.columns c
INNER JOIN sys.tables t
ON c.object_id = t.object_id
WHERE c.collation_name IS NOT NULL
AND c.max_length > 25 AND c.name LIKE '%gl%acc%';
CREATE TABLE #Results(TableName NVARCHAR(128), ColumnName NVARCHAR(128));
OPEN CC
FETCH NEXT FROM CC INTO #TableName, #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Query = 'IF EXISTS(SELECT 1 FROM '+#TableName+'
WHERE LEN('+#ColumnName+') > 25)
INSERT INTO #Results
VALUES(#TableName,#ColumnName)'
EXEC sp_executesql #Query,
N'#TableName NVARCHAR(128),#ColumnName NVARCHAR(128)',
#TableName,
#ColumnName;
FETCH NEXT FROM CC INTO #TableName, #ColumnName
END
CLOSE CC
DEALLOCATE CC
SELECT *
FROM #Results
Here's an option without cursors that also doesn't add XML overhead. Note that it also protects you from potential type conflicts (e.g. try the others in a database with hierarchyid columns, like AdventureWorks), from table or column names with apostrophes, and from table names that exist in more than one schema.
DECLARE #sql NVARCHAR(MAX) = N'';
CREATE TABLE #Results
(
SchemaName NVARCHAR(128), TableName NVARCHAR(128), ColumnName NVARCHAR(128)
);
SELECT #sql += N'INSERT #Results SELECT '''
+ REPLACE(s.name,'''','''''') + ''','''
+ REPLACE(t.name,'''','''''') + ''','''
+ REPLACE(c.name,'''','''''') + '''
WHERE EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name)
+ '.' + QUOTENAME(t.name) + ' WHERE
LEN(' + QUOTENAME(c.name) + ') > 25);
'
FROM sys.columns AS c
INNER JOIN sys.tables AS t
ON c.[object_id] = t.[object_id]
INNER JOIN sys.schemas AS s
ON t.[schema_id] = s.[schema_id]
WHERE
(
c.system_type_id IN (35,99) -- text,ntext
OR (c.system_type_id IN (167,231) -- varchar,nvarchar, could be max
AND c.max_length > 25 OR c.max_length = -1)
OR (c.system_type_id IN (175,239) -- char, nchar
AND c.max_length > 25)
)
AND c.name LIKE N'%gl%acc%';
EXEC sp_executesql #sql;
SELECT SchemaName, TableName, ColumnName FROM #Results;
Yet another solution with dynamic SQL.
But now without cursors. It uses FOR XML statement and should be much faster.
DECLARE #sqlstatement VARCHAR(MAX);
SET #sqlstatement =
REPLACE (
STUFF ( (
SELECT 'UNION ALL SELECT ''' + t.name + ''' as TableName, '''
+ c.name + ''' AS ColumnName, '
+ c.name + ' AS Value FROM '
+ t.name + ' WHERE LEN (' + c.name + ') ' + CHAR(62) + ' 25'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE '%gl%acc%'
FOR XML PATH('')
), 1, 10, '')
, '>', '>')
EXEC (#sqlstatement)
You may want to add extra filter for columns by their type and max_length:
INNER JOIN sys.types ty ON c.system_type_id = ty.system_type_id
AND (
ty.name IN ('text', 'ntext')
OR (
ty.name IN ('varchar', 'char', 'nvarchar', 'nchar')
AND (c.max_length > 25 OR c.max_length = -1)
)
You will need to create dynamic SQL because you cannot dynamically specify the source table. You could do this using a cursor, or write a select statement that makes a row for each statement you need to run. This shows how to do it with a cursor. You problem looks like an acceptable usage for a cursor:
DECLARE #ColName VARCHAR(MAX);
DECLARE #TableName VARCHAR(MAX);
DECLARE #SomeSQL VARCHAR(MAX);
DECLARE db_cursor CURSOR FOR
SELECT c.name AS ColName, t.name AS TableName
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE '%gl%acc%'
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #ColName, #TableName;
WHILE ##FETCH_STATUS = 0
BEGIN
-- you need to make dynamic SQL
SELECT #SomeSQL = 'SELECT * FROM ' + #TableName + ' WHERE LEN(' + #ColName + ') > 25;'
PRINT(#SomeSQL + CHAR(10));
-- you could execute it directly if you wish.
--EXEC (#SomeSQL);
FETCH NEXT FROM db_cursor INTO #ColName, #TableName;
END
CLOSE db_cursor;
DEALLOCATE db_cursor;
I wasn't sure if you needed to do anything with the results, but this will return the records that meet the criteria you posted in your question
Declare #TableName sysname
Declare #ColName sysname
Declare #dynamic_SQL varchar(MAX)
Declare some_cursor CURSOR FOR
SELECT c.name AS ColName, t.name AS TableName
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
WHERE c.name LIKE '%gl%acc%'
OPEN some_cursor
FETCH NEXT FROM some_cursor INTO #ColName, #TableName
WHILE ##FETCH_STATUS = 0
Begin
select #dynamic_SQL = '
Select *
From ' + #TableName + '
Where LEN('+ #ColName +') > 25
'
exec (#dynamic_SQL)
FETCH NEXT FROM some_cursor INTO #ColName, #TableName
End
CLOSE some_cursor
DEALLOCATE some_cursor

How to get Column value without knowing column name ? SQL Server

I have table name as #Table_Name
I have column value as #Value but don't have the column name (but that exist at 1st position and can be Seek_id or prov_id ...I have to compare my value with this id )
How can I compare that table column name value ?
I want something like
SELECT * FROM #Table_Name
WHERE Table.Column[1].Value = #Value
for example #Table_Name = bb_match and #Value = 6
Possible this be helpful for you -
Query:
IF OBJECT_ID (N'dbo.bb_match') IS NOT NULL
DROP TABLE dbo.bb_match
CREATE TABLE dbo.bb_match (seek_id INT, prov_id INT)
INSERT INTO dbo.bb_match (seek_id, prov_id)
VALUES (6, 1), (2, 6)
DECLARE
#ColumnID TINYINT
, #Value INT
, #TableName SYSNAME
, #SQL NVARCHAR(500)
SELECT
#ColumnID = 1
, #Value = 6
, #TableName = 'dbo.bb_match'
SELECT #SQL = 'SELECT * FROM ' + #TableName + ' WHERE [' + c.name + '] = ' + CAST(#Value AS NVARCHAR(MAX))
FROM sys.objects o WITH (NOWAIT)
JOIN sys.schemas s WITH (NOWAIT) ON o.[schema_id] = s.[schema_id]
JOIN sys.columns c WITH (NOWAIT) ON o.[object_id] = c.[object_id]
WHERE o.[type] = 'U' -- <-- only for tables columns
AND s.name + '.' + o.name = #TableName
AND c.column_id = #ColumnID
PRINT #SQL
EXEC sp_executesql #SQL
Shorter, but unsafe (sys.columns contains column_name for tables, views, procedures, ...):
SELECT #SQL = 'SELECT * FROM ' + #TableName + ' WHERE [' + c.name + '] = ' + CAST(#Value AS NVARCHAR(MAX))
FROM sys.columns c WITH (NOWAIT)
WHERE c.[object_id] = OBJECT_ID(#TableName)
AND c.column_id = #ColumnID
EXEC sys.sp_executesql #SQL
Output:
SELECT * FROM dbo.bb_match WHERE [seek_id] = 6
Results:
seek_id prov_id
----------- -----------
6 1
declare #sql varchar(MAX)
declare #tablename varchar(100) = 'MyTable' --add your table name here
declare #value varchar(100) = 'SomeValue' -- add your desired value hree
select #sql = 'SELECT * FROM ' + #tablename + ' WHERE '
+ name
+ ' = ''' + #value + ''''
from sys.columns where object_id = object_id(#tablename) and column_id = 1
exec (#sql)
There are three parts to this. First I'm declaring three strings. #sql is where I will build up the query, #tablename and #value are the table and search value to look in/for. I've put in the dummy values MyTable and SomeValue to show what I'm talking about
Next I build up the sql statement. The first line sets the string as SELECT * FROM MyTable WHERE
I then add in the column name by selecting Name from the SQL SERver system table sys.columns, filtering on the first column (column_id = 1) and the table name
The next step is to add the value we want to search for in the column.
Finally, EXEC(#sql) interprets the string as a command and runs it.