Comparing table names across multiple SQL Server databases - sql

I am trying to run a SQL Server export which compares the table names listed in three or more databases on a SQL Server instance (actually the number is a lot higher!)
I can use something simple like the following to get a list of all of the tables in a particular database.
SELECT [name]
FROM DB1.sys.tables
What I would like to be able to do is to be able to join these for multiple databases to create a single view to spot differences (and similarities).
So for example if I have three databases with the following tables
DB1:
Customers
Orders
Products
DB2:
Products
Orders
DB3:
Products
Suppliers
How could I join them all together to get something like the following?
DB1
DB2
DB3
Customers
NULL
NULL
Orders
Orders
NULL
Products
Products
Products
NULL
NULL
Suppliers
Thanks!

This script should give you what you are looking for and can be easily extended by fiddling with the WHERE clause around line 20
--Set up some global temporary tables
CREATE TABLE ##all_tables(
[database_name] sysname,
[table_name] sysname
)
CREATE TABLE ##tables_for_relevant_dbs(
[database_name] sysname,
[table_name] sysname
)
--Capture a list of all the tables in all of the databases on the instance
exec sp_MSforeachdb 'insert into ##all_tables select ''?'', [name] from [?].sys.tables'
--Filter to get the tables from the relevant databases
INSERT INTO ##tables_for_relevant_dbs
SELECT dbs.[database_name],tbls.[table_name]
FROM (select distinct database_name from ##all_tables) dbs
LEFT JOIN ##all_tables tbls on tbls.database_name=dbs.database_name
WHERE dbs.database_name like '%' --PUT SOMETHING ELSE HERE
--This magic TSQL concatenates a column of values into a single string
--This string is then used in the dynamic pivoting below
DECLARE #dblist VARCHAR(MAX)
SELECT
#dblist = STUFF((SELECT ', [' + d_name + ']' AS [text()]
FROM (SELECT DISTINCT
database_name d_name
FROM ##tables_for_relevant_dbs) d
ORDER BY d_name
FOR XML PATH('')), 1, 1, '')
--Pivot the data to turn the database names into columns
EXEC ('SELECT ' + #dblist + '
FROM (SELECT at1.database_name, at2.table_name src, at1.table_name dest
FROM ##tables_for_relevant_dbs at1
LEFT JOIN ##tables_for_relevant_dbs at2 on at2.database_name=at1.database_name and at2.table_name=at1.table_name) t
PIVOT (max(t.src) FOR [database_name] IN (' + #dblist + ')) as [PVT]')
--Clean up
DROP TABLE ##all_tables
DROP TABLE ##tables_for_relevant_dbs

using common table expression to get table names in the information schema table and full outer join does the trick.
;with tables_in_DB1 as
(
select * from DB1.INFORMATION_SCHEMA.TABLES
where TABLE_TYPE='BASE TABLE'
),
tables_in_DB2 as
(
select * from DB2.INFORMATION_SCHEMA.TABLES
where TABLE_TYPE='BASE TABLE'
),
tables_in_DB3 as
(
select * from DB3.INFORMATION_SCHEMA.TABLES
where TABLE_TYPE='BASE TABLE'
)
select db1.table_name as DB2,db2.table_name as DB2,
db3.table_name as DB3 from tables_in_DB1 db1
full outer join tables_in_DB2 db2 on db1.TABLE_NAME=db2.TABLE_NAME
full outer join tables_in_DB3 db3 on db2.TABLE_NAME=db3.TABLE_NAME

Related

Execute create table query from column in SQL server

I have two select statements which are to create table and insert values into table. How to execute all the queries inside the column at one go? Below is my code and the output:
select n.*
into #norm
from specNormalization n
select n.*
into #raw
from rawdata n
select distinct 'CREATE TABLE raw' + c.desttablename + ' (' + STUFF(
(select ','+QUOTENAME( c.[destfieldname] ) + c.datatype
from #norm c
group by c.destfieldname, c.datatype
for xml path ('')),1,1,'') as createTableSQL
select distinct 'INSERT INTO raw' + c.desttablename +
select d.contents
from #raw d join #norm c on d.tablename = c.desttablename
as insertTableSQL
rawdata table
desttablename
destfieldname
datatype
rawtable
SbjNum
int
rawtable
Surveyor
nvarchar(20)
rawtable
Location
nvarchar(20)
rawtable2
SbjNum
int
rawtable2
Name
nvarchar(20)
rawtable2
Address
nvarchar(20)
specnomalization table
tablename
destfieldname
contents
rawtable
SbjNum
1
rawtable
Surveyor
Alex
rawtable
Location
Georgia
rawtable2
SbjNum
1
rawtable2
Name
Sandra
rawtable2
Address
Portland
createTableSQL
CREATE TABLE rawtable ([Sbjnum])int, ([Surveyor])nvarchar(200), ([Location])nvarchar(200)
CREATE TABLE rawtable2 ([Sbjnum])int, ([Name])nvarchar(200), ([Address])nvarchar(200)
insertTableSQL
INSERT INTO rawtable SELECT [Sbjnum], [Surveyor], [Location] from #raw
INSERT INTO rawtable2 SELECT [Sbjnum], [Name], [Address] from #raw
You can create the create query by using stuff. Since you would have multiple tables so you have to group by the query to return multiple table names and it's columns separately. So the result query you can set into a variable and can execute by using
EXEC sp_executesql
So you can easily create as much as tables based on your table data.
The following query will help you to create multiple tables. This you can optimize if needed, but this will give you the insight to do the same for inserting values to the tables respectively.
Declare #sql nvarchar(MAX) = ( SELECT ' CREATE TABLE '+ desttablename +' (' + STUFF((SELECT ', ' + sn.[destfieldname] + ' ' +datatype
FROM dbo.specNormalization As sn
ORDER BY sn.destfieldname
FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 2, '') + ' )'
FROM dbo.specNormalization as P2
GROUP BY P2.desttablename FOR XML PATH('') )
-- Uncomment to see the created query
-- SELECT #sql
-- To execute the created query. This will create the tables with column and datatype
EXEC sp_executesql #sql
Hope this helps. Happy coding :)
Too long for a comment. Try to generate SELECT .. INTO .. FROM to create and populate a target table. Kind of
SELECT cast([Sbjnum] as int) [Sbjnum], cast([Surveyor] as nvarchar(200)) [Surveyor], cast([Location] as nvarchar(200)) [Location]
INTO rawtable
FROM #raw
If the source types are the same as target, skip cast functions. Can't tell what is the query to generate it as no sample data are provided.

