Compare tables on Linked Servers SQL dynamically - sql-server

I have to compare table (SIB$) to get unmatched records on two different LINKED SERVERS (LATESTDUMP, OLDDUMP) that are identical. I have already tried to create a dynamic query. Can some one please help me with following:
1) Is there a way where I dont have to pass the column names to the code and the code dynamically gets the column names and use it as the column list to compare.
So all I have to do is pass to the stored proc the two table names
Code i have worked on:
DECLARE #sql nvarchar(max) = ' ((SELECT * FROM LATESTDUMP...SIB$) t1 FULL
OUTER JOIN (SELECT * FROM OLDDUMP...SIB$) t2
ON t1.id = t2.id
WHERE
t1.id IS NULL OR
t2.id IS NULL)'
SELECT #sql += ' or t1.' + quotename(column_name) + ' <> t2.' +
quotename(column_name) from information_schema.columns where table_name =
'SIB$'

Looks like you might want to look at the MERGE statement that's been available since SQL 2008.
https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql

Here's some code to get you started. It pulls out a primary key (assumes one column primary keys, which may or may not be a valid assumption for you), and grabs a comma-delimited string list of the remaining columns.
From here you can use split-string to build a sql string that joins the two same-named tables from the hard-coded linked servers on the primary key, comparing each of the columns for difference, and then execute the dynamic SQL. I've included some test scaffolding so you can work it through:
DECLARE #tableName sysname;
SET #tableName = 'some table'
-- Validate parameter
IF #tableName IS NULL OR NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #tableName AND TABLE_TYPE = 'BASE TABLE')
BEGIN
RAISERROR ('Invalid table name specified', 16, 1);
RETURN;
END;
-- Validate table has a primary key
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND TABLE_NAME = #tableName)
BEGIN
RAISERROR ('Specified table does not have a primary key', 16, 1);
RETURN;
END;
-- Get info about the Primary Key columns
DECLARE #pkcolName sysname;
SELECT #pkcolName = c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.COLUMNS c ON kcu.TABLE_NAME = c.TABLE_NAME AND kcu.COLUMN_NAME = c.COLUMN_NAME
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY' AND tc.TABLE_NAME = #tableName AND kcu.ORDINAL_POSITION = 1
-- Grab the names of all the remaining columns
DECLARE #nonKeyColumns nvarchar(MAX);
SELECT #nonKeyColumns = STUFF ( ( SELECT N'], [' + c.name
FROM sys.columns c
WHERE object_id = (select top 1 object_id FROM sys.objects where name = #tableName)
AND c.name <> #pkcolName
ORDER BY c.column_id
FOR XML PATH('')), 1, 2, '') + ']';
SELECT #pkcolName
SELECT #nonKeyColumns

Related

Is it possible to select alle columns from table, except one (eg. ID)?

