Question :
Is it possible to have an SP that looks something like this:
DECLARE @tableName = null;
DECLARE @cols = null;
DECLARE @values = null;
INSERT INTO @tableName @cols @values;
The resultant query looks like this:
INSERT INTO <table name> (<set of column names>) VALUES (<set of corresponding values>);
My use case is that I’m parsing Excel data into a database, i.e. a single workbook of ‘type X’ equates to a single row in this database. These workbooks are filled in from a template, so the data format is pretty constant. To parse the data I create a sheet in the workbook called ‘DATA‘ or something, that looks like this:
tableName | attributeName | attributeValue | .. plus some config stuff
People | Firstname | Johnny | etc
People | Lastname | Heinz | etc
People | Firstname | Sam | etc
People | Lastname | Smith | etc
This would equate to two rows in the People
table of the database (multiple rows are recognized by repeating attributeNames
for a specific table).
The reasons this needs to be dynamic SQL is:
- I’d like this to be applicable to any table in the database
- I won’t know how many cols a particular table has before parsing
Answer :
- You can’t parameterize table names or column names – you’ll have to use dynamic SQL.
- You should also be passing the table’s schema.
-
You should be validating table names and column names. Table names are easy to check, but for column names we’ll use this function:
CREATE FUNCTION dbo.ParseColumnList ( @List NVARCHAR(MAX) ) RETURNS TABLE AS RETURN ( SELECT i, c = LTRIM(CONVERT(SYSNAME, SUBSTRING(@List, i, CHARINDEX(',', @List + ',', i) - i))) FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_columns) AS n(i) WHERE i <= LEN(@List) AND SUBSTRING(',' + @List, i, 1) = ',' );
-
You should be using strongly-typed parameters for parameter values, to protect yourself from SQL injection vulnerabilities. Understanding where these parameter values come from will help to further refine this solution, but for now you’ll just have to blindly append your values list and hope for the best:
CREATE PROCEDURE dbo.DealersChoice @TableSchema sysname, @TableName sysname, @ColumnList nvarchar(max), @ParamValues nvarchar(max) AS BEGIN SET NOCOUNT ON; IF NOT EXISTS ( -- make sure table exists: SELECT 1 FROM sys.tables AS t INNER JOIN sys.schemas AS s ON t.[schema_id] = s.[schema_id] WHERE t.name = QUOTENAME(@TableName) AND s.name = QUOTENAME(@TableSchema) ) BEGIN -- raise an appropriate error here RETURN; END IF EXISTS ( -- make sure columns exist: SELECT 1 FROM dbo.ParseColumnList(@ColumnList) AS f LEFT OUTER JOIN sys.columns AS c ON f.c = c.name WHERE c.[object_id] = OBJECT_ID(QUOTENAME(@TableSchema) + N'.' + QUOTENAME(@TableName)) WHERE c.name IS NULL ) BEGIN -- raise appropriate error RETURN; END DECLARE @sql nvarchar(max) = N'INSERT ' + QUOTENAME(@TableSchema) + N'.' + QUOTENAME(@TableName) + N' (' + @ColumnList + N') VALUES(' + @ParamValues + N');'; EXEC sys.sp_executesql @sql; END
I am not sure how you are building the @ParamValues
parameter but if you have string delimiters embedded in it (like 5, 'foo', '20160206'
), you’ll need to double those up somewhere so that it becomes 5, ''foo'', ''20160206''
– this becomes a challenge when you have strings like O'Brien
– one of just many reasons why you should be using proper parameters instead of concatenating one big string with all your values.
See the following: