In DB I have #temp tables with data and I need to generate insert Scripts (for all data).
How it can be done ? I right clicked on tempDB and selected > Tasks > Generate Scripts but I can't select #temp tables to generate script (they are not avaialble to select).
how I can geneate Insert script from #temp tables I m using SQL Server 2008 R2.
You can insert your query results into sql table (temporary table , it will be created automatically ):
SELECT * INTO myTempTable FROM (query results)
e.g : SELECT * INTO myTempTable FROM user where condition
A table named myTempTable will be created inside schema dbo
Then click on database click :
Tasks > Generate Scripts
and you choose the table myTempTable
Another best way to do it just bit faster but longer.
Use SqlPubWiz
Just go:
C:\Program Files (x86)\Microsoft SQL Server\90\Tools\Publishing\1.4
and run and fill required information regarding log in into database and choose your database and get your whole database script and find the table's insert script that you wanted in your saved script file.
You can use the following query batch for generating scripts for temp tables and you can select the rows based on the conditions. I have got this from here. In the original source, the Author created Stored Procedure for generating scripts. I have formatted and modified for declaring TableName with Condition. You need to run this query batch in tempdb with your #temp table name. Thanks to Neeraj Prasad Sharma.
DECLARE #QUERY VARCHAR(MAX) = 'Dbo.#Temp where 1 = 1'
SET NOCOUNT ON
DECLARE #WithStrINdex AS INT
DECLARE #WhereStrINdex AS INT
DECLARE #INDExtouse AS INT
DECLARE #SchemaAndTAble VARCHAR(270)
DECLARE #Schema_name VARCHAR(30)
DECLARE #Table_name VARCHAR(240)
DECLARE #Condition VARCHAR(MAX)
SELECT #WithStrINdex = 0
SELECT #WithStrINdex = CHARINDEX('WITH', #Query), #WhereStrINdex = CHARINDEX('WHERE', #Query)
IF(#WithStrINdex != 0)
SELECT #INDExtouse = #WithStrINdex
ELSE
SELECT #INDExtouse = #WhereStrINdex
SELECT #SchemaAndTAble = LEFT(#Query, #INDExtouse - 1)
SELECT #SchemaAndTAble = LTRIM(RTRIM(#SchemaAndTAble))
SELECT #Schema_name = LEFT(#SchemaAndTAble, CHARINDEX('.', #SchemaAndTAble ) - 1)
,#Table_name = SUBSTRING(#SchemaAndTAble, CHARINDEX('.', #SchemaAndTAble ) + 1, LEN(#SchemaAndTAble))
,#CONDITION = SUBSTRING(#Query, #WhereStrINdex + 6, LEN(#Query))--27+6
DECLARE #COLUMNS TABLE([Row_number] SMALLINT, Column_Name VARCHAR(MAX))
DECLARE #CONDITIONS AS VARCHAR(MAX)
DECLARE #Total_Rows AS SMALLINT
DECLARE #Counter AS SMALLINT
DECLARE #ComaCol AS VARCHAR(MAX)
SELECT #ComaCol = '', #Counter = 1, #CONDITIONS = ''
print #Schema_name
print #Table_name
INSERT INTO #COLUMNS
SELECT ROW_NUMBER() OVER(ORDER BY ORDINAL_POSITION) [Count] ,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = #Schema_name
AND TABLE_NAME = #Table_name
AND COLUMN_NAME NOT IN ('SYNCDESTINATION','PENDINGSYNCDESTINATION' ,'SKUID','SALECREDITEDTO')
SELECT #Total_Rows = COUNT(*) FROM #COLUMNS
SELECT #Table_name = '['+#Table_name+']'
SELECT #Schema_name = '['+#Schema_name+']'
WHILE (#Counter< = #Total_Rows )
BEGIN
SELECT #ComaCol = #ComaCol + ' ['+Column_Name+'],' FROM #COLUMNS
Where [Row_number] = #Counter
SELECT #CONDITIONS = #CONDITIONS+ ' + CASE WHEN ['+Column_Name+'] IS NULL THEN ''NULL'' ELSE '''''''' +
REPLACE( CONVERT(VARCHAR(MAX),['+Column_Name+']) ,'''''''','''')
+'''''''' END +'+''','''
FROM #COLUMNS WHERE [Row_number] = #Counter
SET #Counter = #Counter + 1
END
SELECT #CONDITIONS = RIGHT(#CONDITIONS, LEN(#CONDITIONS) -2)
SELECT #CONDITIONS = LEFT(#CONDITIONS, LEN(#CONDITIONS) -4)
SELECT #ComaCol = SUBSTRING (#ComaCol, 0, LEN(#ComaCol))
SELECT #CONDITIONS = '''INSERT INTO ' + #Schema_name + '.' + #Table_name + '(' + #ComaCol + ')' +' VALUES( '+'''' + '+' + #CONDITIONS
SELECT #CONDITIONS = #CONDITIONS + '+' + ''')'''
SELECT #CONDITIONS = 'SELECT' + #CONDITIONS + 'FROM' + #Schema_name + '.' + #Table_name + ' WITH(NOLOCK) ' + ' WHERE ' + #Condition
PRINT(#CONDITIONS)
EXEC(#CONDITIONS)
You have to right click on database and click on
Tasks -> Generate Scripts
now you are having a popup screen go to next click
Select specific database objects
now select your table from Tables option and click next now you have Advanced button there click on it.
You will have another small popup screen available choose
Types of data to script and select
Data only
click OK and don't forget to see the path as file name where your script save carefully.
Now click Next and again Next your script is ready with the data.
Related
I'm trying to build a stored procedure that will query multiple database depending on the databases required.
For example:
SP_Users takes a list of #DATABASES as parameters.
For each database it needs to run the same query and union the results together.
I believe a CTE could be my best bet so I have something like this at the moment.
SET #DATABASES = 'DB_1, DB_2' -- Two databases in a string listed
-- I have a split string function that will extract each database
SET #CURRENT_DB = 'DB_1'
WITH UsersCTE (Name, Email)
AS (SELECT Name, Email
FROM [#CURRENT_DB].[dbo].Users),
SELECT #DATABASE as DB, Name, Email
FROM UsersCTE
What I don't want to do is hard code the databases in the query. The steps I image are:
Split the parameter #DATABASES to extract and set the #CURRENT_DB Variable
Iterate through the query with a Recursive CTE until all the #DATABASES have been processed
Union all results together and return the data.
Not sure if this is the right approach to tackling this problem.
Using #databases:
As mentioned in the comments to your question, variables cant be used to dynamically select a database. Dynamic sql is indicated. You can start by building your template sql statement:
declare #sql nvarchar(max) =
'union all ' +
'select ''#db'' as db, name, email ' +
'from [#db].dbo.users ';
Since you have sql server 2016, you can split using the string_split function, with your #databases variable as input. This will result in a table with 'value' as the column name, which holds the database names.
Use the replace function to replace #db in the template with value. This will result in one sql statement for each database you passed into #databases. Then, concatenate the statements back together. Unfortunately, in version 2016, there's no built in function to do that. So we have to use the famous for xml trick to join the statements, then we use .value to convert it to a string, and finally we use stuff to get rid of the leading union all statement.
Take the results of the concatenated output, and overwrite the #sql variable. It is ready to go at this point, so execute it.
I do all that is described in this code:
declare #databases nvarchar(max) = 'db_1,db_2';
set #sql = stuff(
(
select replace(#sql, '#db', value)
from string_split(#databases, ',')
for xml path(''), type
).value('.[1]', 'nvarchar(max)')
, 1, 9, '');
exec(#sql);
Untested, of course, but if you print instead of execute, it seems to give the proper sql statement for your needs.
Using msForEachDB:
Now, if you didn't want to have to know which databases had 'users', such as if you're in an environment where you have a different database for every client, you can use sp_msForEachDb and check the structure first to make sure it has a 'users' table with 'name' and 'email' columns. If so, execute the appropriate statement. If not, execute a dummy statement. I won't describe this one, I'll just give the code:
declare #aggregator table (
db sysname,
name int,
email nvarchar(255)
);
insert #aggregator
exec sp_msforeachdb '
declare #sql nvarchar(max) = ''select db = '''''''', name = '''''''', email = '''''''' where 1 = 2'';
select #sql = ''select db = ''''?'''', name, email from ['' + table_catalog + ''].dbo.users''
from [?].information_schema.columns
where table_schema = ''dbo''
and table_name = ''users''
and column_name in (''name'', ''email'')
group by table_catalog
having count(*) = 2
exec (#sql);
';
select *
from #aggregator
I took the valid advice from others here and went with this which works great for what I need:
I decided to use a loop to build the query up. Hope this helps someone else looking to do something similar.
CREATE PROCEDURE [dbo].[SP_Users](
#DATABASES VARCHAR(MAX) = NULL,
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)
)
BEGIN
SET NOCOUNT ON;
--Local variables
DECLARE
#COUNTER INT = 0,
#SQL NVARCHAR(MAX) = '',
#CURRENTDB VARCHAR(50) = NULL,
#MAX INT = 0,
#ERRORMSG VARCHAR(MAX)
--Check we have databases entered
IF #DATABASES IS NULL
BEGIN
RAISERROR('ERROR: No Databases Provided,
Please Provide a list of databases to execute procedure. See stored procedure:
[SP_Users]', 16, 1)
RETURN
END
-- SET Number of iterations based on number of returned databases
SET #MAX = (SELECT COUNT(*) FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i)X)
-- Build SQL Statement
WHILE #COUNTER < #MAX
BEGIN
--Set the current database
SET #CURRENTDB = (SELECT X.Value FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i
ORDER BY RowNumber OFFSET #COUNTER
ROWS FETCH NEXT 1 ROWS ONLY) X);
SET #SQL = #SQL + N'
(
SELECT Name, Email
FROM [' + #CURRENTDB + '].[dbo].Users
WHERE
(Name = #PARAM1 OR #PARAM1 IS NULL)
(Email = #PARAM2 OR #PARAM2 IS NULL)
) '
+ N' UNION ALL '
END
PRINT #CURRENTDB
PRINT #SQL
SET #COUNTER = #COUNTER + 1
END
-- remove last N' UNION ALL '
IF LEN(#SQL) > 11
SET #SQL = LEFT(#SQL, LEN(#SQL) - 11)
EXEC sp_executesql #SQL, N'#CURRENTDB VARCHAR(50),
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)',
#CURRENTDB,
#PARAM1 ,
#PARAM2
END
Split Variable Function
CREATE FUNCTION [dbo].[udf_SplitVariable]
(
#List varchar(8000),
#SplitOn varchar(5) = ','
)
RETURNS #RtnValue TABLE
(
Id INT IDENTITY(1,1),
Value VARCHAR(8000)
)
AS
BEGIN
--Account for ticks
SET #List = (REPLACE(#List, '''', ''))
--Account for 'emptynull'
IF LTRIM(RTRIM(#List)) = 'emptynull'
BEGIN
SET #List = ''
END
--Loop through all of the items in the string and add records for each item
WHILE (CHARINDEX(#SplitOn,#List)>0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT Value = LTRIM(RTRIM(SUBSTRING(#List, 1, CHARINDEX(#SplitOn, #List)-1)))
SET #List = SUBSTRING(#List, CHARINDEX(#SplitOn,#List) + LEN(#SplitOn), LEN(#List))
END
INSERT INTO #RtnValue (Value)
SELECT Value = LTRIM(RTRIM(#List))
RETURN
END
I have (for testing purposes) many dbs with the same schema (=same tables and columns basically) on a sql server 2008 r2 instance.
i would like a query like
SELECT COUNT(*) FROM CUSTOMERS
on all DBs on the instance. I would like to have as result 2 columns:
1 - the DB Name
2 - the value of COUNT(*)
Example:
DBName // COUNT (*)
TestDB1 // 4
MyDB // 5
etc...
Note: i assume that CUSTOMERS table exists in all dbs (except master).
Try this one -
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
[COUNT] INT
, DB VARCHAR(50)
)
DECLARE #TableName NVARCHAR(50)
SELECT #TableName = '[dbo].[CUSTOMERS]'
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'SELECT ' + QUOTENAME(name, '''') + ', COUNT(1) FROM ' + QUOTENAME(name) + '.' + QUOTENAME(#TableName)
FROM sys.databases
WHERE OBJECT_ID(QUOTENAME(name) + '.' + QUOTENAME(#TableName)) IS NOT NULL
FOR XML PATH(''), TYPE).value('text()[1]', 'NVARCHAR(MAX)'), 1, 1, '')
INSERT INTO #temp (DB, [COUNT])
EXEC sys.sp_executesql #SQL
SELECT *
FROM #temp t
Output (for example, in AdventureWorks) -
COUNT DB
----------- --------------------------------------------------
19972 AdventureWorks2008R2
19975 AdventureWorks2012
19472 AdventureWorks2008R2_Live
Straight forward query
EXECUTE sp_MSForEachDB
'USE ?; SELECT DB_NAME()AS DBName,
COUNT(1)AS [Count] FROM CUSTOMERS'
This query will show you what you want to see, but will also throw errors for each DB without a table called "CUSTOMERS". You will need to work out a logic to handle that.
Raj
How about something like this:
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master', 'tempdb') --might need to exclude more dbs
OPEN c_db_names
FETCH c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
EXEC('
INSERT INTO #report
SELECT
''' + #db_name + '''
,COUNT(*)
FROM ' + #db_name + '..linkfile
')
FETCH c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names
SELECT * FROM #report
declare #userdb_list table (name varchar(4000) not null);
-- fill the db list with custom subset
insert into #userdb_list
select name from sys.databases --can add where condition to filter db names
declare
#curr_userdb varchar(300),
#db_placeholder varchar(300),
#final_db_exec_query varchar(max),
#query varchar(max);
set #query = '' -- <add ur query here>
set #db_placeholder = 'use {db}';
set #curr_userdb = (select min(name) from #userdb_list);
while #curr_userdb is not null
begin
set #final_db_exec_query = replace(#db_placeholder, '{db}', #curr_userdb + ' ' + #query);
exec (#final_db_exec_query);
--print #final_db_exec_query
set #curr_userdb = (select min(name) from #userdb_list where name > #curr_userdb);
end
GO
Solution without cursor - clean and simple
Because I know that a question was just referred to here that asked a slightly different question... if you only want to execute on certain databases, those databases could be stored in some table. Here I stored in a temporary table.
CREATE TABLE #Databases (
DbName varchar(255))
INSERT INTO #Databases (DbName)
Values ('GIS_NewJersey'), ('GIS_Pennsylvania')
DECLARE #command varchar(1000)
SELECT #command = 'Use [' + DbName + '];
Update sde.SAP_Load
SET FullAddress = CONCAT_WS('','', HouseNumber, Street, City, Postal, RegionName)
Update sde.PREMISE
SET FullAddress = CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)
Update sde.PREMISE_GEOCODE
SET FullAddress = CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)'
FROM #Databases
EXEC #command
I have a master table which contains the table names and columns corresponding to that table.
I want to write a procedure which iterates through all the records of tables and gets all the data and returns it as a single result set.
You need to use Dynamic Query
DECLARE #sql VARCHAR(max)=''
SET #sql = (SELECT #sql + 'select ' + column_name + ' from '
+ table_name + ' union all '
FROM master_table
FOR xml path(''))
SELECT #sql = LEFT(#sql, Len(#sql) - 9)
EXEC (#sql)
Note : The datatype of all the columns should be same. If it is not the case then you may have to do explicit conversion to varchar
SET #sql = (SELECT #sql + 'select cast(' + column_name + ' as varchar(4000)) from '
+ table_name
+ ' union all '
FROM Master_table
FOR xml path(''))
Assuming that all tables listed in your Master table is having same columns with same order and data types. Then it will be as follows:
create table ##a
(
Value int
)
create table ##b
(
Value int
)
create table ##c
(
Value int
)
declare #all table
(
Value int
)
declare #master table
(
TableName varchar(10)
)
declare #TableName varchar(10)
insert ##a values (1), (2), (3)
insert ##b values (4), (5), (6)
insert ##c values (7), (8), (9)
insert #master values ('##a'), ('##b'),('##c')
declare looper cursor local static forward_only read_only for
select TableName from #master
open looper
fetch next from looper into #TableName
while ##fetch_status = 0
begin
insert #all exec('select Value from ' + #TableName)
fetch next from looper into #TableName
end
close looper
deallocate looper
select * from #all
drop table ##a
drop table ##b
drop table ##c
If the tables are of different structures, please visit Stored procedures and multiple result sets in T-SQL. It will squeeze the content of each table into a single XML cell. The article also explains how to read them back.
I assume that you are using many tables with different columns in your master table. You should loop your master table. Try like this,
DECLARE #sql NVARCHAR(max) = ''
DECLARE #start INT = 1
,#end INT = 0
,#tablename VARCHAR(100) = ''
DECLARE #TableList TABLE (
id INT identity(1, 1)
,tablename VARCHAR(128)
)
INSERT INTO #TableList (tablename)
SELECT DISTINCT table_name
FROM YourMasterTableName
WHERE TABLE_NAME = 'productss'
SET #end = ##ROWCOUNT
WHILE (#start <= #end)
BEGIN
SET #tablename = (
SELECT tablename
FROM #TableList
WHERE id = #start
)
SET #sql = (
SELECT ',[' + column_name + ']'
FROM YourMasterTableName M
WHERE TABLE_NAME = #tablename
FOR XML path('')
)
SET #sql = 'SELECT ' + stuff(#sql, 1, 1, '') + ' FROM ' + #tablename
EXEC sp_executesql #sql
SET #start = #start + 1
END
I have (for testing purposes) many dbs with the same schema (=same tables and columns basically) on a sql server 2008 r2 instance.
i would like a query like
SELECT COUNT(*) FROM CUSTOMERS
on all DBs on the instance. I would like to have as result 2 columns:
1 - the DB Name
2 - the value of COUNT(*)
Example:
DBName // COUNT (*)
TestDB1 // 4
MyDB // 5
etc...
Note: i assume that CUSTOMERS table exists in all dbs (except master).
Try this one -
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
[COUNT] INT
, DB VARCHAR(50)
)
DECLARE #TableName NVARCHAR(50)
SELECT #TableName = '[dbo].[CUSTOMERS]'
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'SELECT ' + QUOTENAME(name, '''') + ', COUNT(1) FROM ' + QUOTENAME(name) + '.' + QUOTENAME(#TableName)
FROM sys.databases
WHERE OBJECT_ID(QUOTENAME(name) + '.' + QUOTENAME(#TableName)) IS NOT NULL
FOR XML PATH(''), TYPE).value('text()[1]', 'NVARCHAR(MAX)'), 1, 1, '')
INSERT INTO #temp (DB, [COUNT])
EXEC sys.sp_executesql #SQL
SELECT *
FROM #temp t
Output (for example, in AdventureWorks) -
COUNT DB
----------- --------------------------------------------------
19972 AdventureWorks2008R2
19975 AdventureWorks2012
19472 AdventureWorks2008R2_Live
Straight forward query
EXECUTE sp_MSForEachDB
'USE ?; SELECT DB_NAME()AS DBName,
COUNT(1)AS [Count] FROM CUSTOMERS'
This query will show you what you want to see, but will also throw errors for each DB without a table called "CUSTOMERS". You will need to work out a logic to handle that.
Raj
How about something like this:
DECLARE c_db_names CURSOR FOR
SELECT name
FROM sys.databases
WHERE name NOT IN('master', 'tempdb') --might need to exclude more dbs
OPEN c_db_names
FETCH c_db_names INTO #db_name
WHILE ##Fetch_Status = 0
BEGIN
EXEC('
INSERT INTO #report
SELECT
''' + #db_name + '''
,COUNT(*)
FROM ' + #db_name + '..linkfile
')
FETCH c_db_names INTO #db_name
END
CLOSE c_db_names
DEALLOCATE c_db_names
SELECT * FROM #report
declare #userdb_list table (name varchar(4000) not null);
-- fill the db list with custom subset
insert into #userdb_list
select name from sys.databases --can add where condition to filter db names
declare
#curr_userdb varchar(300),
#db_placeholder varchar(300),
#final_db_exec_query varchar(max),
#query varchar(max);
set #query = '' -- <add ur query here>
set #db_placeholder = 'use {db}';
set #curr_userdb = (select min(name) from #userdb_list);
while #curr_userdb is not null
begin
set #final_db_exec_query = replace(#db_placeholder, '{db}', #curr_userdb + ' ' + #query);
exec (#final_db_exec_query);
--print #final_db_exec_query
set #curr_userdb = (select min(name) from #userdb_list where name > #curr_userdb);
end
GO
Solution without cursor - clean and simple
Because I know that a question was just referred to here that asked a slightly different question... if you only want to execute on certain databases, those databases could be stored in some table. Here I stored in a temporary table.
CREATE TABLE #Databases (
DbName varchar(255))
INSERT INTO #Databases (DbName)
Values ('GIS_NewJersey'), ('GIS_Pennsylvania')
DECLARE #command varchar(1000)
SELECT #command = 'Use [' + DbName + '];
Update sde.SAP_Load
SET FullAddress = CONCAT_WS('','', HouseNumber, Street, City, Postal, RegionName)
Update sde.PREMISE
SET FullAddress = CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)
Update sde.PREMISE_GEOCODE
SET FullAddress = CONCAT_WS('', '', HouseNumber, Street, City, Postal, RegionName)'
FROM #Databases
EXEC #command
I was looking at the MERGE command which seems cool but still it requires the columns to be specified. I'm looking for something like:
MERGE INTO target AS t
USING source AS s
WHEN MATCHED THEN
UPDATE SET
[all t.fields = s.fields]
WHEN NOT MATCHED THEN
INSERT ([all fields])
VALUES ([all s.fields])
Is it possible?
I'm lazy... this is a cheap proc I wrote that will spit out a general MERGE command for a table. It queries information_schema.columns for column names. I ripped out my source database name - so, you have to update the proc to work with your database (look for #SourceDB... I said it was cheap.) Anyway, I know others could write it much better - it served my purpose well. (It makes a couple assumptions that you could put logic in to handle - namely turning IDENTITY_INSERT OFF - even when a table doesn't have identity columns.) It updates the table in your current context. It was written against sql server 2008 to sync up some tables. Use at your own risk, of course.
CREATE PROCEDURE [dbo].[GenerateMergeSQL]
#TableName varchar(100)
AS
BEGIN
SET NOCOUNT ON
declare #sql varchar(5000),#SourceInsertColumns varchar(5000),#DestInsertColumns varchar(5000),#UpdateClause varchar(5000)
declare #ColumnName varchar(100), #identityColName varchar(100)
declare #IsIdentity int,#IsComputed int, #Data_Type varchar(50)
declare #SourceDB as varchar(200)
-- source/dest i.e. 'instance.catalog.owner.' - table names will be appended to this
-- the destination is your current db context
set #SourceDB = '[mylinkedserver].catalog.myDBOwner.'
set #sql = ''
set #SourceInsertColumns = ''
set #DestInsertColumns = ''
set #UpdateClause = ''
set #ColumnName = ''
set #isIdentity = 0
set #IsComputed = 0
set #identityColName = ''
set #Data_Type = ''
DECLARE #ColNames CURSOR
SET #ColNames = CURSOR FOR
select column_name, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IsIdentity ,
COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsComputed') as IsComputed , DATA_TYPE
from information_schema.columns where table_name = #TableName order by ordinal_position
OPEN #ColNames
FETCH NEXT FROM #ColNames INTO #ColumnName, #isIdentity, #IsComputed, #DATA_TYPE
WHILE ##FETCH_STATUS = 0
BEGIN
if #IsComputed = 0 and #DATA_TYPE <> 'timestamp'
BEGIN
set #SourceInsertColumns = #SourceInsertColumns +
case when #SourceInsertColumns = '' THEN '' ELSE ',' end +
'S.' + #ColumnName
set #DestInsertColumns = #DestInsertColumns +
case when #DestInsertColumns = '' THEN '' ELSE ',' end +
#ColumnName
if #isIdentity = 0
BEGIN
set #UpdateClause = #UpdateClause +
case when #UpdateClause = '' THEN '' ELSE ',' end
+ #ColumnName + ' = ' + 'S.' + #ColumnName + char(10)
END
if #isIdentity = 1 set #identityColName = #ColumnName
END
FETCH NEXT FROM #ColNames INTO #ColumnName, #isIdentity, #IsComputed, #DATA_TYPE
END
CLOSE #ColNames
DEALLOCATE #ColNames
SET #sql = 'SET IDENTITY_INSERT ' + #TableName + ' ON;
MERGE ' + #TableName + ' AS D
USING ' + #SourceDB + #TableName + ' AS S
ON (D.' + #identityColName + ' = S.' + #identityColName + ')
WHEN NOT MATCHED BY TARGET
THEN INSERT(' + #DestInsertColumns + ')
VALUES(' + #SourceInsertColumns + ')
WHEN MATCHED
THEN UPDATE SET
' + #UpdateClause + '
WHEN NOT MATCHED BY SOURCE
THEN DELETE
OUTPUT $action, Inserted.*, Deleted.*;
SET IDENTITY_INSERT ' + #TableName + ' OFF'
Print #SQL
END
Not everything you wanted, but partially:
WHEN NOT MATCHED THEN
INSERT([all fields])
VALUES (field1, field2, ...)
(The values list has to be complete, and match the order of the fields in your table's definition.)
Simple alternative to merge without naming any fields or having to update statement whenever table design changes. This is uni-directional from source to target, but it can be made bi-directional. Only acts on changed records, so it is very fast even with linked servers on slower connection.
--Two statement run as transaction batch
DELETE
C
FROM
productschina C
JOIN
(select * from productschina c except select * from productsus) z
on c.productid=z.productid
INSERT into productschina select * from productsus except select * from productschina
Here is code to setup tables to test above:
--Create a target table
--drop table ProductsUS
CREATE TABLE ProductsUS
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Rate MONEY
)
GO
--Insert records into target table
INSERT INTO ProductsUS
VALUES
(1, 'Tea', 10.00),
(2, 'Coffee', 20.00),
(3, 'Muffin', 30.00),
(4, 'Biscuit', 40.00)
GO
--Create source table
--drop table productschina
CREATE TABLE ProductsChina
(
ProductID INT PRIMARY KEY,
ProductName VARCHAR(100),
Rate MONEY
)
GO
--Insert records into source table
INSERT INTO ProductsChina
VALUES
(1, 'Tea', 10.00),
(2, 'Coffee', 25.00),
(3, 'Muffin', 35.00),
(5, 'Pizza', 60.00)
GO
SELECT * FROM ProductsUS
SELECT * FROM ProductsChina
GO
I think this answer deserves a little more love. It's simple, elegant and works. However, depending on the tables in question, it may be a little bit slow because the except clause is evaluating every column.
I suspect you can save a little bit of time by just joining on the primary key and the last modified date (if one exists).
DELETE
C
FROM
productschina C
JOIN
(select primary_key, last_mod_date from productschina c except select primary_key, last_mod_date from productsus) z
on c.productid=z.productid
INSERT into productschina select * from productsus except select * from productschina