Oct 3

Written by: Bruce Chapman
Tuesday, October 03, 2006 4:33 PM 

Creating Automatic insert and update statements from SQL Tables using T-SQL

Shifting data around is a requirement for every developer and DBA.  Whether it is building (or rebuilding!) a database, migrating data for releases or just creating test data, I at least always seem to be opening a new query page and typing in 'insert into ....'.   I know that SQL Server 2005 has great little widgets for creating insert statements based off the table, but what I have always needed was a way to generate an update or insert statement based on data in an existing table in a different database.  Sometimes a DTS / BCP import/export job is too much work when there is only one or two rows, or maybe you can't get a file anywhere near the server because of security restrictions.  Maybe you just like doing things the hard way.

What you need is a T-Script which will read the contents of a table, then create update / insert statements for that data to go into the same table in a different server and database.

I've alway had little scripts written for specific tables, but on prompting from a friend, I decided to get serious and create a one-size-fits-all SQL generator which will generate a line of SQL for each row in a source table, which, when run on the target database, will perfectly replicate the data.  I know there are tools around to do this, but this is quick and very easy.  It also will generate either for a specific row of text, or simply for all rows in the source table.  Sorry no partial matching has been built in, but I'm sure a smart developer out there can easily tweak to introduce partial matching for rows.

Starting Out

The first port of call for a project like this is the system tables.  I've always loved the system tables, which are one of those weird circumstances where the structure of the product you are using is described by metadata within the product.  Like Reflection and other self-describing metadata, you can get a bit chicken-and-egg if you think about it for too long.

The first thing you need to do is get a list of the columns within the target table, and the data type of each of those columns.  This is done with a pretty simple piece of SQL:

select so.name, sc.name, st.name, sc.length
, Case when sc.status = 0x80 then 'Y' else 'N' END as IsIdent
, ColOrder
from sysobjects so
inner join syscolumns sc
on so.id= sc.id inner join systypes st
on sc.xtype = st.xusertype where so.Name = 'SourceTableName'
Order by ColOrder

This will give you the list of columns within a source table, in the same order they are in the database.   This is the basic structure needed to get the data out.  The tricky column with the CASE statement checks the Status binary column and generates a Y/N depending on whether or not the column is an identity column - which we will come to later.

The problem with getting the information out is that we are working in looping sets - which is difficult to do in SQL Server (well, pre .NET anyway).  In order to collate the results needed, a temporary table will be created.  This temporary table will contain the actual output of the script.  Another temporary table is created as a staging point for the data which is in each column.

create table #output (Line varChar(4000), LineOrder int) -- Holds the output for the script
create table #ColumnValues (ColName varChar(250), ColOrder int, RowNumber int, ColValue varchar(4000), colType varchar(50)) -- Holds the values for each column/row combination

Working out the Algorithm

The basic algorithm in pseudo-code is:

for each column in the source table
   if the column is not an identity column
       insert into the #ColumnValues table the column name and the value from the source table
  end if
end for
for each row in the #ColumnValues table
   while each column in the table belonged to the same row in the source table
       concatenate the update/insert statement together into a string variable
   end while
   at the end of each column set for a row
   if an insert statement desired
     create the insert statement specific text
   else
     create the update statement specific text
   end if
end for
select all rows from the #Output table
 

This basic algorithm has some complicated parts when translated into T-SQL - the most conceptually difficult is selecting the values from a particular column in a table.  The problem is that you need dynamic SQL - no problem there - but you need to store the result into some type of variable.  You can't use locally declared T-SQL @ variables, because they are not in the scope of the dynamic SQL.  This is why a temporary table is used instead.    This can be used in the dynamic SQL, which looks like this:

