Using dynamic sql in a procedure to insert data into schema tables - sql-server

I hope that you are all doing well.
I've been working on a project where I need to store data about my college that includes ID numbers, names, contact details etc.
I'm having a bit of difficulty in creating a stored procedure that will be able to insert data into a specified schema.table_name. The procedure must be able to allow the EXEC command to specify which schema you would like the insert data into. The table_name will stay the same for all the 14 schemas. The following code sample is what I have come up with but it doesn't seem to work:
CREATE PROCEDURE AddStudent_proc(#campus varchar(50), #StudentID numeric(4,0), #Name varchar(50), #Surname varchar(50), #ID_numeric numeric(13,0), #Address varchar(100))
AS
BEGIN
DECLARE #dynamic varchar(MAX)
SET #dynamic = 'INSERT INTO ['+quotename(#campus)+'].Student_tbl(
StudentID,
Name,
Surname,
ID_numeric,
Address
)
VALUES('+quotename(#StudentID)+','+quotename(#Name)+','+quotename(#Surname)+','+quotename(#ID_numeric)+','+quotename(#Address)+');'
EXEC (#dynamic);
END
GO
My entire structure can be found
here
I'd appreciate any help on this topic as I am still quite new to SQL as a whole.
Thanks in advance.

You don't need to use quotename for data - as the name of the function implies, it should be used with names (A.K.A identifiers).
Also, when you are using quotename it addeds [ and ] around the value it receives, so no point of adding them again (['+quotename(#campus)+'] in your code).
I would recommend three improvements to the procedure you have now:
Change the data type of #campus to sysname - this is a special data type synonym to nvarchar(128) not null used by SQL Server for all identifiers.
white-list the schema name.
This is a critical change to protect against SQL Injection attacks.
Anything that can't be parameterized needs to be white-listed.
use sp_ExecuteSql instead of EXEC
This will result in a better stored procedure because it eliminates the threat of SQL Injection.
I've written a couple of blog posts that adds some more information and background on this subject: The do’s and don’ts of dynamic SQL for SQL Server and Back to basics: SQL Injection.
Anyway, here's how I would write this procedure:
CREATE PROCEDURE AddStudent_proc(
#campus sysname,
#StudentID numeric(4,0),
#Name varchar(50),
#Surname varchar(50),
#ID_numeric numeric(13,0),
#Address varchar(100)
)
AS
BEGIN
IF EXISTS(
SELECT 1
FROM Sys.Schemas
WHERE name = #campus
)
BEGIN
DECLARE #dynamic nvarchar(4000),
#paramDefinition nvarchar(4000)
SELECT #dynamic = N'INSERT INTO '+ quotename(#campus) + N'.Student_tbl (
StudentID,
Name,
Surname,
ID_numeric,
Address
)
VALUES(#StudentID, #Name, #Surname, #ID_numeric, #Address)',
#paramDefinition =
N'#StudentID numeric(4,0),
#Name varchar(50),
#Surname varchar(50),
#ID_numeric numeric(13,0),
#Address varchar(100)'
EXEC sp_executeSql #dynamic, #paramDefinition, #StudentID, #Name, #Surname, #ID_numeric, #Address;
END
END
GO

Related

How to pass schema name as parameter in SQL Server stored procedure?

As I have seen so far, people suggested using dynamic SQL.
For example:
How to pass schema as parameter to a stored procedure in sql server?
How to pass schema name as parameter in stored procedure
However, dynamic SQL has the risk of SQL injection. Hence, I want to know if there are any other safe alternatives?
Basically, this stored procedure that I am creating will be called at runtime. There will be 2 possible schemas to be passed in. And the table name will be passed in as well.
Something like below: (It does not work)
CREATE PROCEDURE [EFM].[usp_readApexTable]
#SCHEMANAME VARCHAR(20) = NULL,
#TABLENAME VARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM [#SCHEMANAME].[#TABLENAME];
END
GO
This is just an example of READ action. My plan is to create for CRUD, which requires 4 different stored procedures.
You can use QUOTENAME to avoid any SQL injection and build your dynamic query like the following:
CREATE PROCEDURE [EFM].[usp_readApexTable]
#SCHEMANAME VARCHAR(20) = NULL,
#TABLENAME VARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL VARCHAR(MAX)=N'SELECT * FROM '
+ QUOTENAME(#SCHEMANAME) + '.' + QUOTENAME(#TABLENAME)
EXEC (#SQL)
END
GO
Note: If you have any plan to add parameters also for your WHERE clause, in that case QUOTENAME will not help much, I suggest to to use sp_executesql by passing appropriate parameters used in WHERE clause.
Still you need to use QUOTENAME for schema and table name as SQL excepts it only as literal, you can't use variable names for table and schema.
For example.
declare #sql nvarchar(max)
set #sql = N'select * from ' + quotename(#SCHEMANAME ) + '.' + quotename(#TABLENAME )
+ ' where (City = #City)'
exec sp_executesql
#sql,
N'#City nvarchar(50)',
#City
You can find more details here
You need to use dynamic sql to do this operation
CREATE PROCEDURE [EFM].[usp_readApexTable]
#SCHEMANAME VARCHAR(20) = NULL,
#TABLENAME VARCHAR(100) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sqlCommand nvarchar(MAX)
SET #sqlCommand='SELECT * FROM ['+#SCHEMANAME+'].['+#TABLENAME+'];'
--Create Your Temp Table where you can set the records after executing the dynamic query
CREATE TABLE #tmpTbl(
Column1 [datatype]
Column2 [datatype]
.
.
ColumnN
)
INSERT INTO #tmpTbl EXEC sp_executesql #sqlCommand --Copy data to #tmpTbl table
SELECT * FROM #tmpTbl
DROP TABLE #tmpTbl
END
GO

How to use table type in T-SQL dynamically

I want to create a stored procedure with dynamic parameters. One of these parameters is a table type
CREATE TYPE [dbo].[IdTable] AS TABLE ([Id] [int] NULL)
GO
CREATE PROCEDURE [dbo].[SP_deleteCells]
#table IdTable READONLY,
#tableName NVARCHAR(50),
#fieldName NVARCHAR(50),
#result BIT OUTPUT
AS
DECLARE #SQL NVARCHAR(500);
SET #SQL='delete from TBL_CustomerTerminal where ID in (select ID from #table)'
EXEC (#SQL);
SET #result = ##ROWCOUNT;
How can I exec this code without errors?? Right now, I get:
Must declare the table variable "#table"
Use sp_executesql
exec sp_executesql N'delete from TBL_CustomerTerminal where ID in (select ID from #table)'
, N'#table dbo.IdTable readonly' /* parameter declaration for sp_executesql */
, #table /* pass the parameters */
It doesn't seem that you need dynamic SQL for the query above. But I assume it is just a sample.
The dynamic SQL query has it's own code visibility. It can't see any variable outside of the locally defined variables. If you want to pass parameters to your query you need to use sp_executesql instead of EXEC.
https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql

How to SELECT * into a SQL table incremntally by date?

I have a SQL Server table called "tblProducts".
Sometimes I backup this table by making a copy of it with this simple query:
SELECT *
INTO [test01].[dbo].[tblProducts_20141206]
FROM [test01].[dbo].[tblProducts]
Every time when making a backup, the date is included in the table name.
I would like to create a SQL Job that runs this kind of query once every week.
Is it possible to maybe in a stored procedure or declaring a variable to achieve this that allows the backed-up table name to be named like [tblProducts_todaysDate]?
Thanks.
If you are using a SP, you can do something like:
CREATE PROC sp_createATable
#name VARCHAR(20) AS
CREATE TABLE #name
...
do your insert
Or, if you want to, w/o SP:
DECLARE #name varchar(20)
SET #name = 'tblName' + SELECT CONVERT(VARCHAR(8), GETDATE(), 112) AS [YYYYMMDD]
CREATE TABLE #name
...
do your insert
You need Dynamic SQL to create the tables names appended with date.
CREATE PROC usp_createtable( #tablename VARCHAR(20),
#Dbname VARCHAR(20),
#SchemaName VARCHAR(20))
AS
BEGIN
DECLARE #sql NVARCHAR(max)
SET #sql =' SELECT * INTO '+#Dbname+'.'+#SchemaName+'.'+#tablename+'CONVERT(VARCHAR(8), GETDATE(), 112) FROM '+#Dbname+'.'+#SchemaName+'.'+#tablename''
EXEC sp_executesql
#sql
END

SQL Server Select-Into stored procedure

I am trying to write a custom stored procedure to carry out a select into operation. I want to copy a table (or some columns from a table) from one database to another. I am using SQL Server 2012
CREATE Procedure select_into
AS
Begin
#selection varchar(128),
#newtabname varchar(128)
#fromtabname varchar(128)
Select selection,
INTO table1,
FROM table2,
WHERE selection = #selection AND table1 = #newtabname AND table2 =#fromtabname;
go
EXEC select_into, Ecode, relational_db.dbo.work, dbo.Work_Data;
I get an error message indicating a syntax error near the "." in relational_db.dbo.work.
I would appreciate any help in getting this right
You have a missing comma in parameter list and wrong syntax for procedure declaration. It should be::
CREATE Procedure select_into
(
#selection varchar(128),
#newtabname varchar(128),
#fromtabname varchar(128)
)
AS
Begin
BUT, in addition your syntax for an INSERT INTO contains extra commas and you cannot perform dynamic T-SQL that way.
Can I suggest you first learn TSQL's syntax for SQL Server.
Try something like this ...
CREATE Procedure select_into
#selection NVARCHAR(128),
#newtabname NVARCHAR(128),
#fromtabname NVARCHAR(128)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'Select ' + QUOTENAME(#selection) +
N' INTO ' + QUOTENAME(#newtabname) +
N' FROM ' + QUOTENAME(#fromtabname)
EXECUTE sp_executesql #sql
END

Generic Insert stored proc : Runtime error

The following code generates the primaey key for the new record to be inserted and inserts the record into a table, whose name and the values to be inserted are given as parameters to the stored procedure. I am getting a runtime error. I am using Visual Studio 2005 to work with SQL Server 2005 Express Edition
ALTER PROCEDURE spGenericInsert
(
#insValueStr nvarchar(300),
#tblName nvarchar(10)
)
AS
DECLARE #sql nvarchar(400)
DECLARE #params nvarchar(200)
DECLARE #insPrimaryKey nvarchar(10)
DECLARE #rowCountVal integer
DECLARE #prefix nvarchar(5)
--following gets the rowcount of the table--
SELECT #rowCountVal = ISNULL(SUM(spart.rows), 0)
FROM sys.partitions spart
WHERE spart.object_id = object_id(#tblName) AND spart.index_id < 2
SET #rowCountVal = #rowCountVal+1
--Following Creates the Primary Key--
IF #tblName = 'DEFECT_LOG'
SET #prefix='DEF_'
ELSE IF #tblName='INV_Allocation_DB'
SET #prefix='INV_'
ELSE IF #tblName='REQ_Master_DB'
SET #prefix='REQ_'
ELSE IF #tblName='SW_Master_DB'
SET #prefix='SWI_'
ELSE IF #tblName='HW_Master_DB'
SET #prefix='HWI_'
SET #insPrimaryKey= #prefix + RIGHT(replicate('0',5)+ convert(varchar(5),#rowCountVal),5) -- returns somethin like 'DEF_00005'
-- Following is for inserting into the table --
SELECT #sql = N' INSERT INTO #tableName VALUES ' +
N' ( #PrimaryKey , #ValueStr )'
SELECT #params = N'#tableName nvarchar(10), ' +
N'#PrimaryKey nvarchar(10), ' +
N'#ValueStr nvarchar(300)'
EXEC sp_executesql #sql, #params, #tableName=#tblName, #PrimaryKey=#insPrimaryKey, #ValueStr=#insValueStr
Output Message:
Running [dbo].[spGenericInsert] ( #insValueStr = 2,"Hi",1/1/1987, #tblName = DEFECT_LOG ).
Must declare the table variable "#tableName".
No rows affected.
(0 row(s) returned)
#RETURN_VALUE = 0
Finished running [dbo].[spGenericInsert].
You are going to have to concatenate the table name directly into the string, as this cannot be parameterized:
SELECT #sql = N' INSERT INTO [' + #tblName + '] VALUES ' +
N' ( #PrimaryKey , #ValueStr )'
SELECT #params = N'#PrimaryKey nvarchar(10), ' +
N'#ValueStr nvarchar(300)'
To prevent injection attacks, you should white-list this table name. This also isn't robust if the table has other non-nullable columns, etc.
note: Personally, though, I don't think this is a good use of TSQL; it might be more appropriate to construct the command in the client (C# or whatever), and execute it as a parameterized command. There are use-cases for dynamic-SQL, but I'm not sure this is a good example of one.
Better yet, use your preferred ORM tool (LINQ-to-SQL, NHibernate, LLBLGen, Entity Framework, etc) to do all this for you, and concentrate on your actual problem domain.
White list essentially means make sure that the table being passed in is a valid table that you want them to be able to insert into. Let's just say for arguments sake that table name is user provided, the user could then start inserting records into system tables.
You can do a white list check by bouncing the table name of the sysobjects table:
select * from sysobjects where name=#tblname and xType='U'
However as Marc suggested this is not a good use of TSQL, and your better off handling this in the app tier as a paramatized query.
Agree with Marc- overall this is an extremely poor idea. Generic inserts/updates or deletes cause problems for the database eventually.
Another point is that this process will have problems when two users run simulutaneously against the same table as they will try to insert the same Primary Key.

Resources