drop all SQL tables that appear in a query - sql

I am attempting to develop a script to compare two databases to determine extra tables in one, then delete those tables. Here's my current script to locate the extraneous tables:
-- Any location where TARGET appears, replace TARGET with the database to be
-- modified
-- Any location where MODEL appears, replace MODEL with the database being
-- used as a model for comparison
select 'TARGET' as dbname, t1.table_name
from TARGET.[INFORMATION_SCHEMA].[tables] as t1
where table_name not in (select t2.table_name
from
MODEL.[INFORMATION_SCHEMA].[tables] as t2
)
That gives me the results I need, but now I need to fire out how to drop the tables. I'm afraid I'm utterly lost at this point. Wouldn't mind a way to declare variables instead of typing in the DBname repeatedly either, but not sure I can in this instance.

You could use dynamic SQL:
DECLARE #sql NVARCHAR(MAX) = N'';
select #sql += CONCAT('DROP TABLE ',QUOTENAME(t1.table_name,''''),';',CHAR(13))
from TARGET.[INFORMATION_SCHEMA].[tables] as t1
where table_name not in (select t2.table_name
from MODEL.[INFORMATION_SCHEMA].[tables] as t2);
SELECT #sql; -- debug
--EXEC(#sql);
EDIT:
MySQL(may need some nitpicking):
SET #s = (select GROUP_CONCAT('DROP TABLE ''' + t1.table_name + ''';' SEPARATOR CHAR(13))
from TARGET.INFORMATION_SCHEMA.tables as t1
where table_name not in (select t2.table_name
from MODEL.INFORMATION_SCHEMA.tables as t2));
SELECT #s; -- debug
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

My immediate thought is to assign each result in your query a Row Number and place your results into a temp table. Use a while loop starting at 1 and loop through the maximum number you have within that temp table getting the name of the table that Row Number is assigned to each loop. Use that name to delete from the database.
select 'TARGET' as dbname, t1.table_name
, ROW_NUMBER() OVER (Partition By t1.table_name) AS RowNumber
INTO #temp
from TARGET.[INFORMATION_SCHEMA].[tables] as t1
where table_name not in (select t2.table_name
from MODEL.[INFORMATION_SCHEMA].[tables] as t2)
DECLARE #counter INT = 1
DECLARE #maxNum INT
SELECT #maxNum = MAX(RowNumber) FROM #temp
While #counter <= #maxNum
BEGIN
DECLARE #tableName AS VARCHAR(MAX)
SELECT #tableName = table_name FROM #temp WHERE RowNumber = #counter
DELETE TABLE #tableName ' This may not be possible, but follow my lead
#counter += 1
END
DROP TABLE #temp
I am not sure if "DELETE TABLE #tableName" is a proper command but there is probably a very similar solution using what I have given you. I assume this is T-SQL..

Related

Selecting columns in a table where table name is selected from another table and concatenate them selecting only specific columns

I have a table:
TableName rn
Tab_1 1
Tab_2 2
Tab_3 3
Tab_1, Tab_2 and Tab_3 are tables stored in the database.
What i want is to read all these tables using a loop, select specific columns (say col1, col2, and col3) and concatenate them.
What i tried was:
'''
DECLARE
#table NVARCHAR(128),
#sql NVARCHAR(MAX);
SET #table = N'select tablename from #db2 where rn=1';
SET #sql = N'SELECT * FROM ' + #table;
EXEC sp_executesql #sql;
'''
This query does not exactly concatenate the tables one by one but i am first trying to select the tables dynamically first before i use them in a loop. This does not seem to be working, it returns 'incorrect syntax near 'select''.
#db2 is the temp table that has all the table names.
I have looked at various methods but am not able to figure one out to suit this specific problem.
How do i go about working this out?
You are actually storing SQL select statement into #table by the Set Statement
SET #table = N'select tablename from #db2 where rn=1';
Due to which the final #sql has the statement like "SELECT * FROM select tablename from #db2 where rn=1"
While you need to set the value into #table by
Select #table = db.TableName From #db2 As db With (Nolock) Where db.rn = 1
Try this way, it will work. You need either use While Loop or Cursor for your requirments.

Retrieve Max loaded date across all tables on a DB