exec ('insert into #ColumnValues (ColName, ColOrder, ColValue, ColType)
select ''' + @colName + ''', ' + @ColOrder + ', Convert(nvarchar(4000),' + @colName + ') , ''' + @colType +
''' from ' + @tabName + ' order by ' + @SortCol + ' ' + '
declare @counter int
set @counter = 0 ' + '
update #ColumnValues Set @Counter = RowNumber = @Counter + (' + @numCols + ' * 10)
where ColName = ''' + @colName + '''' )

This rather scary looking piece of code does a couple of things.   The @colName variable is taken from the cursor loop, and is simply the name of the Column in the source table (as is @colType and @tabName).  The code works one column at a time across the table - like learning to read - left to right, down to the next row and on and on...  The insert statement takes the value read from the specific column of the source table and inserts the value read from the source table into the temp table (#columnValues).  The next line declares a counter and then incrementally assigns the RowNumber of the source table - this is to keep all the columns of the same row together - otherwise the results would be all over the place, with the values in different columns matched with the wrong primary keys - a bit like a board game accidentally bumped and all the pieces in the wrong place.  The last update statement uses the very oblique Set @value = Column = @Value + number syntax - a bit of a hidden secret of SQL Server which allows you to update a running counter for each row in a table.   You can either try and work out how it works or accept that it does and move on.

The row counter is then used a bit later on when creating the insert/update statement from the values in the #ColumnValues table.  It is used a loop switch so the code knows when the first column finishes and the next one ends.  This is because the structure of the original table :

row1 : col1 col2 col3 col4
row2 : col1 col2 col3 col4

is now represented in a table as :

row1 col1
row1 col2
row1 col3
row1 col4
row2 col1
row2 col2
etc....

The row counter (@RowNumber) is checked after each column in a post-loop check, to determine whether or not we have switched rows - ie, from row1 to row2.  At each row switch reading from the table, the insert/update statement is finished off and a new one is started.  Each time an insert/update statement is finished off, it is inserted into the results table.  When the script is finished, all of the statements are read from the temporary table, and the result will give you a nice list of insert statements which can then be executed into the server/database of your choice (providing, of course, that the exact same table definition exists in that database).

Putting it all together and getting output

To run the script, simply load it up in Query Editor or SQL Server manager.  Edit the @tab variable to the name of the source table, and type in 'INSERT' or 'UPDATE' into the @StatementType variable.  If you wish to do an Update statement or wish to do a single-row insert statement, put in the value of the Primary Key column for the table (currently this only works with a single column primary key) and the name of the column which is the primary key.   

Source Code Example

The above example shows the script ready to run an all - rows set of insert statements for a table called 'PaymentStatusTest'.

This is the structure of the 'PaymentStatusTest' table:

Example Table Column list

Execute the script, and then copy out the results from the query results window into the location of your choice.  It helps to run Query Editor as 'text' output.

Query Output

The code is tested on a narrow range of tables, and more esoteric column types may throw the code off.  Extra types (such as uniqueidentifier) can be programmed for by adding to the 'case' statement which determines what to do for each found column type.  This is primarily how to treat the column in the output script, and whether to have quotes around it or not.

Tags:

9 comment(s) so far...

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Hello Bruce,
Great script! I was searching for a script to do just waht this does and stumbled on your code. I have one little question though. Is it possible to format the output to a single line per row? I have looked through the code trying to find where I can take out the line breaks but looks like it is a part of the logic. Is there a way of doing this.

Regards,
fargus

By fargus on   Thursday, March 29, 2007 7:51 PM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

@fargus

Sure thing- and a good idea as well. I've thrown a new script together which seems to work pretty well, although it's not the prettiest code I've ever written :-)

The reason it comes out with line breaks is that each piece of the query is assembled as a row in a temp table - which means that when the table is selected, they all come out as separate lines in the query window.

Basically I've added a second loop which combines the output data into another temp table, concatenating the code for each insert/update statement on the original table. Let me know how it goes, I'll probably post the update on code project as well.

Just re-download the zip file - I've added a second script file in with it

regards
Bruce

By Bruce Chapman on   Friday, March 30, 2007 12:36 AM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Hi Bruce,
Thanks for the response.
I realised that was the case (each piece of the query in rows) and so my efforts at concatenating the output by replacing char(13) with space yielded no results.

I will try the new version and let you know how it goes.

Cheers.

fargii

By fargii on   Saturday, March 31, 2007 3:05 AM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Hi Bruce,
I have just tried the new version. Works a charm. You are a star!!

fargii

By Fargii on   Saturday, March 31, 2007 3:10 AM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Hi Bruce,

Great Script :-) But I have a unique problem. One of the column in my table is of binary data type. When you convert the binary data type to varchar or nvarchar it becomes junk character. What I would love to see is something like "insert into table.... col. val (oxacf89788890, .....)

I have now run out of options in SQL 2000 because there is no implicit conversion from binary to nvarchar and even if you use convert or cast you end up seeing junk characters.

Can somebody help me???

Thanks

By Santhosh on   Thursday, June 14, 2007 11:57 AM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Santhosh

I'm pretty sure you could get nvarchar/binary conversion to work, but you'd have to do some specific coding. The best way to go would be to create a function that did this for you in the specified format, then modify the insert generate script so that it calls the function for the binary column.

I don't have a database with binary data in it to try it on, so I can't easily fix your problem for you. But it should be solveable, as you can select from binary columns, and you can insert into binary columns, ergo, you should be able to string the two together.

If you do solve it let me know

-Bruce

By Bruce on   Thursday, June 14, 2007 5:45 PM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Great job Bruce.
Why do we need temp table in between? Could we not form a query string with "Insert into targetTable Values( ' ... all the columns we fetch...' ) select '... all the columns in same order...' from sourcedb..sourceTable" ?

By Chester Ng on   Tuesday, June 26, 2007 3:41 AM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Chester

The temp table is required - the reason is simple but obscure. In order to get the value of a column in the source table, dynamic sql must be used and constructed at runtime. If selecting from 'myTable', then you must create, at run time, 'Select MyColumn from MyTable' (because you don't know the name of the table at declaration time).

Because dynamic sql (submitted with the 'exec' command) is in a different scope from your script, you can't pass back a value from the exec statement into your 'main' statement.

An example:
declare @myValue int
exec ('select @myValue = MyValue From MyTable')

This line is not legal Sql, because the scope of the declaration of @myValue is different from the scope of the dynamic Sql.

Even if you wrote it like this :
exec ('declare @myValue int select @MyValue = MyValue from MyTable')
select @MyValue

It is still not valid Sql because of the difference in scope of the @MyValue variable.

So what is needed is a way of declaring a variable that goes across the two scopes. The '@' style t-sql variables don't have the ability to do this, so the other option is a temp table, which is available to both the 'main' script and any dynamic sql.

It's true that I could have used a single-column, single row temp table to store and output the values one at-a-time and kept it all in a single loop, but once the table is created, it provides other benefits like being able to maintain the output order of the columns, and a few other bits and pieces.

But, yes, you do need the Temp table.

By Bruce on   Tuesday, June 26, 2007 8:37 AM

Re: Creating Automatic insert and update statements from SQL Tables using T-SQL

Great script, it helped me alot.
But I think you missed out on one row. You should add:

when 'varchar' then ''''

...as a quotetype (you know where in the code). I realised this when the script didnt quote my varchar columns and such didnt produce t-sql that worked. :)

By Robert Nilsson on   Friday, August 24, 2007 6:42 PM

Your name:
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 
Bruce Chapman
Hi, I'm Bruce Chapman, and this is my blog. You'll find lots of information here - my thoughts about business and the internet, technical information, things I'm working on and the odd strange post or two.