Delete Record By ID From All Tables - sql

I want a quick way to delete all records relating to a particular 'master' record from both the 'master' table and all associated tables. This is primarily so that I can easily enter test records and then delete all trace of them, which would take a long time manually given that there are dozens of tables where the record could be referenced.
So, in summary, in any table which contains a column called AdmissionID, I want to delete all records where the AdmissionID is equal to a value I will specify. I thought I could do this quite easily. Here's my stored procedure:
ALTER PROCEDURE [Admin].[sp_RemoveByAdmissionID]
#admissionID int
AS
--Removes records from all tables relating the AdmissionID entered
DECLARE #loop INT
DECLARE #object sysname
DECLARE #cmdstring varchar(500)
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
BEGIN
DROP TABLE #TEMP
END
SELECT
TABLE_NAME
,ROW_NUMBER() OVER (ORDER BY TABLE_NAME) ROWID
INTO #TEMP
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
WHILE #loop <= (SELECT MAX(ROWID) FROM #TEMP)
BEGIN
SELECT #object = TABLE_NAME FROM #TEMP WHERE ROWID = #loop
IF EXISTS (SELECT * FROM SYS.COLUMNS WHERE Name = 'AdmissionID' AND Object_ID = Object_ID(#object))
BEGIN
SET #cmdstring = 'DELETE FROM ' + #object + ' WHERE AdmissionID = ' + #admissionID
Exec(#cmdstring)
END
SET #loop += 1
END
Unfortunately, executing this procedure doesn't delete any records from anywhere. I suspected the problem might be to do with my parameter not being enclosed in quotation marks in the line where I build up the cmdstring string, but even attempting to mitigate for this doesn't seem to have worked.
Any advice on where I'm going wrong, or if there's a simpler way to do this? I'd prefer not to use cascading deletes on my PK-FK relationships.
UPDATE
Following the advice of Luv I found that his query does return a very nice list of the exact commands I want to use. I thought it'd be easy from there, but I still can't make this work. The new SP I'm trying is as follows:
ALTER PROCEDURE TEST
#admissionID VARCHAR(10)
AS
DECLARE #loop INT
DECLARE #cmd VARCHAR(500)
IF OBJECT_ID('tempdb..#TEMP') IS NOT NULL
BEGIN
DROP TABLE #TEMP
END
SELECT 'DELETE FROM '+ C.TABLE_NAME +' WHERE AdmissionID = '+#admissionID AS cmd
,ROW_NUMBER() OVER (ORDER BY 'DELETE FROM '+ C.TABLE_NAME +' WHERE AdmissionID = '+#admissionID) ROWID
INTO #TEMP
FROM INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES T on C.TABLE_NAME=T.TABLE_NAME
WHERE C.COLUMN_NAME='AdmissionID'
AND T.TABLE_TYPE='BASE TABLE'
WHILE #loop <= (SELECT MAX(ROWID) FROM #TEMP)
BEGIN
SELECT #cmd = cmd
FROM #TEMP
WHERE ROWID = #loop
EXEC(#cmd)
SET #loop += 1
END

Copy the result from the Below Query and Run the Query.
declare #yourvalue varchar
set #yourvalue=''
select 'DELETE FROM '+ C.TABLE_NAME +' WHERE AdmissionID = '+#yourvalue 
from INFORMATION_SCHEMA.COLUMNS C
INNER JOIN INFORMATION_SCHEMA.TABLES T on C.TABLE_NAME=T.TABLE_NAME
where C.COLUMN_NAME='AdmissionID'
and T.TABLE_TYPE='BASE TABLE'
You can even make an SP and execute Dynamically too

Related

Find all unique values of a column name in a SQL database

We are building a large database using SQL. Every table in the database has many columns but one of the columns in the tables tells who added the row of data. That value "Name of person" is tied to a variable in SSIS. Again, the variable tells who added the row. How can I create a query to pull back all the names in that column, no matter where it is used in the database. The value of the column is different depending on the day.
RE: Every table has the same <Column_Name> ... a query to pull back all the values of that column, no matter where it is used in the database.
IF you have a large database and every table has the same column <Column_Name>, then you will be pulling a value from every row in every table ... in a very large database. Not sure that is what you want to do, but it can be easily done. The following will work even if <column_name> is only in a few tables.
Grab a list of every schema.table that contains <column_name>, then loop over it to get the the value for <column_name>. The following should work.
DECLARE #colname sysname = '<Column_name>' -- just in case it is not in every table
-- capture name of every table here
CREATE TABLE #tablename ( schema_name sysname, table_name sysname, Id INT IDENTITY(1,1) PRIMARY KEY CLUSTERED)
INSERT INTO #tablename (schema_name, table_name)
SELECT s.name schemaname, t.name tablename FROM sys.columns c
INNER JOIN sys.tables t ON t.object_id = c.object_id
INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
WHERE c.name = #colname
-- capture result of query here
CREATE TABLE #result ( schema_name sysname, table_name sysname, column_value VARCHAR(100) )
DECLARE #i INT = 1, #imax INT
SELECT #imax = MAX(Id) FROM #tablename
-- loop over tablename
DECLARE #query NVARCHAR(255)
WHILE #i <= #imax
BEGIN
SELECT #query = N'SELECT ' + schema_name + '.' + table_name + ',' + ' <Column_Name> FROM ' + schema_name + '.' + table_name
FROM #tablename WHERE Id = #i
INSERT INTO #result ( schema_name, table_name, column_value)
EXEC sp_executesql #query
SET #i += 1
END

View that displays all rows from tables returned by separate query

Need to display all rows from multiple tables. The tables to be included are to come from a separate query to ensure. So we know where the data has originated from, a new column must be added that contains the table name.
The tables to be included in this must be dynamic, to reduce maintenance, so newly created tables are automatically included.
To select the tables I have create this query:
select TABLE_NAME
from INFORMATION_SCHEMA.COLUMNS
where COLUMN_NAME = 'contact_info_type'
and TABLE_NAME NOT LIKE '%test%'
and TABLE_NAME NOT LIKE '%_STAGING'
The testing of the 'contact_info_type' column is to ensure the table is of the expected structure. All of these tables are exactly the same.
But then how do you pass these results into a new select statement?
I attempts this, which produced duplicate results and would not stop processing. Its also missing the extra column that adds in the table name
declare #tableNames nvarchar(max)
select #tableNames = COALESCE(#tableNames + ', ', '') + Cast(TABLE_NAME as varchar) from INFORMATION_SCHEMA.COLUMNS
where COLUMN_NAME = 'contact_info_type' and TABLE_NAME not like '%_STAGING' and TABLE_NAME like '%test%'
select #tableNames
declare #sqlText nvarchar(max)
set #sqlText = ''
select #sqlText = #sqlText + 'SELECT * from ' + #tableNames where person = 'Joe'
select #sqlText
Each table will contain a few thousand rows but the condition (name = 'Joe') will limit the results to around 100.
Running Server 2008 R2 SP3 (10.50).
May be this query would help you,
But you need to add the additional columns as what you need in your #results table.
DECLARE #tables TABLE (tableName VARCHAR(1000), nmbr INT IDENTITY(1,1))
DECLARE #results TABLE (person varchar(500), tablename varchar(1000))
DECLARE #i INT,
#tableName varchar(1000),
#sql varchar(500)
INSERT INTO #tables
SELECT DISTINCT TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'contact_info_type'
AND TABLE_NAME NOT LIKE '%test%'
AND TABLE_NAME NOT LIKE '%_STAGING'
SELECT #i = MAX(nmbr)
FROM #tables AS t
WHILE (#i > 0)
BEGIN
SELECT #tableName = tablename
FROM #tables
WHERE nmbr = #i
SET #sql = 'SELECT person, '''+#tablename +''' as tablename
FROM '+ #tableName +'
WHERE person = ''joe'''
INSERT INTO #results
EXEC (#sql)
SET #i = #i - 1
END
SELECT * FROM #results

SQL: Looping through a column, stored the value as a variable, run SQL, then move on to the next line?

I'm currently shifting roles at my job and trying to teach myself some SQL Skills.
Scenario: I'm in charge of 1 database - 10 tables with 10 Primary Keys. Every month, our code team publishes updates to the tables. I am suppose to drop the tables and generate scripts to create the updated tables.
Rather than just drop the old tables and stored procedures, I want to rename my current tables to preserve the structure/data for whatever reason.
In my database, I have an additional table called "TableUpdateList" with 1 column "TableName" and 10 rows - each row containing the name of the updated column (Row 1 = TableName1, Row 2 = TableName2, Row 3 = TableName3)
I would like to be able to "loop" through the TableUpdateList Table and insert each value into a set of SQL statements.
For Example, here are the SQL statements I want to run:
--drop the previous backup table
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME = '*TableName1*'+'_Old') DROP TABLE TableName1_Old
-- rename the current tables to _old
EXEC sp_rename *TableName1*, TableName1_Old;
I'm trying to find a way to scroll through the column of my TableUpdateList and run the above two statements filling in where I've italicized with whatever value is present in that row.
Just taking a wild stab because I think in order to get an answer here, you have to try something so here is my pseudo-code:
Declare #TableNames as List
For i in #TableNames
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES where TABLE_NAME = '*i*'+'_Old') DROP TABLE TableName1_Old
-- rename the current tables to _old
EXEC sp_rename *i*, TableName1_Old;
Oi, thanks in advance for any help or a point in the right direction to where I could do some further reading about the above online.
You can use sp_executesql with CURSORS for such type of work. Here is what i think you need:
Test objects:
CREATE TABLE TableName1 ( ID INT )
GO
CREATE TABLE TableName2 ( ID INT )
GO
CREATE TABLE TableNames ( Name NVARCHAR(MAX) )
GO
INSERT INTO TableNames
VALUES ( 'TableName1' ),
( 'TableName2' )
Script itself:
DECLARE #name NVARCHAR(MAX) ,
#dropStatement NVARCHAR(MAX),
#renameStatement NVARCHAR(MAX)
DECLARE cur CURSOR FAST_FORWARD READ_ONLY
FOR
SELECT Name
FROM dbo.TableNames
OPEN cur
FETCH NEXT FROM cur INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS ( SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #name + '_Old' )
BEGIN
SET #dropStatement = 'DROP TABLE ' + #name + '_Old'
EXEC sp_executesql #dropStatement
END
SET #renameStatement = 'sp_rename ' + #name + ', ' + #name + '_Old';
EXEC sp_executesql #renameStatement
FETCH NEXT FROM cur INTO #name
END
CLOSE cur
DEALLOCATE cur
After this you should add TableName1 and TableName2 again.
Cursors must be avoided as long as possible.
--Preparing script which would check if the old tables exists. If it does,
--it drops the old table
--e.g. first the value 'Table1' is found in TableUpdateList table.
--Then, Table1_Old is deleted and Table1 is renamed to Table1_Old
SELECT 'DROP TABLE ' + b.name + '_Old; EXEC sp_rename ''' + b.name+ ''', ''' + b.name+ '_Old;''' AS [Action]
INTO #Action
FROM INFORMATION_SCHEMA.TABLES A JOIN TableUpdateList B ON A.TABLE_NAME = b.NAME + '_Old'
DECLARE #sql VARCHAR(8000)
SELECT #sql = COALESCE(#sql + ' ', '') + [Action]
FROM #Action
select #sql
--EXEC (#sql)
First verify the value of variable #sql. Then, uncomment the last line to execute the code.
SQL fiddle

Looping through a column in SQL table that contains names of other tables

I have fairly new to using SQL, currently I have a table that has a column that contains the names of all the tables I want to use for one query, so what I want to do is to loop through that column and go to every single one of these tables and then search one of their columns for a value (there could be multiple values), so whenever a table contains the value, I will list the name of the table. Could someone give me a hint of how this is done? Is cursor needed for this?
I don't have enough reputation to comment but is the table with the column that contain the table names all in one column, meaning that all the table names are comma separated or marked with some sort of separator? This would cause the query to be a little more complicated as you would have to take care of that before you start looping through your table.
However, this would require a cursor, as well as some dynamic sql.
I will give a basic example of how you can go about this.
declare #value varchar(50)
declare #tableName varchar(50)
declare #sqlstring nvarchar(100)
set #value = 'whateveryouwant'
declare #getTableName = cursor for
select tableName from TablewithTableNames
OPEN #getTableName
fetch NEXT
from #getTableName into #tableName
while ##FETCH_STATUS = 0
BEGIN
set #sqlstring = 'Select Count(*) from ' + #tableName + 'where ColumnNameYouwant = ' + #value
exec #sqlstring
If ##ROWcount > 0
insert into #temptable values (#tableName)
fetch next
from #getTableName into #tableName
END
select * from #temptable
drop table #temptable
close #getTableName
deallocate #getTableName
I'm currently not able to test this out as for time constraint reasons, but this is how I would go about doing this.
You could try something like this:
--Generate dynamic SQL
DECLARE #TablesToSearch TABLE (
TableName VARCHAR(50));
INSERT INTO #TablesToSearch VALUES ('invoiceTbl');
DECLARE #SQL TABLE (
RowNum INT,
SQLText VARCHAR(500));
INSERT INTO
#SQL
SELECT
ROW_NUMBER() OVER (ORDER BY ts.TableName) AS RowNum,
'SELECT * FROM ' + ts.TableName + ' WHERE ' + c.name + ' = 1;'
FROM
#TablesToSearch ts
INNER JOIN sys.tables t ON t.name = ts.TableName
INNER JOIN sys.columns c ON c.object_id = t.object_id;
--Now run the queries
DECLARE #Count INT;
SELECT #Count = COUNT(*) FROM #SQL;
WHILE #Count > 0
BEGIN
DECLARE #RowNum INT;
DECLARE #SQLText VARCHAR(500);
SELECT TOP 1 #RowNum = RowNum, #SQLText = SQLText FROM #SQL;
EXEC (#SQLText);
DELETE FROM #SQL WHERE RowNum = #RowNum;
SELECT #Count = COUNT(*) FROM #SQL;
END;
You would need to change the "1" I am using as an example to the value you are looking for and probably add a CONVERT/ CAST to make sure the column is the right data type?
You actually said that you wanted the name of the table, so you would need to change the SQL to:
'SELECT ''' + ts.TableName + ''' FROM ' + ts.TableName + ' WHERE ' + c.name + ' = 1;'
Another thought, it would probably be best to insert the results from this into a temporary table so you can dump out the results in one go at the end?

SQL: Query many tables with same column name but different structure for specific value

I'm working on cleaning up an ERP and I need to get rid of references to unused users and user groups. There are many foreign key constraints and therefor I want to be sure to really get rid of all traces!
I found this tidy tidbit of code to find all tables in my db with a certain column name, in this case let's look at the user groups:
select table_name from information_schema.columns
where column_name = 'GROUP_ID'
With the results I can search through the 40+ tables for my unused ID... but this is tedius. So I'd like to automate this and create a query that loops through all these tables and deletes the rows where it finds Unused_Group in the GROUP_ID column.
Before deleting anything I'd like to visualize the existing data, so I started to build something like this using string concatenation:
declare #group varchar(50) = 'Unused_Group'
declare #table1 varchar(50) = 'TABLE1'
declare #table2 varchar(50) = 'TABLE2'
declare #tableX varchar(50) = 'TABLEX'
select #query1 = 'SELECT ''' + rtrim(#table1) + ''' as ''Table'', '''
+ rtrim(#group) + ''' = CASE WHEN EXISTS (SELECT GROUP_ID FROM ' + rtrim(#table1)
+ ' WHERE GROUP_ID = ''' + rtrim(#group) + ''') then ''MATCH'' else ''-'' end FROM '
+ rtrim(#table1)
select #query2 = [REPEAT FOR #table2 to #tableX]...
EXEC(#query1 + ' UNION ' + #query2 + ' UNION ' + #queryX)
This gives me the results:
TABLE1 | Match
TABLE2 | -
TABLEX | Match
This works for my purposes and I can run it for any user group without changing any other code, and is of course easily adaptable to DELETE from these same tables, but is unmanageable for the 75 or so tables that I have to deal with between users and groups.
I ran into this link on dynamic SQL which was intense and dense enough to scare me away for the moment... but I think the solution might be in there somewhere.
I'm very familiar with FOR() loops in JS and other languages, where this would be a piece of cake with a well structured array, but apparently it's not so simple in SQL (I'm still learning, but found alot of negative talk about the FOR and GOTO solutions available...). Ideally a I'd have a script that queries to find tables with a certain column name, query each table as above, and spit me a list of matches, and then execute a second similar script to delete the rows.
Can anyone help point me in the right direction?
Ok, try this, there are three variables; column, colValue and preview. Column should be the column you're checking equality on (Group_ID), colValue the value you're looking for (Unused_Group) and preview should be 1 to view what you'll delete and 0 to delete it.
Declare #column Nvarchar(256),
#colValue Nvarchar(256),
#preview Bit
Set #column = 'Group_ID'
Set #colValue = 'Unused_Group'
Set #preview = 1 -- 1 = preview; 0 = delete
If Object_ID('tempdb..#tables') Is Not Null Drop Table #tables
Create Table #tables (tID Int, SchemaName Nvarchar(256), TableName Nvarchar(256))
-- Get all the tables with a column named [GROUP_ID]
Insert #tables
Select Row_Number() Over (Order By s.name, so.name), s.name, so.name
From sysobjects so
Join sys.schemas s
On so.uid = s.schema_id
Join syscolumns sc
On so.id = sc.id
Where so.xtype = 'u'
And sc.name = #column
Select *
From #tables
Declare #SQL Nvarchar(Max),
#schema Nvarchar(256),
#table Nvarchar(256),
#iter Int = 1
-- As long as there are tables to look at keep looping
While Exists (Select 1
From #tables)
Begin
-- Get the next table record to look at
Select #schema = SchemaName,
#table = TableName
From #tables
Where tID = #iter
-- If the table we're going to look at has dependencies on tables we have not
-- yet looked at move it to the end of the line and look at it after we look
-- at it's dependent tables (Handle foreign keys)
If Exists (Select 1
From sysobjects o
Join sys.schemas s1
On o.uid = s1.schema_id
Join sysforeignkeys fk
On o.id = fk.rkeyid
Join sysobjects o2
On fk.fkeyid = o2.id
Join sys.schemas s2
On o2.uid = s2.schema_id
Join #tables t
On o2.name = t.TableName Collate Database_Default
And s2.name = t.SchemaName Collate Database_Default
Where o.name = #table
And s1.name = #schema)
Begin
-- Move the table to the end of the list to retry later
Update t
Set tID = (Select Max(tID) From #tables) + 1
From #tables t
Where tableName = #table
And schemaName = #schema
-- Move on to the next table to look at
Set #iter = #iter + 1
End
Else
Begin
-- Delete the records we don't want anymore
Set #Sql = Case
When #preview = 1
Then 'Select * ' -- If preview is 1 select from table
Else 'Delete t ' -- If preview is not 1 the delete from table
End +
'From [' + #schema + '].[' + #table + '] t
Where ' + #column + ' = ''' + #colValue + ''''
Exec sp_executeSQL #SQL;
-- After we've done the work remove the table from our list
Delete t
From #tables t
Where tableName = #table
And schemaName = #schema
-- Move on to the next table to look at
Set #iter = #iter + 1
End
End
Turning this into a stored procedure would simply involve changing the variables declaration at the top to a sproc creation so you would get rid of...
Declare #column Nvarchar(256),
#colValue Nvarchar(256),
#preview Bit
Set #column = 'Group_ID'
Set #colValue = 'Unused_Group'
Set #preview = 1 -- 1 = preview; 0 = delete
...
And replace it with...
Create Proc DeleteStuffFromManyTables (#column Nvarchar(256), #colValue Nvarchar(256), #preview Bit = 1)
As
...
And you'd call it with...
Exec DeleteStuffFromManyTable 'Group_ID', 'Unused_Group', 1
I commented the hell out of the code to help you understand what it's doing; good luck!
You're on the right track with INFORMATION_SCHEMA objects. Execute the below in a query editor, it produces SELECT and DELETE statements for tables that contain GROUP_ID column with 'Unused_Group' value.
-- build select DML to manually review data that will be deleted
SELECT 'SELECT * FROM [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] WHERE [GROUP_ID] = ''Unused_Group'';'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'GROUP_ID';
-- build delete DML to remove data
SELECT 'DELETE FROM [' + TABLE_SCHEMA + '].[' + TABLE_NAME + '] WHERE [GROUP_ID] = ''Unused_Group'';'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'GROUP_ID';
Since this seems to be a one-time cleanup effort, and especially since you need to review data before it is deleted, I don't see the value in making this more complicated.
Consider adding referential integrity and enforcing cascade delete, if you can. It won't help with visualizing the data before you delete it, but will help controlling orphaned rows.