Output I'm trying to get to;
(Database name = ATT)
Table Name
Column name
MAX loaded date = MAX(loaded_date) for this column only
loaded_date is a column in around 50 tables in a database with the same name and datatype (Datetime)
select * FROM sys.tables
select * FROM syscolumns
I've been exploring the system tables without much luck, looking at some posts it may be done dynamic SQL which I've never done.
You can write an sql that writes an sql..
SELECT REPLACE(
'select ''{tn}'' as table_name, max(loaded_date) as ld from {tn} union all'
,'{tn}',table_name)
FROM
information_schema.columns
WHERE
column_name = 'loaded_date'
Run that, then copy all but the final UNION ALL out of the results window and into the query window, and run again
If you wanted to get all this into a single string for dynamic exec, i guess it'd look like (untested) a procedure that contained:
DECLARE #x NVARCHAR(MAX);
SELECT #x =
STRING_AGG(
REPLACE(
'select ''{tn}'' as table_name, max(loaded_date) as ld from {tn}'
,'{tn}',table_name)
,' union all ')
FROM
information_schema.columns
WHERE
column_name = 'loaded_date';
EXECUTE sp_executesql #x;
If your SQLS is old and doesnt have string_agg it's a bit more awkward - but there are many examples of "turn rows into CSV" in sql server that look like STUFF..FOR XML PATH - https://duckduckgo.com/?t=ffab&q=rows+to+CSV+SQLS&ia=web
I wrote up a more permanent type of script that does this. It returns a result set of the list of tables in the current database with a column named loaded_date along with the MAX(loaded_date) result from each table. This script individually queries each table by looping through and running the query on each table individually and keeping track of the max value for each table in a table variable. It also has a #Debug variable that allows you to see the text of the queries that would be run instead of actually running them and implements custom error message to troubleshoot any issues.
/*disable row count messages*/
SET NOCOUNT ON;
/*set to 1 to debug (aka just print queries instead of running)*/
DECLARE #Debug bit = 0;
/*get list of tables to query and assign a unique index to row to assist in looping*/
DECLARE #TableList TABLE(
SchemaAndTableName nvarchar(257) NOT NULL
,OrderToQuery bigint NOT NULL
,MaxLoadedDate datetime NULL
,PRIMARY KEY (OrderToQuery)
);
INSERT INTO #TableList (SchemaAndTableName,OrderToQuery)
SELECT
CONCAT(QUOTENAME(s.name),N'.', QUOTENAME(t.name)) AS SchemaAndTableName
,ROW_NUMBER() OVER(ORDER BY s.name, t.name) AS OrderToQuery
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.name = N'loaded_date';
/*declare and set some variables for loop*/
DECLARE #NumTables int = (SELECT TOP (1) OrderToQuery FROM #TableList ORDER BY OrderToQuery DESC);
DECLARE #I int = 1;
DECLARE #CurMaxDate datetime;
DECLARE #CurTable nvarchar(257);
DECLARE #CurQuery nvarchar(max);
/*start loop*/
WHILE #I <= #NumTables
BEGIN
/*build text of current query*/
SET #CurTable = (SELECT SchemaAndTableName FROM #TableList WHERE OrderToQuery = #I);
SET #CurQuery = CONCAT(N'SELECT #MaxDateOut = MAX(loaded_date) FROM ', #CurTable, N';');
/*check debugging status*/
IF #Debug = 0
BEGIN
BEGIN TRY
EXEC sys.sp_executesql #stmt = #CurQuery
,#params = N'#MaxDateOut datetime OUTPUT'
,#MaxDateOut = #CurMaxDate OUTPUT;
END TRY
BEGIN CATCH
DECLARE #ErrorMessage nvarchar(max) = CONCAT(
N'Error querying table ', #CurTable, N'.', NCHAR(13), NCHAR(10)
,N'Errored query: ', NCHAR(13), NCHAR(10), #CurQuery, NCHAR(13), NCHAR(10)
,N'Error message: ', ERROR_MESSAGE()
);
RAISERROR(#ErrorMessage,16,1) WITH NOWAIT;
/*on error end loop so error can be investigated*/
SET #I = #NumTables + 1;
END CATCH;
END;
ELSE /*currently debugging*/
BEGIN
PRINT(CONCAT(N'Debug output: ', #CurQuery));
END;
/*update value in our table variable*/
UPDATE #TableList
SET MaxLoadedDate = #CurMaxDate
WHERE
OrderToQuery = #I;
/*increment loop*/
SET #I = #I + 1;
END;
SELECT
SchemaAndTableName AS TableName
,MaxLoadedDate AS Max_Loaded_date
FROM
#TableList;
I like this solution better as querying each table one at a time would be much less system impact than attempting one large UNION ALL query. Querying a large set of a tables all at once could cause some serious resource semaphore or locking contention (depending on usage of your db).
It is fairly well commented, but let me know if something is not clear.
Also, just a note, dynamic SQL should be used as a last resort. I provided this script to answer your question, but you should explore better options than something like this.
You can go for undocumented stored procedure sp_MSforeachtable. But, don't use in production code, as this stored procedure might not be available in future versions.
Read more on sp_MSforeachtable
EXEC sp_MSforeachtable 'SELECT ''?'' as tablename, max(loaded_Date) FROM ?'

MSSQL: procedure to remove duplicates

Consider the table which does not have any primary or foreign keys. I would like to write procedure which will remove all the duplicate rows given the table name.
The row should be considered duplicate of other if all of the fields are the same.
Can you suggest me if this is possible. One thing I tried is to group by every field but this approach is not universal.
You could achieve it using Dynamic-SQL
Quick backed solution (great room for improvements):
CREATE TABLE tab1(a INT, b INT);
INSERT INTO tab1(a,b) VALUES (1,1),(1,1),(1,1),(2,3);
GO
Procedure:
CREATE PROCEDURE dbo.remove_duplicates
#tab_name SYSNAME
,#debug BIT = 0
AS
BEGIN
SET NOCOUNT ON;
-- TODO: validation if table does not exist, raise error
-- TODO: Add #schema parameter
-- TODO: Wrap with BEGIN TRY, omit calculated columns, CAST `TEXT/IMAGE/BINARY`....
DECLARE #sql NVARCHAR(MAX) =
'WITH cte AS
(
SELECT *, rn = ROW_NUMBER() OVER(PARTITION BY <cols> ORDER BY (SELECT 1))
FROM <tab_placeholder>
)
DELETE FROM cte
WHERE rn <> 1;';
DECLARE #cols NVARCHAR(MAX) = STUFF((SELECT ',' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tab_name
AND TABLE_SCHEMA = 'dbo'
FOR XML PATH('')), 1, 1, '');
SET #sql = REPLACE(#sql, '<tab_placeholder>', QUOTENAME(#tab_name));
SET #sql = REPLACE(#sql, '<cols>', #cols);
IF #debug = 1 SELECT #sql;
EXEC dbo.sp_executesql #sql;
END
GO
Execution:
EXEC [dbo].[remove_duplicates] #tab_name = 'tab1', #debug = 1;
SELECT * FROM tab1;
LiveDemo
This will remove duplicates from a table. Your partition by must contain the fields that you wish to group by to determine what a duplicate is. In your case, all of them.
IF OBJECT_ID('tempdb..#TABLE') IS NOT NULL DROP TABLE #TABLE
CREATE TABLE #TABLE ( SOMEINT INT,SOMEVALUE VARCHAR(255) )
INSERT INTO #TABLE ( SOMEINT, SOMEVALUE )
VALUES (1,'VALUE1')
,(1,'VALUE2')
,(1,'VALUE2')
,(1,'VALUE3')
,(1,'VALUE4')
,(1,'VALUE4')
,(1,'VALUE4')
,(1,'VALUE4')
,(1,'VALUE5')
,(1,'VALUE6')
,(1,'VALUE6')
,(1,'VALUE6')
,(1,'VALUE7')
,(1,'VALUE8')
,(1,'VALUE8')
,(1,'VALUE9')
,(1,'VALUE10')
;WITH dedup
AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY SOMEINT,SOMEVALUE ORDER BY SOMEINT ASC) AS SEQUENCE
FROM #TABLE
)
DELETE
FROM dedup
WHERE SEQUENCE > 1
GO
SELECT * FROM #TABLE
There are number of ways
First,
Create a temp table, and copy distinct data to that temp table. Delete or truncate the data from your actual table. And copy the temp table to your actual table. Drop the temp table
SELECT DISTINCT * INTO #table1 from TABLE1
DELETE FROM TABLE1
INSERT INTO TABLE1
SELECT * FROM #table1
DROP TABLE #table1
or
Second,
Add one column to the table, update that column using ROW_NUMBER PARTITION, then remove the rows where COLUMN <> 1 . Drop the newly created column.

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

Delete Record By ID From All Tables

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