How select all columns count of all tables by date

Today I have this query that returns all rows from all tables. But now I want to add a new column that would be the number of records in the last year. Most tables have a column called "DateInsert".
I have this:
SELECT
SCHEMA_NAME(t.[schema_id]) AS [SCHEMA],
OBJECT_NAME(p.[object_id]) AS [NOME_TABELA],
SUM(p.[rows]) AS [ROW_COUNT]
FROM [sys].[partitions] p
INNER JOIN [sys].[tables](NOLOCK) t ON p.[object_id] = t.[object_id]
WHERE p.[index_id] < 2
GROUP BY p.[object_id]
,t.[schema_id]
ORDER BY [ROW_COUNT] desc
How do i add a new column counting rows from the last year only?
Assuming, each table has DateInsert column. You can try using Dynamic SQL to prepare the count for each table from last year in one temporary table and then you can Join that temp table with your final query.
Something like this
Please note that, if any of your table dont have DateInsert Column, this query will fail. In order to look at list of tables, you can prepare the dynamic SQL for those tables only
DECLARE #SQLQuery NVARCHAR(MAX)
-- Preparing Dynamic SQL
SELECT #SQLQuery = STUFF(
(SELECT CONCAT('UNION SELECT ''', name ,''' AS [TableName], COUNT(1) AS [NoOfRowsFromLastYear] FROM ['
, name
, '] WITH (NOLOCK) WHERE YEAR(DateInsert) = (YEAR(GETDATE())-1)')
FROM [sys].[tables]
--WHERE name in ('TableName')
FOR XML PATH(''))
, 1, LEN('UNION '), ''
)
SELECT #SQLQuery = CONCAT('SELECT * INTO ##TableDetail FROM (', #SQLQuery, ') DataSet')
-- Check for table, if available, drop it
IF OBJECT_ID('tempdb..##TableDetail') IS NOT NULL DROP TABLE ##TableDetail;
-- Execute prepared query
EXECUTE sp_executesql #SQLQuery
-- Final Query with [ROW_COUNT_FROM_LAST_YEAR]
SELECT
SCHEMA_NAME(t.[schema_id]) AS [SCHEMA],
OBJECT_NAME(p.[object_id]) AS [NOME_TABELA],
SUM(p.[rows]) AS [ROW_COUNT],
td.NoOfRowsFromLastYear AS [ROW_COUNT_FROM_LAST_YEAR]
FROM [sys].[partitions] p
INNER JOIN [sys].[tables](NOLOCK) t ON p.[object_id] = t.[object_id]
INNER JOIN ##TableDetail td ON td.[TableName] = t.name
WHERE p.[index_id] < 2
GROUP BY p.[object_id]
,t.[schema_id]
,td.NoOfRowsFromLastYear
ORDER BY [ROW_COUNT] desc

Update columns in multiple tables by names pulled from a temporary table

I have a temp table where various table names and connected column names are stored. If I were to run a simple SELECT on it the results would look something like this:
----------------
TableName | ColumnName
------------------
Users | RoleId
Tables | OwnerId
Chairs | MakerId
etc...
I'm looking for a way to set mentioned column values in the connected tables to NULL.
I know how to do it via a CURSOR or a WHILE loop by processing each row individually but I'm trying to eliminate these performance hoarders from my stored procedures.
Is there any way to build a join by table names from the TableName column to the actual tables to then set connected ColumnName column values to NULL ?
Check this Script-
IF OBJECT_ID('SampleTable') IS NOT NULL
DROP TABLE SampleTable
CREATE TABLE SampleTable
(
Table_Name VARCHAR(50) NOT NULL,
Column_Name VARCHAR(50) NOT NULL
)
GO
INSERT INTO SampleTable
VALUES
('Users','RoleId'),('Tables','OwnerId'),('Chairs','MakerId') --Give your Combo here
GO
--Check this scripts
SELECT 'UPDATE ' + QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(S1.TABLE_NAME) +
' SET ' + QUOTENAME(S1.COLUMN_NAME) + ' = NULL ; '
AS [Dynamic_Scripts]
FROM SampleTable S JOIN INFORMATION_SCHEMA.COLUMNS S1 ON s.Table_Name=s1.Table_Name and s.Column_Name=s1.Column_Name
--Check this scripts (multiple column single script; 1 table 'n' column - 1 update query)
SELECT 'UPDATE ' + CONCAT('[',TABLE_SCHEMA,'].[',S1.TABLE_NAME,'] SET ') + STRING_AGG(CONCAT('[',S1.COLUMN_NAME,']=NULL'),',') + ' ; ' AS [Dynamic_Scripts]
FROM SampleTable S JOIN INFORMATION_SCHEMA.COLUMNS S1 ON s.Table_Name=s1.Table_Name and s.Column_Name=s1.Column_Name
GROUP BY CONCAT('[',TABLE_SCHEMA,'].[',S1.TABLE_NAME,'] SET ')
Try this,
IF OBJECT_ID('SampleTable') IS NOT NULL
DROP TABLE SampleTable
CREATE TABLE SampleTable
(
Table_Name VARCHAR(50) NOT NULL,
Column_Name VARCHAR(50) NOT NULL
)
GO
INSERT INTO SampleTable
VALUES
('Users','RoleId'),('Tables','OwnerId'),('Chairs','MakerId')
,('Users','Appid'),('Tables','Column') --Give your Combo here
GO
declare #Sql nvarchar(1000)=''
;with CTE as
(
select QUOTENAME(a.Table_Name)Table_Name
,stuff((select ','+QUOTENAME(Column_Name),'=null'
from SampleTable B
where a.Table_Name=b.Table_Name for xml path('') ),1,1,'')UpdateCol
from SampleTable A
group by a.Table_Name
)
select #Sql=coalesce(#Sql+char(13)+char(10)+SingleUpdate,SingleUpdate)
from
(
select CONCAT('Update ',Table_Name,' ','SET ',UpdateCol)SingleUpdate
from cte
)t4
print #Sql
select #Sql
Execute sp_executeSql #Sql

How do I delete Columns from a table if not another table using SQL?

Let's say
Table1 has columns: Column1 Column2 Column3
Table2 has columns: Column2 Column3 Column4
I want Column1 to be deleted because it's not in Table2.
I am guessing I need to a JOIN and then delete from that. I did some searching and found this article:
How can I get column names from a table in SQL Server?
I tried:
SELECT T.TABLE_NAME AS 'TABLE NAME',
C.COLUMN_NAME AS 'COLUMN NAME'
FROM INFORMATION_SCHEMA.TABLES T
INNER JOIN INFORMATION_SCHEMA.COLUMNS C ON
T.TABLE_NAME=C.TABLE_NAME
WHERE T.TABLE_TYPE='BASE TABLE'
AND T.TABLE_NAME LIKE 'T'
but I can only get the Column names to show for one Table. I tried modifying it with no luck, and of course I need to delete as well. Even if I could get a list of columns that don't match would help. I am no SQL expert but that's as far as I got. Any help would be appreciated. Thanks!
I've made a simple query that checks what column names both tables are containing and then counts the number of occurences of each name. It then shows the columns that appear less than two times i.e. the ones that only appears in one of the two tables.
select name from (
select [object_id], name from sys.all_columns where [object_id] = (select [object_id] from sys.tables where name = 'Table1')
UNION ALL
select [object_id], name from sys.all_columns where [object_id] = (select [object_id] from sys.tables where name = 'Table2')
) o
group by o.name
having count([object_id]) < 2
You can use the data from this table to make a separate "drop column" query.
You need a dynamic query in this case because you build your drop statement while you are running the select statement to get the column name.
declare #column varchar(max)
set #column = (select............)
-- Print #column -- Use this to check if the column name is what you want
declare #sql nvarchar(max)
set #sql = 'alter table Table1 drop column ' + #column
execute (#sql)
Let me know if you have any questions.

How to use the dynamic column name from the table in a where clause

I am trying to get the dynamic column names from the table using the 'INFORMATION_SCHEMA.COLUMNS' Following is the query.
Select COLUMN_NAME into #TempTable
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'MyTable'
Result:
COLUMN_NAME
Person_ID
Person_Name
Person_Address
Wanting to Do:
Select * from MyTable where Person_ID = 1
What can be the ways to use the Person_ID from 1st query to the second query?
You can use dynamic SQL to execute this via the EXEC command.
Build a VARCHAR string for your query based on the dynamic column names you are getting from your first query, then EXEC on the string you have created.
You have not provided enough information on exactly what columns you need in your WHERE clause, or how you determine which ones, but dynamic SQL seems to be what you need here.
if you are trying to do something like this
select * from [table] where [col] =#param
then you can use query like below
declare #query nvarchar(max)
select
#query='select * from '+t.name +
' where '+c.name + ' ='+
case
when c.name ='Person_ID' then '1'
when c.name ='Someother_ID' then '10'
else c.name
end
from sys.tables t join sys.columns c
on c.object_id=t.object_id
and t.name ='MyTable'
exec( #query)