When building a dynamic query, an error is shown converting data type varchar to float - how to solve it? - sql

I work on SQL Server 2012; when building a dynamic query, I get this error
Msg 8114, Level 16, State 5, Line 16
Error converting data type varchar to float
How to solve this error?
I build a dynamic query based on #Header and #column and #Body.
#Header represents the header must display as headers.
#column represent pivot columns.
#Body represent select query for data.
CREATE TABLE #FinalTable
(
PART_ID nvarchar(50) ,
CompanyName nvarchar(50),
PartNumber nvarchar(50),
DKFeatureName nvarchar(100),
value float,
StatusId int,
DisplayOrder int,
splitFlag bit
)
INSERT INTO #FinalTable
VALUES
('1222', 'Honda', 'silicon', 'package', '15.50Am', 2, 5, 0),
('1900', 'MERCEIS', 'GLASS', 'family', '90.00Am', 2, 2, 1),--have column per Unit on #Header because FlagAllow=1
('5000', 'TOYOTA', 'alominia', 'source', '70.20kg', 2, 1, 0),
('8000', 'MACDA', 'motor', 'parametric', '50.40kg', 2, 3, 1),--have column per Unit on #Header because FlagAllow=1
('8900', 'JEB', 'mirror', 'noparametric', '75.35kg', 2, 4, 0)
DECLARE #Header NVARCHAR(MAX)
SELECT
#Header = STUFF((SELECT ', ' + CASE WHEN A.splitFlag = 1 AND a.value <> '-' AND (a.Value IS NOT NULL) THEN '''' + A.DKFeatureName + ''' AS '' '+ A.DKFeatureName + ''', ''' + A.DKFeatureName + 'Units' + ''' AS ''' + A.DKFeatureName +'Units' +'''' else ''''+A.DKFeatureName +''' as ''' + A.DKFeatureName +'''' END
FROM #FinalTable A
WHERE StatusId = 2
ORDER BY DisplayOrder
FOR XML PATH ('')), 1, 2, '')
DECLARE #Columns NVARCHAR(MAX)
SELECT
#Columns = STUFF(
(
SELECT ', ' + case when A.splitFlag = 1 and a.value<> '-' and (a.Value is not null) then '['+A.DKFeatureName+'],['+A.DKFeatureName+'Unit]' else quotename(A.DKFeatureName) end
FROM #FinalTable A where StatusId=2
ORDER BY DisplayOrder
FOR XML PATH ('')
),1,2,''
)
DECLARE #Body NVARCHAR(MAX)
SELECT
#Body = STUFF(
(
SELECT ', ' + case when A.splitFlag = 1 and a.value<> '-' and (a.Value is not null) then 'LEFT(' + QUOTENAME (A.DKFeatureName) + ',PATINDEX(''%[^0-9.]%'',' + QUOTENAME (A.DKFeatureName) + '+ ' + ''' ''' + ')-1) as ['+A.DKFeatureName+'],RIGHT('+ QUOTENAME (A.DKFeatureName) +',LEN('+ QUOTENAME (A.DKFeatureName) +') - PATINDEX(''%[^0-9.]%'','+ QUOTENAME (A.DKFeatureName) +')+1) as ['+A.DKFeatureName +'Units'+']' else quotename(A.DKFeatureName) end
FROM #FinalTable A
where StatusId=2
ORDER BY A.DisplayOrder
FOR XML PATH ('')
),1,2,''
)
DECLARE #SQL NVARCHAR(MAX)
select #SQL =CONCAT('
SELECT * Into #NewTable
FROM #FinalTable
PIVOT(max(Value) FOR DKFeatureName IN ('+#Columns+')) AS PVTTable
',
N' Select ''PART_ID'' as ''PART_ID'' ,''CompanyName'' as ''CompanyName'',''PartNumber'' as ''PartNumber'' , ' +#Header + '
union all
select PART_ID,CompanyName,PartNumber, ' +#Body + ' from #NewTable
')
EXEC (#SQL)

The column value you are using is float which is a number not varchar. But you input varchar = "15.50Am".
You can create new columns for units (am, kg, etc.)
or you can change value like value numeric (5,2)

Related

Need all columns on one row

I wrote the following query:
IF OBJECT_ID ('tempdb..#ColumnsType') IS NOT NULL DROP TABLE #ColumnsType
DECLARE #vQuery NVARCHAR(MAX) =''
IF OBJECT_ID ('tempdb..#random') IS NOT NULL DROP TABLE #random
CREATE TABLE #random (
ColumnID INT PRIMARY KEY IDENTITY(1,1) NOT NULL
, randomname VARCHAR(50)
, randomvalue INT)
INSERT INTO #random (randomname, randomvalue)
VALUES ('a3', 123)
, ('bla', 4325)
, ('another_bla', 5643)
, ('end_here', 3)
select *
from #random
CREATE TABLE #ColumnsType (
ColumnID INT PRIMARY KEY IDENTITY(1,1) NOT NULL
, ColumnName sysname
, DataType sysname
)
INSERT INTO #ColumnsType (ColumnName, DataType)
SELECT [name],
system_type_id
FROM Tempdb.Sys.Columns
WHERE Object_ID = Object_ID('tempdb..#random')
AND system_type_id = 56
DECLARE #i INT = (SELECT MIN(ColumnID) FROM #random);
DECLARE #maxId INT = (SELECT MAX(ColumnID) FROM #random);
DECLARE #ColumnName VARCHAR(200);
DECLARE #DataType VARCHAR(200);
WHILE #i <= #maxId
BEGIN
SET #ColumnName = (SELECT ColumnName FROM #ColumnsType WHERE ColumnId = #i)
-- SET #DataType = (SELECT DataType FROM #ColumnsType WHERE ColumnId = #i)
SELECT #vQuery =
'SELECT
MIN(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_MinValue
, MAX(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_MaxValue
, AVG(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName + '_AvgValue
, STDEV(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_StandardDeviation
, SUM(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_TotalSum
FROM tempdb..#random' -- +#Schema+'.'+#Table+ ''
EXEC sp_executesql #vQuery
PRINT #vQuery
SET #i = #i + 1
END
For the sake of demonstration I create temp table with random values. I perform profiling on part of the columns which are consisting only of numeric values. To filter the columns I get their names and filter by type, using Tempdb.Sys.Columns. In normal case with my original data, I use INFORMATION_SCHEMA.COLUMNS but I think this is not that important.
The query returns the following:
The result is presented on two rows. What I'd like to do is to have this result on one row. The idea is to pivot the one row result after and to receive the following result:
As I mentioned, you need to not use a loop, use a set based method and UNION ALL your dynamic statements. I assume here, as well, that you are using a recent version of SQL Server. If not, you'll need to replace STRING_AGG with the old FOR XML PATH (and STUFF) method.
This should be enough to get you started:
USE Sandbox;
GO
CREATE TABLE dbo.YourTable (Col1 int,
Col2 varchar(10));
GO
DECLARE #SchemaName sysname = N'dbo',
#TableName sysname = N'YourTable';
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE #Delimiter nvarchar(50) = #CRLF + N'UNION ALL' + #CRLF;
SELECT #SQL = STRING_AGG(CONVERT(nvarchar(MAX),N'SELECT MIN(') + QUOTENAME(c.[name]) + N') AS ' + QUOTENAME(c.[name] + N'_MIN') + N',' + #CRLF +
N' MAX(' + QUOTENAME(c.[name]) + N') AS ' + QUOTENAME(c.[name] + N'_MAX') + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
,#Delimiter) WITHIN GROUP (ORDER BY c.column_id)
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE s.[name] = #SchemaName
AND t.[name] = #TableName
PRINT #SQL;
EXEC sys.sp_executesql #SQL;
GO
DROP TABLE dbo.YourTable;
Here is a very clean and nice solution which is what I was looking for:
I choose the columns, from specific table, scan it once and perform multiple calculations on it
I use dynamic query and make it one statement for all columns.
It works really fast. It took a little over 5 minutes to return result for table with 50 mil rows.
The only thing that is left to do is to UNPIVOT in order to insert results in a table that I want.
DECLARE
#q1 NVARCHAR(MAX)
, #q2 NVARCHAR(MAX)
, #q3 NVARCHAR(500)
, #schema VARCHAR(50) = '' -- choose schema
, #table VARCHAR(200) = '' -- choose table
SET #Q1 = 'SELECT ' + '''' + #table + '''' + ' as tableName, '
SET #Q3 = ' FROM ' + #schema + '.' + #table
SELECT #q2 = COALESCE(#q2 + ', ', '')
+ ' max(' + columnName + ') as ' + columnName + '_max, '
+ ' min(' + columnName + ') as ' + columnName + '_min, '
+ ' avg(' + columnName + ') as ' + columnName + '_avg, '
+ ' stdev(' + columnName + ') as ' + columnName + '_stdev, '
+ ' sum(' + columnName + ') as ' + columnName + '_sum '
FROM (
SELECT s.[name] as schemaName, t.[name] as tableName, c.[name] as columnName, st.[name] as typeName
FROM sys.schemas s
INNER JOIN sys.tables t ON s.schema_id = t.schema_id
INNER JOIN sys.columns c ON t.object_id = c.object_id
INNER JOIN sys.types st ON st.user_type_id = c.user_type_id
WHERE 1=1
AND s.[name] = #schema
AND t.[name] = #table
AND st.[name] IN ('') -- choose columns of specific data type, that you want to profile
) data
SELECT #q1 = #q1 + #q2 + #q3
EXEC sys.sp_executesql #Q1
Enjoy!

SQL: PIVOT return all NULL values

Hello everyone and thanks in advance.
I have two tables that I need to merge using Pivot.
1)Table with list of users:
#Tbl_staff(UserId INT,
Name NVARCHAR(MAX),
Surname NVARCHAR(MAX),
Level NVARCHAR (MAX)
)
2) Table with users' actions:
#Tbl_acts(Day DATE,
UserIdfk INT,
WorkedHours NVARCHAR(MAX),
Absence NVARCHAR(MAX),
Festivity NVARCHAR(2)
)
Table 2 has a complete month for each ID and for each day the action recorded. (Hours worked, hours of absence and type of absence, if the day is a holiday)..
It can also be null if in the db there is no action for that person on that day.
I wrote my Pivot like this:
DECLARE #columns NVARCHAR(MAX), #sql NVARCHAR(MAX);
SET #columns = N'';
SELECT #columns += N', p.' + QUOTENAME((cast(USERID AS VARCHAR(MAX)) + ' ' + NAME+ ' ' + SURNAME+' ' +(ISNULL (LEVEL,''))))
FROM (SELECT p.USERID,p.NAME,p.SURNAME,p.LEVEL FROM #TBL_STAFF AS p
INNER JOIN #TBL_ACTS AS o
ON p.USERID = o.USERIDFK
GROUP BY p.USERID,P.NAME,P.SURNAME,P.LEVEL) AS x;
SET #sql = N'
SELECT DAY,' + STUFF(#columns, 1, 2, '') + '
FROM
(
SELECT DISTINCT o.DAY,IDU = CAST(p.USERID AS NVARCHAR(MAX)), o.WORKEDHOURS
FROM #TBL_STAFF AS p, #TBL_ACTS AS o
where p.USERID= o.USERIDFK
) AS j
PIVOT
(
MAX(WORKEDHOURS) FOR IDU IN ('
+ STUFF(REPLACE(#columns, ', p.[', ',['), 1, 1, '')
+ ')
) AS p ORDER BY DAY;';
PRINT #sql;
EXEC sp_executesql #sql;
(Trying to show only the worked hours ... would be a good start)
It's the result: Column 'day' is ok(31 rows) and 'staff' too but it shows nothing inside it.

Inserting Different Value IN a Table

Well I'm Creating a Table Based on Result Set Of Databases On Server which contain Multiple Databases.
My table will Create a list of column which contain Database_Name
FOR EG:
DECLARE #strt INT,#End INT,#Database NVARCHAR(20), #ColumnDeclaration VARCHAR(2000),#SqlSelect NVARCHAR(MAX),#column_Name NVARCHAR(255)
SELECT * INTO #T FROM SYS.DATABASES
ORDER BY NAME
SELECT #ColumnDeclaration=STUFF(( SELECT ', ' + Name + ' NVARCHAR(255)'
FROM #T
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)'), 1, 1, '')
SET #SqlSelect=' CREATE TABLE Temp_Comp (' + #ColumnDeclaration + ');'
PRINT #SqlSelect
EXEC (#SqlSelect)
SELECT * FROM Temp_Comp
I wan't Insert the Data INTO Temp_Comp Table a specific Value from
the database whose Name will be in Temp_comp Table
Not sure what are you trying to achieve, but I assume that you want something like:
DECLARE #strt INT,#End INT,#Database NVARCHAR(20), #ColumnDeclaration NVARCHAR(MAX),#SqlSelect NVARCHAR(MAX),#column_Name NVARCHAR(255)
SELECT * INTO #T
FROM SYS.DATABASES
ORDER BY NAME;
SELECT #ColumnDeclaration=STUFF(( SELECT ', ' + QUOTENAME(Name) + ' NVARCHAR(255)'
FROM #T
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)'), 1, 1, '');
SET #SqlSelect=' CREATE TABLE Temp_Comp (col_desc NVARCHAR(200), ' + #ColumnDeclaration + ');';
SELECT #SqlSelect;
EXEC (#SqlSelect);
SET #SQLSelect = ' INSERT INTO Temp_Comp(col_desc, ' + REPLACE(#ColumnDeclaration, 'NVARCHAR(255)', '') + ')' +
' SELECT col,'+ REPLACE(#ColumnDeclaration, 'NVARCHAR(255)', '') +' FROM (' +
' SELECT name, sub.* FROM sys.databases' +
' OUTER APPLY (SELECT ''database_id'', CAST(database_id AS NVARCHAR(MAX)) UNION ALL SELECT ''compatibility_level'', CAST(compatibility_level AS NVARCHAR(MAX)) ) sub(col, val)) src' +
' PIVOT (MAX(val) FOR name IN ('+ REPLACE(#ColumnDeclaration, 'NVARCHAR(255)', '') +') ) piv';
SELECT #SQLSelect;
EXEC (#SQLSelect);
DBFiddle
Result:
In order to INSERT you could use dynamic PIVOT. To add more values just extend OUTER APPLY part.
Keep in mind that you've defined columns as ' NVARCHAR(255)' so you may need to convert values before insert.

T-SQL Pivot - Total Row and Dynamic Columns

Let's jump straight into it. Here's the code
SELECT [prov], [201304], [201305], [201306], [201307]
FROM (
SELECT [prov], [arrival], [Amount]
FROM [tblSource]) up
PIVOT (SUM([Amount]) FOR [arrival] IN ([201304], [201305], [201306], [201307])) AS pvt
GO
It brings me back an ever so lovely table. I was wondering how I would get the totals for each "date" column to show in an appended last row?
In addition, the underlying table will have more data added, specifically more dates. This means that 201308 will be added next, then 201309 etc
This will mean that currently I will have to amend the code above each month to reflect the addition. Is there anyway around this?
You can dynamically create the columns using dynamic SQL, however, I would really recommend handling dynamic pivots in a layer designed for it, such as SSRS or excel.
DECLARE #SQL NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '',
#SQL3 NVARCHAR(MAX) = '';
-- COMPILE THE UNIQUE VALUES FOR ARRIVAL THAT NEED TO BE PIVOTED
SELECT #SQL = #SQL + ',' + QUOTENAME(Arrival),
#SQL2 = #SQL2 + '+ISNULL(' + QUOTENAME(Arrival) + ', 0)',
#SQL3 = #SQL3 + ',' + QUOTENAME(Arrival) + ' = ISNULL(' + QUOTENAME(Arrival) + ', 0)'
FROM (SELECT DISTINCT Arrival FROM tblSource) s;
-- COMBINE THEM INTO A SINGLE QUERY
SET #SQL = 'SELECT [Prov]' + #SQL3 + ', [Total] = ' + STUFF(#SQL2, 1, 1, '') + '
FROM ( SELECT Arrival, Prov, Amount
FROM [tblSource]
UNION ALL
SELECT Arrival, ''Total'', SUM(Amount)
FROM [tblSource]
GROUP BY Arrival
) up
PIVOT
( SUM(Amount)
FOR Arrival IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt;';
-- EXECUTE THE QUERY
EXECUTE SP_EXECUTESQL #SQL;
This creates and executes the following SQL:
SELECT [Prov],
[2013-01-01] = ISNULL([2013-01-01], 0),
[2013-02-01] = ISNULL([2013-02-01], 0),
[Total] = ISNULL([2013-01-01], 0) + ISNULL([2013-02-01], 0)
FROM ( SELECT Arrival, Prov, Amount
FROM [tblSource]
UNION ALL
SELECT Arrival, 'Total', SUM(Amount)
FROM [tblSource]
GROUP BY Arrival
) up
PIVOT
( SUM(Amount)
FOR Arrival IN ([2013-01-01],[2013-02-01])
) pvt;
It is the query below union in the subquery up that adds the total row at the bottom, and the row total is simply created by adding all the columns in the row.
Example on SQL Fiddle
I will stress again though, I really recommend handling manipulation of data like this outside of SQL.
EDIT
An alternative to using the UNION to get the the total row is to use GROUPING SETS as follows:
DECLARE #SQL NVARCHAR(MAX) = '',
#SQL2 NVARCHAR(MAX) = '',
#SQL3 NVARCHAR(MAX) = '';
-- COMPILE THE UNIQUE VALUES FOR ARRIVAL THAT NEED TO BE PIVOTED
SELECT #SQL = #SQL + ',' + QUOTENAME(Arrival),
#SQL2 = #SQL2 + '+ISNULL(' + QUOTENAME(Arrival) + ', 0)',
#SQL3 = #SQL3 + ',' + QUOTENAME(Arrival) + ' = ISNULL(' + QUOTENAME(Arrival) + ', 0)'
FROM (SELECT DISTINCT Arrival FROM tblSource) s;
-- COMBINE THEM INTO A SINGLE QUERY
SET #SQL = 'SELECT [Prov]' + #SQL3 + ', [Total] = ' + STUFF(#SQL2, 1, 1, '') + '
FROM ( SELECT Arrival, Prov = ISNULL(Prov, 'Total'), Amount = SUM(Amount)
FROM [tblSource]
GROUP BY GROUPING SETS((Prov, arrival), (arrival))
) up
PIVOT
( SUM(Amount)
FOR Arrival IN (' + STUFF(#SQL, 1, 1, '') + ')
) pvt;';
-- EXECUTE THE QUERY
EXECUTE SP_EXECUTESQL #SQL;
SAMPLE TABLE
CREATE TABLE #TEMP([prov] VARCHAR(100),[arrival] INT, AMOUNT NUMERIC(12,2))
INSERT INTO #TEMP
SELECT 'A' [prov],'201304' [arrival],100 AMOUNT
UNION ALL
SELECT 'A' ,'201305' ,124
UNION ALL
SELECT 'A' ,'201306' ,156
UNION ALL
SELECT 'B' ,'201304' ,67
UNION ALL
SELECT 'B' ,'201305' ,211
UNION ALL
SELECT 'B' ,'201306' ,176
UNION ALL
SELECT 'C' ,'201304' ,43
UNION ALL
SELECT 'C' ,'201305' ,56
UNION ALL
SELECT 'C' ,'201306' ,158
QUERY
You can use ROLLUP to get the row total. More about ROLLUP here
-- Get the columns for dynamic pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + CAST([arrival] AS VARCHAR(50)) + ']',
'[' + CAST([arrival] AS VARCHAR(50)) + ']')
FROM (SELECT DISTINCT [arrival] FROM #TEMP) PV
ORDER BY [arrival]
-- Replace NULL value with zero
DECLARE #NulltoZeroCols NVARCHAR (MAX)
SELECT #NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+[arrival]+'],0) AS ['+[arrival]+']'
FROM (SELECT DISTINCT CAST([arrival] AS VARCHAR(50)) [arrival] FROM #TEMP)TAB
ORDER BY CAST([arrival]AS INT) FOR XML PATH('')),2,8000)
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT [prov],' + #NullToZeroCols + ' FROM
(
SELECT
ISNULL([prov],''Total'')[prov],
SUM(AMOUNT)AMOUNT ,
ISNULL(CAST([arrival] AS VARCHAR(50)),''Total'')[arrival]
FROM #TEMP
GROUP BY [arrival],[prov]
WITH ROLLUP
) x
PIVOT
(
MIN(AMOUNT)
FOR [arrival] IN (' + #cols + ')
) p
ORDER BY CASE WHEN ([prov]=''Total'') THEN 1 ELSE 0 END,[prov]'
EXEC SP_EXECUTESQL #query
Note : If you do not want to replace NULL with zero, just replace #NullToZeroCols with #cols in outer query of dynamic pivot

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