I am using a third party application which has an absurd number of columns per table. When I select data, often I need all columns except the ID. Or all columns except ID and DateCreated.
Using the sys.columns it's possible to find out which columns are available in a table. How can I use this information to create statements? What would be the best way to do this?
This script will select all columns for any table except the primary key column of the table and the column names DateCreated.
SELECT
'SELECT '+
SUBSTRING(LIST,1,LEN(LIST)-1)
+' FROM [Person].[Address]'
FROM
(
SELECT
'['+COL.COLUMN_NAME+'],'
FROM INFORMATION_SCHEMA.COLUMNS COL
LEFT JOIN
(
SELECT
CON.CONSTRAINT_TYPE,
USG.TABLE_SCHEMA,
USG.TABLE_NAME,
USG.COLUMN_NAME,
CON.CONSTRAINT_NAME,
USG.TABLE_CATALOG
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE USG
INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS CON
ON USG.CONSTRAINT_NAME = CON.CONSTRAINT_NAME
)Q
ON COL.TABLE_SCHEMA = Q.TABLE_SCHEMA
AND COL.TABLE_NAME = Q.TABLE_NAME
AND COL.TABLE_CATALOG = Q.TABLE_CATALOG
AND COL.COLUMN_NAME = Q.COLUMN_NAME
WHERE COL.TABLE_SCHEMA ='Person'
AND COL.TABLE_NAME = 'Address'
AND
(
Q.CONSTRAINT_TYPE <> 'PRIMARY KEY'
OR
COL.COLUMN_NAME <> 'DateCreated'
)
FOR XML PATH(''))L(LIST)
Replace the string Person with your schema and Address With you table name
try using dynamic Query:
DECLARE #Names VARCHAR(MAX)
SELECT #Names = COALESCE(#Names + ', ', '') + Column_Name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE Table_name='Table1'
And Column_Name!='ID'
DECLARE #Query VARCHAR(MAX)
SELECT #Query = 'SELECT '+ #Names + ' INTO Table2 FROM Table1'
exec (#Query)
SELECT * from Table2

What is the T-SQL syntax to exclude a duplicate column in the output when joining 2 tables?

I am using SQL Server 2014 and I have the following T-SQL query which joins 2 tables:
SELECT a.*, b.* FROM TEMP a
INNER JOIN Extras b ON b.ResaID = a.ResaID
I would like to pull ALL the columns from TEMP and all the columns from "Extras" with the exception of the ResaID column as it is already included in a.* in the above query. Basically, I want to pull a.* + b.* (excluding b.ResaID).
I know I can write the query in the form:
Select a.*, b.column2, b.column3,...
but since b.* has got around 40 columns, is there a way to write the query in a more simplified way to exclude b.ResaID, rather than specify each of the columns in the "Extras" table?
Unfortunately, there is no such syntax. You could either use asterisks (*) and just ignore the duplicated column in your code, or explicitly list the columns you need.
You should create a view and select the columns you need from that view. Here is a script that will generate that view for you:
DECLARE #table1 nvarchar(20) = 'temp'
DECLARE #table1key nvarchar(20) = 'ResaID'
DECLARE #table2 nvarchar(20) = 'Extras'
DECLARE #table2key nvarchar(20) = 'ResaID'
DECLARE #viewname varchar(20) = 'v_myview'
DECLARE #sql varchar(max) = ''
SELECT #sql += '], a.[' + column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table1
SELECT #sql += '], b.[' + column_name
FROM
(
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
EXCEPT
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table1
) x
SELECT
#sql = 'CREATE view ' +#viewname+ ' as SELECT '
+ STUFF(#sql, 1, 3, '') + '] FROM ['
+#table1+ '] a JOIN ['+ #table2
+'] b ON ' + 'a.' + #table1key + '=b.' + #table2key
EXEC(#sql)
You can simply solve this using a dynamic sql query.
DECLARE #V_SQL AS NVARCHAR(2000)='' --variable to store dynamic query
,#V_TAB1 AS NVARCHAR(200)='TEMP' --First Table
,#V_TAB2 AS NVARCHAR(200)='Extras' --Second Table
,#V_CONDITION AS NVARCHAR(2000)='A.ResaID = B.ResaID' --Conditions
SELECT #V_SQL = STUFF(
( SELECT ', '+TCOL_NAME
FROM
( SELECT 'A.'+S.NAME AS TCOL_NAME
FROM SYSCOLUMNS AS S
WHERE OBJECT_NAME(ID) = #V_TAB1
UNION ALL
SELECT 'B.'+S.NAME
FROM SYSCOLUMNS AS S
WHERE OBJECT_NAME(ID) = #V_TAB2
AND S.NAME NOT IN (SELECT S.NAME
FROM SYSCOLUMNS AS S
WHERE OBJECT_NAME(ID) = #V_TAB1)
) D
FOR XML PATH('')
),1,2,'')
EXECUTE ('SELECT '+#V_SQL+'
FROM '+#V_TAB1+' AS A
INNER JOIN '+#V_TAB2+' AS B ON '+#V_CONDITION+' ')

MS SQL find multiple columns in a table

Normally, I use this code to find a column in my table:
Use MyDatabase
Go
IF EXISTS(SELECT * FROM sys.columns
WHERE Name in ( N'String1')
AND Object_ID = Object_ID(N'dbo.Table1'))
BEGIN
Print 'Column String1 exists in Table1'
END
ELSE
BEGIN
Print 'Column String1 does not exist in Table1'
END;
We're updating the table and adding 56 new columns. How do I go about that without duplicating the code 56 times? It's just a quick error check to do after importing the new columns. Another thing I'd like to do is just print out the result only if the column isn't found.
Thanks!
Matt
You can declare a table containing all the 56 column names you want to check and then left join it against the management view:
DECLARE #cols TABLE
(
column_name varchar(500)
)
INSERT INTO #cols
VALUES ('col1'),('col2'),('col3')
IF EXISTS (SELECT *
FROM #cols c
LEFT JOIN INFORMATION_SCHEMA.COLUMNS col ON col.COLUMN_NAME = c.column_name
AND col.TABLE_NAME = 'Table1'
WHERE col.TABLE_NAME IS NULL)
BEGIN
PRINT 'Some columns are missing'
SELECT c.column_name
FROM #cols c
LEFT JOIN INFORMATION_SCHEMA.COLUMNS col ON col.COLUMN_NAME = c.column_name
AND col.TABLE_NAME = 'Table1'
WHERE col.TABLE_NAME IS NULL
END
INFORMATION_SCHEMA.COLUMNS is functionally the same as sys.columns. I prefer it for being cleaner and have more info than sys.columns.

Find Identity columns from another database

Normally with SQL Server you can use the COLUMNPROPERTY function like this to find the Identity columns in a database:
select TABLE_NAME + '.' + COLUMN_NAME, TABLE_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA = 'dbo'
and COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1
order by TABLE_NAME
But I can't figure out how to get this to work when running the query from another database. E.g. this does not return any results:
Use FirstDatabase
Go
select TABLE_NAME + '.' + COLUMN_NAME, TABLE_NAME
from SecondDatabase.INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA = 'dbo'
and COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1
order by TABLE_NAME
Object_ID only works in current db, unless you use a 3-part name, but that form is complicated to use. Also, ColumnProperty only works in current db.
select o.name + '.' + c.name, o.name
from test1.sys.columns c
join test1.sys.objects o on c.object_id = o.object_id
join test1.sys.schemas s on s.schema_id = o.schema_id
where s.name = 'dbo'
and o.is_ms_shipped = 0 and o.type = 'U'
and c.is_identity = 1
order by o.name
There is no way to get information with the help of COLUMNPROPERTY from another database. But there is on workaround:
DECLARE #DatabaseName VARCHAR(MAX)
DECLARE #TableName VARCHAR(MAX)
DECLARE #SQL VARCHAR(MAX)
SET #DatabaseName = 'MyDatabase'
SET #TableName = 'MyTable'
SET #SQL = '
SELECT
C.TABLE_NAME,
C.COLUMN_NAME,
S.IS_IDENTITY
FROM ' + #DatabaseName + '.INFORMATION_SCHEMA.COLUMNS AS C
LEFT JOIN ' + #DatabaseName + '.SYS.COLUMNS AS S ON OBJECT_ID(''' + #DatabaseName + '.dbo.' + #TableName + ''') = S.OBJECT_ID AND C.COLUMN_NAME = S.NAME
WHERE S.IS_IDENTITY = 1'
EXEC(#SQL)
This worked for me using a specific database:
USE <database_name>;
GO
SELECT SCHEMA_NAME(schema_id) AS schema_name
, t.name AS table_name
, c.name AS column_name
FROM sys.tables AS t
JOIN sys.identity_columns c ON t.object_id = c.object_id
ORDER BY schema_name, table_name;
GO
In this example, I've constructed a stored procedure in "Database1" that uses dynamic SQL to retrieve column information from a table in "Database2" (using the [INFORMATION_SCHEMA].[COLUMNS] system view residing in "Database2"):
ALTER PROCEDURE [Database1].[Schema1].[ColumnNames] #Database2 nvarchar(128), #Schema2 nvarchar(128), #Table2 nvarchar(128)
AS
BEGIN
DECLARE #Sql nvarchar(1000)
SET #Sql = 'SELECT [COLUMN_NAME], [ORDINAL_POSITION] FROM [' + #Database2 + '].[INFORMATION_SCHEMA].[COLUMNS]
WHERE [TABLE_SCHEMA] = ''' + #Schema2 + ''' AND [TABLE_NAME] = ''' + #Table2 + ''''
EXEC(#Sql)
END
I'm using SQL Server 2019, and I have run into the same challenge. I'm not sure if this fix will work for older versions, but there is a view in each DB called Your-DB-Name.sys.identity_columns. If you select from this view, you'll see the list of identity columns you have defined in that DB.
From that information you should be able to write a join connecting YourDBName.Information_schema.columns such as below:
SELECT *
FROM YourDBName.Information_Schema.columns col
LEFT OUTER JOIN YourDBName.sys.identity_columns idc
ON idc.name = col.COLUMN_NAME AND idc.object_id = object_id('YourDBName..YourTableName')
WHERE col.TABLE_NAME = 'YourTableName' AND col.table_catalog = 'YourDBName';
The YourDbName.sys.identity_columns view contains the following fields that might be useful:
object_id (used to join back to the table in question in case you have multiple tables with the same identity field name)
name (the name of the Identity field)
column_id (the order of the column in your table)
is_identity (tells you if this is an identity field)
seed_value (the initial value of the identity field)
increment_value (how much the identity field goes up with each insert)

Is it possible to Add column to multiple table simultaneously?

I am using SQL Server. I want to add a single column named [DateCreated] to multiple tables. Is it possible that with a single statement I could add this column to all the tables in my database?
I stumble upon an answer by Joe Steffaneli in which he suggested a query which in turn returns rows consisting Alter table statements.
Query is as follows :
select 'alter table ' + quotename(s.name) + '.' + quotename(t.name) + ' add [DateModified] datetime'
from sys.columns c
inner join sys.tables t
on c.object_id = t.object_id
inner join sys.schemas s
on t.schema_id = s.schema_id
left join sys.columns c2
on t.object_id = c2.object_id
and c2.name = 'DateModified'
where c.name = 'DateCreated'
and t.type = 'U'
and c2.column_id is null /* DateModified column does not already exist */
Is there any way that I can execute returned rows? Sorry for English.
You probably need something like this. Check that the script does what you want before running it (adds a non null column with a default value of getdate())!
DECLARE #Dynsql nvarchar(max)
SET #Dynsql = ''
SELECT #Dynsql = #Dynsql + '
alter table ' + QUOTENAME(SCHEMA_NAME(schema_id))+ '.' + QUOTENAME(name) +
' add [DateCreated] datetime not null default getdate()'
FROM sys.tables
WHERE type='U' and object_id NOT IN (select object_id from sys.columns where name='DateCreated')
EXEC (#Dynsql)
You can use this query
I use the where clause in this query (it is optional) to find all tables that have ID and add the new field to them.
you can change where clause for example find all tables that have BusinessId column and add new filed or remove where clause and add for all tables
DECLARE #SQL varchar(max)
SELECT #SQL= STUFF((SELECT ';' + 'ALTER TABLE ' + t.TABLE_SCHEMA+'.' +t.TABLE_NAME+ ' ADD newfield nvarchar(max)'
from information_schema.tables t
inner join information_schema.columns c on c.table_name = t.table_name
and c.table_schema = t.table_schema
where c.column_name = 'id'
and t.table_schema not in ('information_schema', 'pg_catalog')
and t.table_type = 'BASE TABLE'
FOR XML PATH('')),1,1,'')
EXEC (#SQL)
This stored procedure works fine
USE databaseName;
exec sp_msforeachtable 'alter table ? Add [DateCreated] datetime not null default getdate()';
declare #i int
set #i=1
while(#i<45)
begin
declare #sql varchar(200)
with TempTable as (select Row_number() over(order by stdID) as RowNo,* from SomeTable)
select #sql= 'alter table Table'+(select Name from TempTable where RowNo=#i)+' add NewColumn int'
exec (#sql)
set #i=#i+1
end
No, there is no single statement that will add a column to all the tables in your database.
Next time please tag your question with the RDBMS you're using. If there were a way, we wouldn't be able to give you the command without knowing which database system you are using.

Resources