SQL Server : delete specific data after searching column name in whole database - sql

I have a table CenterDetails like this
| uid | CenterID | CenterName | AccessLock |
| ----|----------|------------|------------|
|1 | 1 | Andheri | 1 |
|2 | 2 | Borivali | 1 |
|1 | 3 | Dadar | 1 |
I have 100's of tables in my database.
If I want to delete Dadar center, then first I need to check, to the whole database, where centerID=3 exist or not.
If dadar center's CenterID does not exist in the whole database where column names are CenterID.
How can I find that CenterID=3 is present in the whole database or not?
Thanks in advance!

I guess this code will help you:
DECLARE #ColumnName SYSNAME = 'CenterID '
,#ColumnValue NVARCHAR(256) = '3'
DECLARE #DynamicSQLStatement NVARCHAR(MAX)
SELECT #DynamicSQLStatement =STUFF
(
(
SELECT ' UNION ALL ' + CHAR(10) + ' SELECT TOP 1 ''' + t.name + ''' AS T FROM ' + SCHEMA_NAME(t.schema_id) + '.' + t.name + ' WHERE ' + #ColumnName + ' = ' + #ColumnValue + CHAR(10)
FROM sys.tables t
INNER JOIN sys.columns c
on t.[object_id] = c.[object_id]
WHERE c.[name] = #ColumnName
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,12
,''
);
EXEC sp_executesql #DynamicSQLStatement
It is looking for all tables that have specific column. Then it query all these tables in order to find if the given column has a specific value, if yes, it will return the table name.
What will be better here is to read about data integrity or more specific the foreign keys.

Hope it helps you
DECLARE #TablesColumns TABLE
(
ID INT IDENTITY,
COLUMN_NAME VARCHAR(50),
TABLE_NAME VARCHAR(50)
)
DECLARE #MinId INT,
#MaxId INT,
#Sql NVARCHAR(MAX),
#TableName VARCHAR(50),
#ColumnName VARCHAR(50)
INSERT INTO #TablesColumns(COLUMN_NAME,TABLE_NAME)--Here we get list of table containing 'centerID'
SELECT COLUMN_NAME,TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME='Credits'
SELECT #MinId=MIN(Id) FROM #TablesColumns
SELECT #MaxId=MAX(Id) FROm #TablesColumns
WHILE (#MinId <=#MaxId)
BEGIN
SELECT #TableName=TABLE_NAME FROm #TablesColumns WHERE Id=#MinId
SELECT #ColumnName=COLUMN_NAME FROm #TablesColumns WHERE Id=#MinId
SET #Sql='DELETE From ' +#TableName+ ' WHERE '+#ColumnName+'=3'
--PRINT #Sql
SET #MinId=#MinId+1
Exec (#Sql)
END

Related

Comparing row counts of two tables for each database in an instance

I have a single sql instance with many databases.
In a single query I want to count the rows of two tables in each database, Shops and Locations, to be able to compare the values.
So far I have the following query:
SELECT ('SELECT COUNT(1) FROM [' + name + '].[abc].[Shops]') as shopCount,
('SELECT COUNT(1) FROM [' + name + '].[def].[Locations]') as locationCount,
name as DB
FROM sys.databases
WHERE OBJECT_ID('[' + name + '].[abc].[Shops]') IS NOT NULL AND
OBJECT_ID('[' + name + '].[def].[Locations]' ) IS NOT NULL
Which results in the following output
shopCount | locationsCount | DB
------------------------------------------------------------------------------------------------------------------
SELECT COUNT(1) FROM [database1].[abc].[Shops] | SELECT COUNT(1) FROM [database1].[def].[Locations] | database1
------------------------------------------------------------------------------------------------------------------
SELECT COUNT(1) FROM [database2].[abc].[Shops] | SELECT COUNT(1) FROM [database2].[def].[Locations] | database2
So pretty obviously, I am not executing the strings as a query but am unable to figure out how to do so.
Something like this:
DECLARE #DynamicTSQLStatement NVARCHAR(MAX);
CREATE TABLE #DataSource
(
[shopCount] INT
,[locationCount] INT
,[database] SYSNAME
);
SET #DynamicTSQLStatement = STUFF
(
(
SELECT ';INSERT INTO #DataSource SELECT (SELECT COUNT(1) FROM [' + name + '].[abc].[Shops]), (SELECT COUNT(1) FROM [' + name + '].[def].[Locations]), ''' + name +''''
FROM sys.databases
WHERE OBJECT_ID('[' + name + '].[abc].[Shops]') IS NOT NULL AND
OBJECT_ID('[' + name + '].[def].[Locations]' ) IS NOT NULL
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
EXEC sp_executesql #DynamicTSQLStatement;
SELECT *
FROM #DataSource;
DROP TABLE #DataSource;
You are trying to do some dynamic sql. Have a read here: http://www.sqlservertutorial.net/sql-server-stored-procedures/sql-server-dynamic-sql/
the first example seems to be what you're looking for.

SQL Columns to Rows

From my database table(Customer) I need to select one record and display the result by interchanging columns to rows.
EG:
actual result
| ID | Name | Age |
| 1 | Tom | 25 |
expected output
| Name | Value|
| ID | 1 |
| Name | Tom |
| Age | 25 |
Other details:
Customer table has different number of colums in different databases
I need to do this inside a function (So I cannot use dynamic queries, UNPIVOT)
Please advice me.
This uses CROSS APPLY with VALUES to perform unpivot
--Set up test data
CREATE TABLE dbo.TEST(ID INT IDENTITY (1,1),Name VARCHAR(20),Age TINYINT)
INSERT INTO dbo.TEST VALUES
('Shaggy',32)
,('Fred',28)
,('Velma',26)
,('Scooby',7)
DECLARE #table VARCHAR(255) = 'Test'
DECLARE #schema VARCHAR(255) = 'dbo'
DECLARE #ID INT = 2
--Create a VALUES script for the desired table
DECLARE #col VARCHAR(1000)
SELECT
#col = COALESCE(#col,'') + '(''' + c.name + ''' ,CAST(A.[' + c.name + '] AS VARCHAR(20))),'
FROM
sys.objects o
INNER JOIN sys.columns c
ON
o.object_id = c.object_id
WHERE
o.name = #table
AND
SCHEMA_NAME(o.schema_id) = #schema
ORDER BY
c.column_id
--Remove trailing ,
SET #col = LEFT(#col,LEN(#col)-1)
--Build Script for unpivoting data.
DECLARE #str VARCHAR(2000) = '
SELECT
CAST(C.Col AS VARCHAR(20)) AS [Name]
,CAST(C.Val AS VARCHAR(20)) AS [Value]
FROM
[' + #schema + '].[' + #table + '] A
CROSS APPLY (VALUES ' + #col + ') C(Col,Val)
WHERE
A.ID = ''' + CAST(#ID AS VARCHAR(8)) + ''''
--Run Script
EXEC (#str)

SQL Server - Include table name programatically

I am in the process of writing a pretty large query that selects from multiple tables and unions them. Because of some really poor database design, a table is created for every user.
What I am doing is something like this:
SELECT *
FROM tbl1
UNION ALL
SELECT *
FROM tbl2
What I am looking for is something generic to add to each line that will enable me to select the table name along with what's inside the table.
I won't accept the below as an answer, because that is not what I've asked for.
SELECT *, 'tbl1'
FROM tbl1
UNION ALL
SELECT *, 'tbl2'
FROM tbl2
To do something like that you either have to make some rather complex and large query into the system views (sys.tables and sys.columns)
But you're properly better off building the query dynamically in what ever code lanuage you use and execute it as a string:
A very rough example could be something like
DECLARE #tableName varchar(255) = 'tbl1';
DECLARE #tableName2 varchar(255) = 'tbl2';
DECLARE #columnList varchar(255) = 'Col1, Col2, Col3';
EXEC(
'SELECT '+ #columnList +' FROM ' +#tableName + '
UNION ALL
SELECT '+ #columnList +' FROM ' +#tableName2
);
Before each select from the table add a SELECT NULL, NULL, 'tableName'; (add as many NULL as table columns so that union all doesn't 'fail'. Or Instead of NULL use '-' or whatever.
I guess the tables looping and obtaining the column names shouldn't be an issue..
Below demo:
create table #t1(C1 int, C2 int)
create table #t2(C1 int, C2 int)
insert #t1 (C1, C2) values (1,2)
insert #t2 (C1, C2) values (3,4)
insert #t2 (C1, C2) values (5,6)
declare #t1 varchar(10) = '#t1';
declare #t2 varchar(10) = '#t2';
declare #cols varchar(100) = 'C1, C2';
declare #sql nvarchar(4000) = ''
set #sql =
' SELECT NULL, NULL, ''' + #t1 + ''' ' + char(10) + char(13) +
' UNION ALL ' + char(10) + char(13) +
' SELECT '+ #cols +', NULL FROM ' + #t1 + char(10) + char(13) +
' UNION ALL ' + char(10) + char(13) +
' SELECT NULL, NULL, ''' + #t2 + ''' ' + char(10) + char(13) +
' UNION ALL ' + char(10) + char(13) +
' SELECT '+ #cols +', NULL FROM ' + #t2
-- select #sql
exec (#sql);
In production code you already need to construct a query that has FROM table_name so you should just add that table name in as a projected column at the same time.
Technically there is a way of doing this in versions that support dm_db_page_info though but this would be very inefficient and require elevated permissions.
CREATE TABLE dbo.T(X INT);
INSERT INTO T VALUES (1),(2),(3);
SELECT OBJECT_NAME(pg_info.object_id) AS table_name, T.*
FROM dbo.T
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%) pl
CROSS APPLY sys.dm_db_page_info(db_id(), pl.file_id, pl.page_id, 'LIMITED') pg_info
Returns
+------------+---+
| table_name | X |
+------------+---+
| T | 1 |
| T | 2 |
| T | 3 |
+------------+---+

Selecting columns from a query

I'm using a request to get a collection of columns name:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE [...]
From this collection, I'd like to count every not null, not empty value from the original table group by column name.
Let's say I have a table containing
COL1 | COL2 | COL3
------------------
VAL1 | VAL2 | NULL
VAL3 | | VAL4
VAL5 | |
I'm looking for a request to get:
COL1 | 3
COL2 | 1
COL2 | 1
It's for analytics purpose.
Thanks for your help!
Here is a simple process. Run the following query:
SELECT 'SELECT ''' + COLUMN_NAME + ''', COUNT(['+COLUMN_NAME']) as NotNull FROM [' +SCHEMA_NAME+ '].['+TABLE_NAME+ '] union all '
FROM INFORMATION_SCHEMA.COLUMNS
WHERE [...]
Copy the results into a query window, remove the final union all, and run the query.
The below code seems to work for your issue
create table sample
(
col1 varchar(10),
col2 varchar(10),
col3 varchar(10)
)
INSERT INTO sample (COL1,COL2,COL3) VALUES ('VAL1 ',' VAL2 ',NULL);
INSERT INTO sample (COL1,COL2,COL3) VALUES ('VAL3 ',' ',' VAL4');
INSERT INTO sample (COL1,COL2,COL3) VALUES ('VAL5 ',' ',' ');
DECLARE #cols1 NVARCHAR(MAX);
DECLARE #sql NVARCHAR(MAX);
SELECT #cols1 = STUFF((
SELECT ', COUNT(CASE WHEN len(['+ t1.NAME + '])!=0 THEN 1 END) AS ' + t1.name
FROM sys.columns AS t1
WHERE t1.object_id = OBJECT_ID('sample')
--ORDER BY ', COUNT([' + t1.name + ']) AS ' + t1.name
FOR XML PATH('')
), 1, 2, '');
SET #sql = '
SELECT ' + #cols1 + '
FROM sample
'
EXEC(#sql)
Hereis my little longer take on this:
declare #cols table (colID integer, colName varchar(50))
declare #results table (colName nvarchar(50), valueCount bigint)
-- table name
declare #tableName nvarchar(50) = 'INSERT TABLE NAME HERE'
-- select column names from table
insert into #cols
select column_id, name from sys.columns where object_id = object_id(#tableName) order by column_id
declare #currentColID int = 0
declare #currentName nvarchar(50) = ''
declare #currentCount bigint = 0
declare #sql nvarchar(max) -- where the dynamic sql will be stored
-- go through all columns
while (1 = 1)
begin
-- step by id
select top 1 #currentColID = c.colID, #currentName = c.colName from #cols c
where c.colid > #currentColID order by c.colID
if ##ROWCOUNT = 0 break;
-- dynamic query to get non-empty, not-null counts
select #sql = 'select #p1=COUNT(' + #currentName + ') from ' + #tableName +
' where ' + #currentName + ' is not null or LEN(' + #currentName + ') > 0'
exec sp_executesql #sql, N'#p1 bigint output', #p1 = #currentCount output
-- insert result to buffer
insert into #results values (#currentName, #currentCount)
end
-- print the buffer
select * from #results
Have fun :)

Select a non-empty value for each column in each table in each database

There might be a better way to do this. But I'm trying to find columns that might contain personal information.
Problem is that the tables are poorly named (non-english, abbreviations). So I'm running this dynamic script, that will return all tables in all databases and their columns.
USE master;
DECLARE #SQL varchar(max)
SET #SQL=';WITH cteCols (dbName, colName) AS (SELECT NULL, NULL '
SELECT #SQL=#SQL+'UNION
SELECT
'''+d.name COLLATE Czech_CI_AS +'.''+sh.name COLLATE Czech_CI_AS +''.''+o.name COLLATE Czech_CI_AS ''dbSchTab''
, c.name COLLATE Czech_CI_AS ''colName''
FROM ['+d.name+'].sys.columns c
JOIN ['+d.name+'].sys.objects o ON c.object_id=o.object_id
JOIN ['+d.name+'].sys.schemas sh ON o.schema_id=sh.schema_id
WHERE o.[type] = ''U'' COLLATE Czech_CI_AS'
FROM sys.databases d
SET #SQL = #SQL + ')
SELECT
*
FROM cteCols cs
ORDER BY 1;'
EXEC (#SQL);
Result:
+---------------------+------------+
| DatabaseSchemaTable | ColumnName |
+---------------------+------------+
| dev1.dbo.Users | Col1 |
| dev1.dbo.Users | Col2 |
| dev1.dbo.Users | Col3 |
| dev1.dbo.Users | Col4 |
+---------------------+------------+
But because of the poor column naming, I can't tell what data is in these columns. I'd like to select a TOP (1) non NULL value from each column, but I'm struggling.
Required result:
+---------------------+------------+--------------+
| DatabaseSchemaTable | ColumnName | ColumnValue |
+---------------------+------------+--------------+
| dev1.dbo.Users | Col1 | 20 |
| dev1.dbo.Users | Col2 | 2018-02-06 |
| dev1.dbo.Users | Col3 | 202-555-0133 |
| dev1.dbo.Users | Col4 | John Doe |
+---------------------+------------+--------------+
Ideas I had:
I would need to either transpose each of the tables (probably not a
job for PIVOT)
I could join with the table dynamically and only display the current column. But I can't use dynamic column in correlated subquery.
Any ideas?
I would create a temporary table such as ##cols, and then use this temporary table to loop through the table, running update queries on the table itself. Mind you, we have a lot of spaces and other potentially troublesome characters in our field names. Therefore I updated your cte with some QUOTENAMEs around the field / table / schema / db names.
USE master;
DECLARE #SQL varchar(max);
SET #SQL=';WITH cteCols (dbName, colName, top1Value) AS (SELECT NULL, NULL, CAST(NULL AS VARCHAR(MAX)) '
SELECT #SQL=#SQL+' UNION
SELECT
'''+QUOTENAME(d.[name]) COLLATE Czech_CI_AS +'.''+QUOTENAME(sh.name) COLLATE Czech_CI_AS +''.''+QUOTENAME(o.name) COLLATE Czech_CI_AS ''dbSchTab''
, QUOTENAME(c.name) COLLATE Czech_CI_AS ''colName'', CAST(NULL AS VARCHAR(MAX)) AS ''top1Value''
FROM ['+d.[name]+'].sys.columns c
JOIN ['+d.[name]+'].sys.objects o ON c.object_id=o.object_id
JOIN ['+d.[name]+'].sys.schemas sh ON o.schema_id=sh.schema_id
WHERE o.[type] = ''U'' COLLATE Czech_CI_AS'
FROM sys.databases d;
SET #SQL = #SQL + ')
SELECT
*
INTO ##Cols
FROM cteCols cs
ORDER BY 1;'
EXEC (#SQL);
DECLARE #colName VARCHAR(255), #dbName VARCHAR(255), #SQL2 NVARCHAR(MAX);
DECLARE C CURSOR FOR SELECT [colName],[dbName] FROM ##Cols;
OPEN C;
FETCH NEXT FROM C INTO #colName, #dbName;
WHILE ##FETCH_STATUS=0
BEGIN
SET #SQL2='UPDATE ##Cols SET [top1Value] = (SELECT TOP 1 x.'+#colName+' FROM '+#dbName+' x WHERE x.'+#colName+' IS NOT NULL) WHERE [colName]='''+#colName+''' AND [dbName]='''+#dbName+''''
EXEC sp_executesql #SQL2
FETCH NEXT FROM C INTO #colName, #dbName
END;
CLOSE C;
DEALLOCATE C;
SELECT * FROM ##Cols;
It's not pretty, but it'd suit your needs.
You might try this:
--In this table we write our findings
CREATE TABLE ##TargetTable(ID INT IDENTITY, TableName VARCHAR(500), FirstRowXML XML);
--the undocumented sp "MSforeachtable" allows to create a statement where the
--question mark is a place holder for the actual table
--(SELECT TOP 1 * FROM ? FOR XML PATH('row')) will create one single XML with all first row's values
EXEC sp_MSforeachtable 'INSERT INTO ##TargetTable(TableName,FirstRowXML) SELECT ''?'', (SELECT TOP 1 * FROM ? FOR XML PATH(''row''))';
--Now it is easy to get what you want
SELECT ID
,TableName
,col.value('local-name(.)','nvarchar(max)') AS colname
,col.value('text()[1]','nvarchar(max)') AS colval
FROM ##TargetTable
CROSS APPLY FirstRowXML.nodes('/row/*') A(col);
GO
DROP TABLE ##TargetTable
Just use SELECT TOP X to get more than one row...
UPDATE
The following will create a table with all columns of all tables of all databases and fetch one value per row.
CREATE TABLE ##TargetTable(ID INT IDENTITY
,TABLE_CATALOG VARCHAR(300),TABLE_SCHEMA VARCHAR(300),TABLE_NAME VARCHAR(300),COLUMN_NAME VARCHAR(300)
,DATA_TYPE VARCHAR(300),CHARACTER_MAXIMUM_LENGTH INT, IS_NULLABLE VARCHAR(10),Command VARCHAR(MAX),OneValue NVARCHAR(MAX));
EXEC sp_MSforeachdb
'USE ?;
INSERT INTO ##TargetTable(TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,IS_NULLABLE,Command)
SELECT ''?''
,c.TABLE_SCHEMA
,c.TABLE_NAME
,c.COLUMN_NAME
,c.DATA_TYPE
,c.CHARACTER_MAXIMUM_LENGTH
,c.IS_NULLABLE
, CASE WHEN c.IS_NULLABLE=''YES''
THEN ''SELECT CAST(MAX('' + QUOTENAME(c.COLUMN_NAME) + '') AS NVARCHAR(MAX))''
ELSE ''SELECT TOP 1 CAST('' + QUOTENAME(c.COLUMN_NAME) + '' AS NVARCHAR(MAX))''
END
+ '' FROM '' + QUOTENAME(''?'') + ''.'' + QUOTENAME(c.TABLE_SCHEMA) + ''.'' + QUOTENAME(c.TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS c
INNER JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_CATALOG=t.TABLE_CATALOG AND c.TABLE_SCHEMA=t.TABLE_SCHEMA AND c.TABLE_NAME=T.TABLE_NAME AND t.TABLE_TYPE=''BASE TABLE''
WHERE c.DATA_TYPE NOT IN(''BINARY'',''VARBINARY'',''IMAGE'',''NTEXT'')';
DECLARE #ID INT,#Command VARCHAR(MAX);
DECLARE cur CURSOR FOR SELECT ID,Command FROM ##TargetTable
OPEN cur;
FETCH NEXT FROM cur INTO #ID,#Command;
WHILE ##FETCH_STATUS=0
BEGIN
SET #Command = 'UPDATE ##TargetTable SET OneValue=(' + #Command + ') WHERE ID=' + CAST(#ID AS VARCHAR(100))
PRINT #command;
EXEC(#Command);
FETCH NEXT FROM cur INTO #ID,#Command;
END
CLOSE cur;
DEALLOCATE cur;
GO
SELECT * FROM ##TargetTable;
GO
DROP TABLE ##TargetTable;