Stored Procedure with Dynamic Query - sql

I am writing a stored procedure using dynamic SQL.
In my procedure, I have say some 10 tables of similar columns.
For example if I consider Designation & Department tables, Designation table has these columns:
Designation, Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn
and Department table has these columns:
Department, Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn
and similarly I have some eight other tables.
In my stored procedure, I need to update and insert data into all the tables. So, instead of writing update & insert statements for each table, I am using a stored procedure which accepts the table name as a parameter and checks if the row already exists in that table.
If that row is present, then that record will be updated otherwise that record will be inserted into the respective table.
ALTER PROC UpdateMasterItems
(
#MasterTypeTmp varchar(50),
#NameTmp varchar(50),
#CodeTmp varchar(10))
AS
BEGIN
DECLARE #CntTmp numeric(2,0)
EXEC('select count(*)' + #CntTmp + ' from ' + #MasterTypeTmp + ' where ' + #MasterTypeTmp + ' = ' + #NameTmp)
IF(#CntTmp > 1)
BEGIN
EXEC('UPDATE ' + #MasterTypeTmp + ' SET ' + 'Code = ' + #CodeTmp + ', ModifiedBy = CURRENT_USER, MOdifiedOn = CURRENT_TIMESTAMP WHERE' + #MasterTypeTmp + ' = ' + #NameTmp)
RETURN 10
END
ELSE
BEGIN
EXEC('INSERT INTO ' + #MasterTypeTmp + '(' + #MasterTypeTmp + ', Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn ) VALUES (' + #NameTmp + ',' + #CodeTmp + ',' + 'CURRENT_USER, CURRENT_TIMESTAMP, CURRENT_USER, CURRENT_TIMESTAMP )')
RETURN 11
END
END
where #MasterTypeTmp is the table name(can be Department/Designation.....)
I am getting an error while executing the procedure:
Exec Statement:
EXEC UpdateMasterItems 'Designation', 'TestName', 'TestCode'
Error Statements:
Invalid column name 'TestName'.
Invalid column name 'TestCode'.
But TestName & TestCode are not column names. Those are the values for the columns. Please let me know if my dynamic queries are wrong or where the problem is!
Thanks in advance
Mounika

I think the error message is because you have wrapped column names with ' (ie; 'Designation' is wrong). Should be Designation
But there are other issue as well.
I think you cannot define variables out side dynamic sql and assign them inside. Because dynamic sql runs in a different session and therefore variables defined outside the scope are unknown. (ie; #CntTmp)
Even thought you are checking #CntTmp > 1 you are not really assigning count(*) value to it (won't work anyway due to reason 1)
Your #CntTmp will overflow if the record count > 99 (not really an issue if you don't have bad data)
If you need to get this work the way you have described, you have to declare variables, check the existance of records and then update/insert all within the same dynamic query. You could do without a variable using if exists (select ....) update ... else insert...

Your string concatenation are missing some ' and the db interpret the values as column names:
EXEC('INSERT INTO ' + #MasterTypeTmp + '(' + #MasterTypeTmp + ', Code, EntryBy, EntryOn, ModifiedBy, ModifiedOn )
VALUES (''' + #NameTmp + ''',''' + #CodeTmp + ''',' + 'CURRENT_USER, CURRENT_TIMESTAMP, CURRENT_USER, CURRENT_TIMESTAMP )')

First, this part will give you an error: Error converting data type varchar to numeric.
DECLARE #CntTmp numeric(2,0)
EXEC('select count(*)' + #CntTmp + ' from ' + #MasterTypeTmp + ' where ' + #MasterTypeTmp + ' = ' + #NameTmp)
Because the CntTmp is an numeric and can't directly used in that expression.
If you change to this:
EXEC('select count(*)' + cast(#CntTmp as varchar(30)) + ' from ' + #MasterTypeTmp + ' where ' + #MasterTypeTmp + ' = ' + #NameTmp)
It will give you an error because you can't use variable directly in dynamic SQL.
Also, it will not give you the output, because CntTmp is null.
So, you can create another variable to store the result from cast the numeric to varchar and then perform ISNULL function to give the variable a value if it's null.
Second, you're missing ' for your column value.
And here is the working stored procedures:
ALTER PROC UPDATEMASTERITEMS
( #MASTERTYPETMP VARCHAR(50), #NAMETMP VARCHAR(50), #CODETMP VARCHAR(10))
AS
BEGIN
DECLARE #CNTTMP NUMERIC(2,0)
DECLARE #CNTTMPVAL VARCHAR(30) = ISNULL(CAST(#CNTTMP AS VARCHAR(30)) , '')
EXEC ('SELECT COUNT(*) ' + #CNTTMPVAL + ' FROM ' + #MASTERTYPETMP + ' WHERE ' + #MASTERTYPETMP + ' = ''' + #NAMETMP + '''')
IF(#CNTTMP > 1)
BEGIN
EXEC('UPDATE ' + #MASTERTYPETMP + ' SET ' + 'CODE = ''' + #CODETMP + ''', MODIFIEDBY = CURRENT_USER, MODIFIEDON = CURRENT_TIMESTAMP WHERE' + #MASTERTYPETMP + ' = ''' + #NAMETMP + '')
RETURN 10
END
ELSE
BEGIN
EXEC('INSERT INTO ' + #MASTERTYPETMP + '(' + #MASTERTYPETMP + ', CODE, ENTRYBY, ENTRYON, MODIFIEDBY, MODIFIEDON ) VALUES (''' + #NAMETMP + ''',''' + #CODETMP + ''',' + 'CURRENT_USER, CURRENT_TIMESTAMP, CURRENT_USER, CURRENT_TIMESTAMP )')
RETURN 11
END
END
Anyway, you can format your SQL using this

Related

Dynamic update issue

I have a table, let's call it "table_X" in that table I have multiple columns (46) and in the future there is a possibility that we will expand it to have more columns, since the source of a table is an old ERP system, we need to transform the dataset in some cases, one of the transformation is that when we replace the '' values with NULLs and here is where I have problem, I wrote a dynamic update, because the previously mentioned reason (in the future we will have more columns), but I got error message and right now I am stuck.
DECLARE #SQL_columnnull NVARCHAR(max)
DECLARE #db2 NVARCHAR(max)
DECLARE #table2 NVARCHAR(max)
SET #db2 = 'db'
SET #table2 = 'table_X'
SELECT #SQL_columnnull_part_1 = STRING_AGG(CAST( N' UPDATE '+#db2+'.[dbo].'+#table2+' WITH (TABLOCK) SET ' AS NVARCHAR(MAX))
+QUOTENAME(COLUMN_NAME,'['']') + N' = NULL WHERE '
+QUOTENAME(COLUMN_NAME,'['']') ,+ N' = '''';')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION <= 3
for the two first column, the code is able to populate the command parts properly, but when it reaches the last column then the "='';" won't be populated
UPDATE db.[dbo].table_X SET [Column_1] = NULL WHERE [Column_1] = '';
UPDATE db.[dbo].table_X SET [Column_2] = NULL WHERE [Column_2] = '';
UPDATE db.[dbo].table_X SET [Column_3] = NULL WHERE [Column_3]
You are messing a bit about with your STRING_AGG
The syntax is
STRING_AGG ( expression, separator )
Your separator is
+ N' = '''';'
Since the separator is not applied after the last entry, you get the result you see!
I would also be wary of the cast, you are casting the start of the expression as nvarchar(max), however you are the concatenationg non varchar strings.
Finally - why are you doing separate updates for each column? this is very poor performance!
First, a fixed query that does what you want would be:
SELECT
#SQL_columnnull =
STRING_AGG(
CAST(
' UPDATE ' + #db2 + '.[dbo].' + #table2 + ' WITH (TABLOCK) SET '
+ QUOTENAME(COLUMN_NAME, '['']') + N' = NULL WHERE '
+ QUOTENAME(COLUMN_NAME, '['']') +N' = '''''
AS NVARCHAR(MAX))
,';')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION <= 3
I have included the full string within the cast, and the separator is now simply ";"
For performance I would however do this in stead:
SELECT
#SQL_columnnull =
N' UPDATE ' + #db2 + '.[dbo].' + #table2 + ' WITH (TABLOCK) SET ' +
STRING_AGG(
CAST(
QUOTENAME(COLUMN_NAME, '['']') + N'='+N'IIF('+ QUOTENAME(COLUMN_NAME, '['']') + N'= '''',NULL,'+ QUOTENAME(COLUMN_NAME, '['']')+') '
AS NVARCHAR(MAX))
,',
')
+'
WHERE '+
STRING_AGG(
CAST(
QUOTENAME(COLUMN_NAME, '['']') + N'= '''' '
AS NVARCHAR(MAX))
,' OR ')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table2
AND INFORMATION_SCHEMA.COLUMNS.ORDINAL_POSITION <= 3
This does just one pass over your table, and updates the columns that have the wrong data.
Finally I would check that all the columns are of a varchar or nvarchar data type, to exclude columns of othe datattypes which might give errors
This gives
UPDATE db.[dbo].table_X
SET [Column_1] = IIF([Column_1] = '', NULL, [Column_1])
,[Column_2] = IIF([Column_2] = '', NULL, [Column_2])
,[Column_3] = IIF([Column_3] = '', NULL, [Column_3])
WHERE [Column_1] = ''
OR [Column_2] = ''
OR [Column_3] = ''

Is there a way to Replace(*) in sql?

So simply I'm doing something similar to:
select
[BadData], [WorseDate], [IQuitData]
into
#BadDataTempTable
from
SomeoneElsesMess
what I want to do now is something similar to:
Select
Replace(#BadDataTempTable.*, ',', ' ')
from
#BadDataTempTable -- Replace all commas in every column `with a space--`
Is this possible? If so please show me the easiest (non-function) way to do so.
Thanks. SQL Server 2012 I think. I'm using SSMS 17
No, the columns have to be specified. You could use dynamic SQL to build your update / query. Then just copy the command you want from the results.
Maybe this will help get you started:
BEGIN
-- Set the replace value
DECLARE #ls_replaceValue NVARCHAR(MAX) = ',';
-- Set the with value
DECLARE #ls_withValue NVARCHAR(MAX) = ' ';
-- Set the table name we want to query
DECLARE #ls_table NVARCHAR(MAX) = 'some_table';
-- Get all of the columns and provide the replace parameters
DECLARE #ls_columns NVARCHAR(MAX) = '';
SELECT #ls_columns = #ls_columns + ', ' + name + ' = REPLACE(' + name + ', ' + '' + '''' + REPLACE(#ls_replaceValue, '''', '''''''') + '''' + ', ' + '''' + REPLACE(#ls_withValue, '''', '''''''') + '''' + ')'
FROM sys.all_columns
WHERE object_id = OBJECT_ID(#ls_table)
AND collation_name IS NOT NULL; -- Skip columns that aren't character based
-- Remove the first ', ' from the column list
SET #ls_columns = SUBSTRING(#ls_columns, 3, LEN(#ls_columns));
IF #ls_columns = ''
BEGIN
PRINT 'Table not found'
RETURN
END
-- Build a query
DECLARE #ls_query_sql NVARCHAR(MAX) = '';
SET #ls_query_sql = 'SELECT ' + #ls_columns + ' FROM ' + #ls_table;
-- Show the results
SELECT #ls_query_sql AS querySQL;
END
Just since the OP asked about how you might do this in dynamic SQL, here's how I'd approach it. Basically get the table schema information and concatenate all the columns, plus the REPLACE logic you want using FOR XML. This basically constructs the statement Rigerta posted, but does it dynamically.
use tempdb
go
if object_id('tempdb.dbo.#SomeoneElsesBadData') is not null drop table #SomeoneElsesBadData
create table #SomeoneElsesBadData
(
BadData varchar(250),
WorseData varchar(250),
IQuitData varchar(250)
)
declare #sql nvarchar(max)
select #sql = 'select '
+ stuff((select ', '
+ name
+ ' = replace(' + name + ''','', '''')'
from tempdb.sys.columns
where object_id = object_id('tempdb.dbo.#SomeoneElsesBadData')
for xml path('')), 1, 1, '')
+ ' into #BadDataTempTable
from #SomeoneElsesBadData'
exec sp_executesql #sql
All things being equal, the data should probably be cleaned before it gets into SQL, but reality is rarely fair.

How to insert without specifying the column name?

Having many tables (for example two):
|PEOPLE_ID|PEOPLE_NAME|PEOPLE_ACCOUNT|OTHER_NULLABLE_COLUMNS|
|001 |"Andrea" |NULL |OTHER_NULLABLE_COLUMNS|
|002 |"..." |NULL |OTHER_NULLABLE_COLUMNS|
and:
|PET_ID|PET_NAME|PET_OWNER|OTHER_NULLABLE_COLUMNS|
|001 |"Fido" |NULL |OTHER_NULLABLE_COLUMNS|
|002 |"..." |NULL |OTHER_NULLABLE_COLUMNS|
And then I need a generic stored procedure to insert to any table passing the name of the table (pet, people) and the id and name as parameters.
exec sp_inserttottable 'people', '456', 'Gustavo'
exec sp_inserttottable 'pet', '852', 'Scooby'
But in the definition of the Stored Procedure I can't do this:
CREATE PROCEDURE sp_inserttottable
#tableName nvarchar(50),
#codeToInsert nvarchar(4),
#detailToInsert nvarchar(100)
AS
DECLARE #cmd AS NVARCHAR(max)
SET #cmd = 'INSERT INTO ' + #tableName + ' VALUES(' + #codeToInsert + ',' + #detailToInsert + ' )' + ' '
EXEC sp_executesql #cmd
It throw me an error I need to specify the column names like this:
'INSERT INTO ' + #tableNames + ' (PEOPLE_ID, PEOPLE_NAME) VALUES(' + #codeToInsert + ',' + #detailToInsert + ' )' + ' '
But if I do that the procedure can't be generic.
If I can do something like this, put values by column order:
'INSERT INTO ' + #tableNames + ' (1, 2) VALUES(' + #codeToInsert + ',' + #detailToInsert + ' )' + ' '
How to achieve this?
(UPDATE) Applying the Solution of #Gordon Linoff
First using DEFAULT KEYWORD
CREATE PROCEDURE sp_InsertTableType
#tableNames nvarchar(50),
#codeToInsert nvarchar(4),
#detailToInsert nvarchar(100)
AS
DECLARE #cmd AS NVARCHAR(max)
SET #cmd = 'INSERT INTO ' + #tableNames + ' VALUES(' + #codeToInsert + ',' + #detailToInsert + ', DEFAULT)' + ' ';
--SET #cmd = 'INSERT INTO TTCODI_NACI_UPDATE(CODE, DESCRI) VALUES(' + #codeToInsert + ',' + #detailToInsert + ')' + ' '
EXEC sp_executesql #cmd
Then when I call:
exec sp_InsertTableType 'TTCODI_NACI', '1000', 'kuwaiti'
It throws me: Invalid column name '%.*ls'.
And the second Alternative:
Creating the view:
create view TTCODI_NACI_UPDATE
as
SELECT CO_NACI AS CODE, DE_NACI AS DESCRI
, DE_ABRE_NACI AS ABRE
FROM TTCODI_NACI;
Changing the Stored Procedure:
CREATE PROCEDURE sp_InsertTableType
#tableNames nvarchar(50),
#codeToInsert nvarchar(4),
#detailToInsert nvarchar(100)
AS
DECLARE #cmd AS NVARCHAR(max)
--SET #cmd = 'INSERT INTO ' + #tableNames + ' VALUES(' + #codeToInsert + ',' + #detailToInsert + ', DEFAULT)' + ' ';
SET #cmd = 'INSERT INTO TTCODI_NACI_UPDATE(CODE, DESCRI) VALUES('
+ #codeToInsert + ',' + #detailToInsert + ')' + ' '
EXEC sp_executesql #cmd
When I call:
exec sp_InsertTableType 'TTCODI_NACI', '1000', 'kuwaiti'
It throws me: Update or insert of view or function '%.*ls' failed because it contains a derived or constant field.
This is a solution to your problem, but I don't really advocate it. Your tables are for different entities, so I'm not so sure that a generic stored procedure is a good idea.
One solution is to use the DEFAULT keyword:
SET #cmd = 'INSERT INTO ' + #tableName + ' VALUES(DEFAULT, ' + #codeToInsert + ',' + #detailToInsert + ' )' + ' ';
Oh, I really don't like that. It means that the insert depends on the order that the columns are defined in the table -- and woe to anyone who adds a new column and messes up this code, far away from the creation of the new column.
Another solution that I'd be slightly more comfortable with is to create views such as:
create v_people_for_update as
select people_id as id, people_name as name, people_account as account
from people;
Then, insert into the view:
SET #cmd = 'INSERT INTO ' + #viewName(name, account) + ' VALUES(, ' + #codeToInsert + ',' + #detailToInsert + ' )' + ' ';
This at least lets you specify the columns (a good thing) and the collection of views can be named so it is obvious that this stored procedure is working on them.
However, if you have tables with such similar structures, then you should probably combine them into one table and dispense with the idea of using dynamic SQL to choose among different tables. Just use one table and add a column specifying the type of thing that each row refers to.

Getting an "out of range value" on a conversion of varchar data type to a datetime data type

The error occurs while stored procedure is executing a function to convert varchar data type into into a datetime data type
Since I cannot debug the SQL statement where the error is occurring, is there a way that I can print the values of the data that which is being converted in the store procedure and causing the out of range error?
Or, how can I find out why is the error occurring?
Code:
BEGIN
DECLARE #MONTH VARCHAR(2) = SUBSTRING(#CASH_DATE_FROM,5,2)
DECLARE #YEAR VARCHAR(4) = SUBSTRING(#CASH_DATE_FROM,1,4)
DECLARE #WORK_FILE VARCHAR(80) = ''
DECLARE #SQL VARCHAR(3000) = ''
-- Create Report Tables
IF (#REPORT_TYPE = '2')
BEGIN
SET #WORK_FILE = 'RRS.STG_PST_COLLECTIONS_REVENUE_01'
-- print data
-- select column from table where column not like '%[^0-9]%';
-- Select records from Staging 1
SET #SQL = 'INSERT INTO ' + #WORK_FILE +
' SELECT
REPORT_PERIOD, DATA_SET
,RRS.udfConvertDatetime(START_CASH_DATE)START_CASH_DATE
,RRS.udfConvertDatetime(END_CASH_DATE)END_CASH_DATE, TAT_GRP
,RRS.udfConvertDatetime(CASH_DATE)CASH_DATE, REMIT_ID
,RRS.udfConvertDatetime(TRANSACTION_DATE)TRANSACTION_DATE, PAYMENT_AMT
,TAT, TAT_IND, ACCT_NBR, ORIGINAL_IND, CASH_TRANS_DAY_IND, TRANSFERED_TO
,TRANSFERED_TO_TAT_IND, TRANSFERED_TO_ACCT_NBR, TRANSFERED_FROM, TRANSFERED_FR_TAT_IND
,TRANSFERED_FR_ACCT_NBR, AR_RETURN_IND, PYMT_TRANS_ID, UNAPPLIED_APPLIED_IND
,BACKED_OUT_DC_REF_IND, FUND_CODE, BATCH, ONLINE_ENTERED_IND, REMIT_TYPE_CODE, EFT_SW
FROM ' + 'RRS.LBS_28_STAGING_' + #MONTH + '_' + #YEAR + '_1' +
' WHERE AR_RETURN_IND = ' + '''A'''
EXEC (#SQL)
-- Select records from Staging 2
SET #SQL = 'INSERT INTO ' + #WORK_FILE +
' SELECT
REPORT_PERIOD, DATA_SET
,RRS.udfConvertDatetime(START_CASH_DATE)START_CASH_DATE
,RRS.udfConvertDatetime(END_CASH_DATE)END_CASH_DATE, TAT_GRP
,RRS.udfConvertDatetime(CASH_DATE)CASH_DATE, REMIT_ID
,RRS.udfConvertDatetime(TRANSACTION_DATE)TRANSACTION_DATE, PAYMENT_AMT
,TAT, TAT_IND, ACCT_NBR, ORIGINAL_IND, CASH_TRANS_DAY_IND, TRANSFERED_TO
,TRANSFERED_TO_TAT_IND, TRANSFERED_TO_ACCT_NBR, TRANSFERED_FROM, TRANSFERED_FR_TAT_IND
,TRANSFERED_FR_ACCT_NBR, AR_RETURN_IND, PYMT_TRANS_ID, UNAPPLIED_APPLIED_IND
,BACKED_OUT_DC_REF_IND, FUND_CODE, BATCH, ONLINE_ENTERED_IND, REMIT_TYPE_CODE, EFT_SW
FROM ' + 'RRS.LBS_28_STAGING_' + #MONTH + '_' + #YEAR + '_2' +
' WHERE AR_RETURN_IND = ' + '''A'''
EXEC (#SQL)
-- Update TAT_GRP
SET #SQL = 'UPDATE ' + #WORK_FILE +
' SET TAT_GRP = (CASE WHEN SUBSTRING(TAT,1,1) = ' + '''S''' + ' THEN ' + '''SUA''' + ' ELSE ' + '''STF''' + ' END)' +
' WHERE CONVERT(VARCHAR(32),START_CASH_DATE) = '
+ CHAR(39) + CONVERT(VARCHAR(32),RRS.udfConvertDatetime(#CASH_DATE_FROM))+ CHAR(39)
+ ' AND END_CASH_DATE = '
+ CHAR(39)+ CONVERT(VARCHAR(32),RRS.udfConvertDatetime(#CASH_DATE_TO))+CHAR(39)
EXEC (#SQL)
END
This will help you find the records which is having non numeric data:
select column from table where column not like '%[^0-9]%';
The date range for DATETIME datatype is January 1, 1753, through December 31, 9999.
So first check whatever dates that you are passing lies between this date range. Also check if you passing invalid date like 31st April etc.
You can print the dynamic query that you are forming by using PRINT function.
eg. PRINT #SQL
You don't have to convert to varchar.
TRY THIS
dtadmi BETWEEN CONVERT(DATETIME,'+''''+#dt_frm+'''''+' ) AND CONVERT(DATETIME,'+''''+#dt_to+''''+').....
where #dt_frm,#dt_to=storedprocedure parameter

Generating Scripts for Specific Records in SQL Server

This is probably a bit of a limited, but valuable scenario. I have a SQL Server 2008 database with a table that has millions of records. There appears to be an intermittent problem with several of the records. I'm trying to repro the problem. In an effort to do this, I finally got the ID of an offending record. I would like to generate an INSERT statement associated with this single record in my PROD database. Then I can easily migrate it into my TESTING database in an effort to repro and resolve the problem.
Basically, I need to generate a single INSERT statement for a single record from a single table where I know the primary key value of the record.
Does anyone have any ideas of how I can accomplish this? Essentially, I want to generate insert statements on a conditional basis.
Thank you!
First try to recreate what you want to insert with a SELECT statement.
After that you can insert into the table with a INSERT INTO like this:
INSERT INTO tablename
SELECT ....
If they are on different servers, you can use INSERT like this:
INSERT INTO tablename VALUES (...)
using the values given by the SELECT in the other server fill the values in the insert.
In your specific case I think you can do this:
CREATE PROCEDURE dbo.GenerateSingleInsert
#table NVARCHAR(511), -- expects schema.table notation
#pk_column SYSNAME, -- column that is primary key
#pk_value INT -- change data type accordingly
AS
BEGIN
SET NOCOUNT ON;
DECLARE #cols NVARCHAR(MAX), #vals NVARCHAR(MAX),
#valOut NVARCHAR(MAX), #valSQL NVARCHAR(MAX);
SELECT #cols = N'', #vals = N'';
SELECT #cols = #cols + ',' + QUOTENAME(name),
#vals = #vals + ' + ' + REPLICATE(CHAR(39),3) + ','
+ REPLICATE(CHAR(39),3) + ' + ' + REPLICATE(CHAR(39),2) + '+'
+ 'RTRIM(' + CASE WHEN system_type_id IN (40,41,42,43,58,61) THEN
'CONVERT(CHAR(8), ' + QUOTENAME(name) + ', 112) + '' ''
+ CONVERT(CHAR(14), ' + QUOTENAME(name) + ', 14)'
ELSE 'REPLACE(' + QUOTENAME(name) + ','''''''','''''''''''')' END + ')
+ ' + REPLICATE(CHAR(39),2)
FROM sys.columns WHERE [object_id] = OBJECT_ID(#table)
AND system_type_id <> 189 -- can't insert rowversion
AND is_computed = 0; -- can't insert computed columns
SELECT #cols = STUFF(#cols, 1, 1, ''),
#vals = REPLICATE(CHAR(39), 4) + ' + ' + STUFF(#vals, 1, 13, '')
+ REPLICATE(CHAR(39), 2);
SELECT #valSQL = N'SELECT #valOut = ' + #vals + ' FROM ' + #table + ' WHERE '
+ QUOTENAME(#pk_column) + ' = ''' + RTRIM(#pk_value) + ''';';
EXEC sp_executesql #valSQL, N'#valOut NVARCHAR(MAX) OUTPUT', #valOut OUTPUT;
SELECT SQL = 'INSERT ' + #table + '(' + #cols + ') SELECT ' + #valOut;
END
GO
So let's try it out:
CREATE TABLE dbo.splunge
(
ID INT, dt DATETIME, rv ROWVERSION, t NVARCHAR(MAX)
);
INSERT dbo.splunge(ID, dt, t)
SELECT 1, GETDATE(), 'foo'
UNION ALL SELECT 2, GETDATE(), 'bar'
UNION ALL SELECT 3, GETDATE(), 'O''Brien';
EXEC dbo.GenerateSingleInsert N'dbo.splunge', N'ID', 1;
SQL
-------------
INSERT dbo.splunge([ID],[dt],[t]) SELECT '1','20120517 10:07:07:330','foo'
EXEC dbo.GenerateSingleInsert N'dbo.splunge', N'ID', 2;
SQL
-------------
INSERT dbo.splunge([ID],[dt],[t]) SELECT '2','20120517 10:07:07:330','bar'
EXEC dbo.GenerateSingleInsert N'dbo.splunge', N'ID', 3;
SQL
-------------
INSERT dbo.splunge([ID],[dt],[t]) SELECT '3','20120517 10:07:07:330','O''Brien'
If there is an IDENTITY column you may need to set SET IDENTITY_INSERT ON for the TEST table, and verify that there is no collision. Probably about 500 caveats I should mention, I haven't tested all data types, etc.
However in the more general case there is a lot more to it than this. Vyas K has a pretty robust stored procedure that should demonstrate how complicated it can get:
http://vyaskn.tripod.com/code/generate_inserts_2005.txt
You are probably far better off using a tool like Red-Gate's SQL Data Compare to pick a specific row and generate an insert for you. As I've blogged about, paying for a tool is not just about the money, it's about the hours of troubleshooting and bug-fixing that someone else has already done for you.
Aaron,
I liked your code, it solved a problem for me. I ran into a few issues using it (like you said I would) with nulls and the text type so I made some changes to address those issues.
ALTER PROCEDURE dbo.GenerateSingleInsert
#table NVARCHAR(511), -- expects schema.table notation
#pk_column SYSNAME, -- column that is primary key
#pk_value INT -- change data type accordingly
AS
BEGIN
SET NOCOUNT ON;
DECLARE #cols NVARCHAR(MAX), #vals NVARCHAR(MAX),
#valOut NVARCHAR(MAX), #valSQL NVARCHAR(MAX);
SELECT #cols = N'', #vals = N'';
SELECT #cols = #cols + ',' + QUOTENAME(name),
#vals = #vals + ' + '','' + ' + 'ISNULL('+REPLICATE(CHAR(39),4)+'+RTRIM(' +
CASE WHEN system_type_id IN (40,41,42,43,58,61) -- datetime types
THEN
'CONVERT(CHAR(8), ' + QUOTENAME(name) + ', 112) + '' ''+ CONVERT(CHAR(14), ' + QUOTENAME(name) + ', 14)'
WHEN system_type_id IN (35) -- text type NOTE: can overflow
THEN
'REPLACE(CAST(' + QUOTENAME(name) + 'as nvarchar(MAX)),'+REPLICATE(CHAR(39),4)+','+REPLICATE(CHAR(39),6)+')'
ELSE
'REPLACE(' + QUOTENAME(name) + ','+REPLICATE(CHAR(39),4)+','+REPLICATE(CHAR(39),6)+')'
END
+ ')+' + REPLICATE(CHAR(39),4) + ',''null'') + '
FROM sys.columns WHERE [object_id] = OBJECT_ID(#table)
AND system_type_id <> 189 -- can't insert rowversion
AND is_computed = 0; -- can't insert computed columns
SELECT #cols = STUFF(#cols, 1, 1, ''),
#vals = REPLICATE(CHAR(39),2) + STUFF(#vals, 1, 6, '') + REPLICATE(CHAR(39),2) ;
SELECT #valSQL = N'SELECT #valOut = ' + #vals + ' FROM ' + #table + ' WHERE '
+ QUOTENAME(#pk_column) + ' = ''' + RTRIM(#pk_value) + ''';';
EXEC sp_executesql #valSQL, N'#valOut NVARCHAR(MAX) OUTPUT', #valOut OUTPUT;
SELECT SQL = 'INSERT ' + #table + '(' + #cols + ') SELECT ' + #valOut;
END