I'm trying to make a dynamic stored procedure for inserting records in tables where I pass in table name, schema name and insert values parameters.
I've made it to select column names from table parameter and store it in a variable, but I can't manage how to make insert statement work.
Please if anyone can help me.
Here is my query:
DECLARE #TableName VARCHAR(100)
DECLARE #SchemaName VARCHAR(10)
DECLARE #Columns NVARCHAR(max)
DECLARE #VALUES NVARCHAR(MAX)
SET #SchemaName = 'dbo'
SET #TableName = 'CLASSROOMS'
SET #Columns = '';
SET #VALUES = 'Classroom1,25'
SELECT #Columns = #Columns + CAST(COLUMN_NAME AS varchar(50)) + ','
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #TableName
AND TABLE_SCHEMA = #SchemaName
AND ORDINAL_POSITION > 1
SET #Columns = SUBSTRING(#Columns, 1, Len(#Columns) - 1)
--SELECT #Columns
DECLARE #SQL AS NVARCHAR(MAX)
SET #SQL=N'INSERT INTO ' + #TableName + '(' + #Columns + ') VALUES (' + #VALUES + ')'
EXEC sp_executesql #SQL
And here is Classroom table create script:
CREATE TABLE [dbo].[CLASSROOMS]
(
[ClassRoomID] [INT] IDENTITY(1,1) NOT NULL,
[ClassRoomName] [NVARCHAR](50) NOT NULL,
[MaxSits] [INT] NOT NULL,
CONSTRAINT [PK_CLASSROOMS]
PRIMARY KEY CLUSTERED ([ClassRoomID] ASC)
) ON [PRIMARY]
Thank you in advance
Try this:
SET #VALUES='''Classroom1'',25'
The [ClassRoomName] is a nvarchar field.
Related
I'm using an Output clause in my Insert statement which requires use of a Table Variable. I also want the Table name to be dynamic so I'm using dynamic SQL but it won't allow use of a Table Variable. I get the error Must declare the scalar variable "#InsertedId".
CREATE PROCEDURE sp_InsertPerson #Name varchar(50), #Table varchar(20) AS
DECLARE #InsertedId TABLE (Id int)
DECLARE #SQL nvarchar(200) = 'INSERT INTO ' + #Table + ' (Name) OUTPUT INSERTED.Id INTO ' + #InsertedId + ' VALUES (' + #Name + ')'
IF (#Name is not null AND #Name != '')
EXEC(#SQL)
SELECT Id FROM #InsertedId
How can I both use the Output clause and a dynamic Table name
First of all, do not use sp_ prefix to your stored procedure, cause it reserved to System stored procedures by MS, and can lead to performance issue and other problems (as it can be a habit). Use SysName datatype for the table name, and use QUOTENAME() function when you concatenate the string.
You need to declare your table in the DynamicSQL as
CREATE PROCEDURE InsertPerson
#Name varchar(50),
#Table SysName
AS
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = N'DECLARE #IDs TABLE (ID INT);'+
'INSERT INTO ' +
QUOTENAME(#Table) +
' (Name) OUTPUT INSERTED.ID INTO #IDs VALUES(#Name);'+
'SELECT * FROM #IDs';
EXECUTE sp_executesql #SQL,
N'#Name VARCHAR(50)',
#Name;
Demo
Try this;
CREATE PROCEDURE sp_InsertPerson #Name varchar(50), #Table varchar(20) AS
DECLARE #SQL nvarchar(200) = ''
SET #SQL = #SQL + 'DECLARE #InsertedId TABLE (Id int)';
SET #SQL = #SQL + 'INSERT INTO ' + #Table + ' (Name) OUTPUT INSERTED.Id INTO #InsertedId (Id) VALUES (''' + #Name + ''')'
SET #SQL = #SQL + 'SELECT Id FROM #InsertedId'
IF (#Name is not null AND #Name != '')
EXEC(#SQL)
Scenario: using an "Instead of Insert" trigger to a different table (overriding inserting into the current table) throws a truncation error.
Try to insert data into TableA
Instead Of Trigger is setup to run on TableA
This trigger instead inserts into TableB
TableA is not written to
Issue:
TableA has a nvarchar(10) desc column and TableB has a nvarchar(200) desc column. An insert trigger is setup on TableA where the data for the desc column is 50 characters long.
With SET ANSI_WARNINGS ON (the default), TableA causes a truncation error.
With SET ANSI_WARNINGS OFF (dangerous), truncation is ignored on TableB. So if the desc came in at 400 characters, it would be truncated to 200 characters with no errors.
Setup
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[results]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[causefailure] [nvarchar](5) NULL,
[type] [nvarchar](50) NOT NULL,
[description] [nvarchar](200) NULL,
[rundate] [datetime] NOT NULL DEFAULT (getdate())
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[test_table]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[description] [nvarchar](10) NULL,
[rundate] [datetime] NOT NULL DEFAULT (getdate())
) ON [PRIMARY]
GO
CREATE TRIGGER [dbo].[InsteadTrigger]
ON [dbo].[test_table]
INSTEAD OF INSERT
AS
SET NOCOUNT ON
SET ANSI_WARNINGS ON
BEGIN
INSERT INTO results([type], [description])
SELECT
(CASE SUBSTRING([description], 1, 1)
WHEN 'a'
THEN 'causes failure or truncation'
ELSE ''
END AS [causefailure],
'Instead Of Trigger' AS [type],
[description]
FROM inserted
END;
GO
ALTER TABLE [dbo].[test_table] ENABLE TRIGGER [InsteadTrigger]
GO
First attempt:
SET ANSI_WARNINGS OFF
INSERT INTO [dbo].[test_table]([description])
SELECT 'atest12345678910' AS [description]
Second attempt:
SET ANSI_WARNINGS ON
INSERT INTO [dbo].[test_table]([description])
SELECT 'btest12345678910' AS [description]
INSERT INTO [dbo].[test_table]([description])
SELECT 'atest12345678910' AS [description]
But when I run the the first code snippet, causefailure gets truncated. But there is no error. Running the second snippet inserts one record in [results]. But it throws an exception on the second.
Is there a way to ignore: constraints, conversion issues, etc., exceptions with the initial write to [test_table]? But have exceptions for any work done in the trigger (e.g. inserting into [results] and maybe even the actual work to [test_tables])?
Edit: I do not want to change the column size of nvarchar(10) desc
on TableA. The ultimate goal might be if the "instead of trigger"
fails to insert on TableA, it saves to TableB. Or it might even change
the size of desc on failure dynamically inside the trigger and retry
the insert.
Hopefully helpful!
Below is an incomplete idea how to work-around the above problem. It kind of evasive. But interesting way test how far you can rework a database.
The below changes the schema around when a table is created or altered. On creating or altering a table, it will be swapped out for an indexed view after the table gets renamed.
ALTER TRIGGER trigger_CreateTable
ON DATABASE
AFTER CREATE_TABLE, ALTER_TABLE
AS
BEGIN
--SELECT EVENTDATA()
DECLARE #Prefix AS nvarchar(256) = N'PleaseUseView_'
DECLARE #Event AS XML = EVENTDATA()
DECLARE #SchemaName AS nvarchar(255) = (#Event.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(255)'))
DECLARE #TableName AS nvarchar(255) = (#Event.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)'))
DECLARE #ObjectType AS nvarchar(255) = (#Event.value('(/EVENT_INSTANCE/ObjectType)[1]', 'NVARCHAR(255)'))
DECLARE #TableWithSchema AS nvarchar(512) = '[' + #SchemaName + '].[' + #TableName + ']'
CREATE TABLE #SchemaBindingDependencies
(
[id] int NOT NULL IDENTITY,
[schema] nvarchar(256) NOT NULL,
[name] nvarchar(256) NOT NULL
)
INSERT INTO #SchemaBindingDependencies([schema], [name])
SELECT DISTINCT s.name AS [schema], o.name
FROM sys.objects AS o
INNER JOIN sysdepends AS d
ON d.id = o.[object_id]
INNER JOIN sys.schemas AS s
ON s.[schema_id] = o.[schema_id]
WHERE o.type ='V' AND d.depid = OBJECT_ID(#TableWithSchema)
AND SUBSTRING(#TableName, LEN(#Prefix) + 1, 256) LIKE o.[name]
IF (EXISTS(SELECT 1 FROM #SchemaBindingDependencies))
BEGIN
DECLARE #Index AS int = (SELECT MAX(id) FROM #SchemaBindingDependencies)
WHILE (#Index > 0)
BEGIN
DECLARE #ViewName1 AS nvarchar(256) = (SELECT [name] FROM #SchemaBindingDependencies WHERE id = #Index)
IF (#ViewName1 IS NOT NULL)
BEGIN
DECLARE #SchemaName1 AS nvarchar(256) = (SELECT [schema] FROM #SchemaBindingDependencies WHERE id = #Index)
DECLARE #DropSchemaBoundViewQuery AS nvarchar(1000) = 'DROP VIEW [' + #SchemaName + '].[' + #ViewName1 + ']'
EXEC(#DropSchemaBoundViewQuery)
END
SET #Index = #Index - 1
END
END
IF (SUBSTRING(#TableName, 1, LEN(#Prefix)) <> #Prefix)
BEGIN
DECLARE #NewTableName AS nvarchar(512) = #Prefix + #TableName + ''
DECLARE #NewTableWithSchema AS nvarchar(512) = '[' + #SchemaName + '].[' + #NewTableName + ']'
EXEC sp_rename #TableWithSchema, #NewTableName
SET #TableName = #NewTableName
SET #TableWithSchema = '[' + #SchemaName + '].[' + #NewTableName + ']'
END
DECLARE #Columns AS nvarchar(max) = (STUFF((SELECT ',[' + x.[name] + ']' FROM (
SELECT c.[name]
FROM sys.columns AS c
INNER JOIN sys.tables AS t
ON t.[object_id] = c.[object_id]
INNER JOIN sys.schemas AS s
ON s.[schema_id] = t.[schema_id]
WHERE t.[name] = #TableName AND s.[name] = #SchemaName) AS x FOR XML PATH('')), 1, 1, ''))
DECLARE #ViewName AS nvarchar(256) = SUBSTRING(#TableName, LEN(#Prefix) + 1, 256)
DECLARE #ViewWithSchema AS nvarchar(512) = '[' + #SchemaName + '].[' + #ViewName + ']'
DECLARE #Query AS nvarchar(max) =
N'CREATE VIEW ' + #ViewWithSchema + N' ' + CHAR(10) + CHAR(13) +
N'WITH SCHEMABINDING ' + CHAR(10) + CHAR(13) +
N'AS ' + CHAR(10) + CHAR(13) +
N' SELECT ' + #Columns + ' ' + CHAR(10) + CHAR(13) +
N' FROM ' + #TableWithSchema + N' '
--SELECT #Query
EXEC(#Query)
SET #Query =
N'CREATE UNIQUE CLUSTERED INDEX [CIX_' + #ViewName + N'] ' + CHAR(10) + CHAR(13) +
N'ON ' + #ViewWithSchema + N'(' + #Columns + N')'
EXEC(#Query)
-- TODO: Use the below double commented to build a variable insert statement for the "Instead of TRIGGER"
--
----DECLARE #tv_source TABLE (id int)
----declare #XML xml;
----set #XML =
---- (
---- select top(0) *
---- from #tv_source
---- for XML RAW, ELEMENTS, XMLSCHEMA
---- );
----SELECT T.c.query('.'), T.c.value('#name', 'nvarchar(256)')
----FROM #XML.nodes('/*/*/*/*/*') AS T(c)
--
--SET #Query =
-- N'CREATE TRIGGER [Trigger_' + #ViewName + N'] ' + CHAR(10) + CHAR(13) +
-- N'ON ' + #ViewWithSchema + N' ' + CHAR(10) + CHAR(13) +
-- N'INSTEAD OF INSERT ' + CHAR(10) + CHAR(13) +
-- N'AS BEGIN ' + CHAR(10) + CHAR(13) +
-- N'BEGIN TRY ' + CHAR(10) + CHAR(13) +
-- N' INSERT INTO ' + #TableWithSchema + N'(' + #Columns + N')'
-- N' SELECT ' + #Columns +
--EXEC(#Query)
END
Ideally, you would use a different schema for tables. And use dbo or
the default for the view.
Once the Instead of TRIGGER is working, you could wrap a TRY/CATCH around it. On the catch, check schema for truncation. And expand the column sizes if needed.
Incomplete solution. But it is the answer I will stick with for now.
If anyone has any better answers or a complete solution, please add it!
tl;dr
One interesting thing is the below query
DECLARE #tv_source TABLE (id int)
declare #XML xml;
select top(0) *
from #tv_source
for XML RAW, ELEMENTS, XMLSCHEMA
You can return schema. SOAP for the above XML. Or JsonSchema or Avro if using the SQL 2016+ Json version to build Restful API's with schema awareness. And with schema awareness, an application gateway could auto-grab many dispersed Micro-Rest API's into one seemly large Rest API.
I am using a stored procedure below. For parameters called LockedByName and LockedByLoginName I want to pass either a string (which can contain spaces etc. - so I quote it with single quotes) or a NULL value.
The procedure works with strings but doesn't work with null value. I assume this is because it somehow silently fails because it cannot put the NULL value into quotes.
What could be the way to both keep the quotes and also keep the ability to use null value?
It similarly fails when the parameter is an empty string which would be fine to use too.
ALTER PROCEDURE [dbo].[ChangeLockFormStatus]
#FormID as int,
#LockedByName as nvarchar(50) = null,
#LockedByLoginName as nvarchar(50) = null,
#TableName as varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ActualTableName AS varchar(255)
SELECT #ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #TableName
DECLARE #sql AS NVARCHAR(MAX)
SELECT #sql = 'UPDATE ' + #ActualTableName
+ ' SET LockedByName=''' + #LockedByName + ''','
+ 'LockedByLoginName=''' + #LockedByLoginName
+ ''' WHERE [ID] = ' + CAST(#FormID AS VARCHAR(25)) + ';'
EXEC(#sql)
END
If you use sp_executesql, you can continue to use your parameters directly, rather than having to mangle them into the string. Something like:
ALTER PROCEDURE [dbo].[ChangeLockFormStatus]
#FormID as int,
#LockedByName as nvarchar(50) = null,
#LockedByLoginName as nvarchar(50) = null,
#TableName as varchar(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ActualTableName AS varchar(255)
SELECT #ActualTableName = QUOTENAME( TABLE_NAME )
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #TableName
DECLARE #sql AS NVARCHAR(MAX)
SELECT #sql = 'UPDATE ' + #ActualTableName
+ ' SET LockedByName=#LockedByName,'
+ 'LockedByLoginName=#LockedByLoginName'
+ ' WHERE [ID] = #FormID;'
EXEC sp_executesql #sql,
N'#LockedByName nvarchar(50),#LockedByLoginName nvarchar(50),#FormID int',
#LockedByName,#LockedByLoginName,#FormID
END
I am attempting to create a table in T-SQL using sp_executesql. The name of the database containing the table is dynamic.
DECLARE #ID int = 1031460
DECLARE #TableName nvarchar(max) = '[MyDatabase' + CAST(#ID as nvarchar(10)) + '].[dbo].[MyTable]'
DECLARE #CreateTable nvarchar(max) = N''
SELECT #CreateTable =
N'
CREATE TABLE #TableName
(
ID int
)
'
EXECUTE sp_executeSQL #CreateTable, N'#TableName nvarchar(max)', #TableName = #TableName
This script results in this error:
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near '#TableName'.
What is the best way to specify the name of the table to create dynamically based on parameters of sp_executeSQL?
You cannot pass Tablename as a parameter even we using sp_executesql procedure. You have to build you own dynamic sql query
SELECT #CreateTable =
N'
CREATE TABLE ' + #TableName + '
(
ID int
)
'
Exec sp_executesql #CreateTable
When you are passing the table name as a parameter to sp_executesql, it treats it as a literal string, to make it treat it as an object name try something like this......
DECLARE #ID int = 1031460
DECLARE #TableName nvarchar(max) = QUOTENAME('MyDatabase' + CAST(#ID as nvarchar(10)))
+ '.[dbo].[MyTable]'
DECLARE #CreateTable nvarchar(max);
SET #CreateTable = N' CREATE TABLE ' + #TableName
+ N' (
ID int
)'
Exec sp_executesql #CreateTable
EDIT
It is good practice to check if an object already exists before you try to create it.
You can check if the object already exists and drop it and then create the new one, you code should look something like.....
DECLARE #ID int = 1031460
DECLARE #TableName nvarchar(max) = QUOTENAME('MyDatabase' + CAST(#ID as nvarchar(10)))
+ '.[dbo].[MyTable]'
DECLARE #CreateTable nvarchar(max), #DropTable nvarchar(max);
-- Drop table if already exists
SET #DropTable = N' IF OBJECT_ID('''+ #TableName +''') IS NOT NULL '
+ N' DROP TABLE ' + #TableName
Exec sp_executesql #DropTable
SET #CreateTable = N' CREATE TABLE ' + #TableName
+ N' (
ID int
)'
Exec sp_executesql #CreateTable
How to create table from select statement?
For example, I have format table as below:
FormatID Label
1 ID
2 Name
3 DOB
So I want to create new table with column name ID, Name, DOB.
Any pointer would be appreciated.
You could try like this:
-- build the SQL query
declare #sql nvarchar(max) = ''
select #sql = #sql + '[' + Label + '] nvarchar(255), ' from Format order by FormatID
select #sql = 'create table [MyTable] (' + #sql + ')'
-- create the table
EXECUTE sp_executesql #sql
go
-- lets see if the table actually got created
sp_help MyTable
go
you can do ...
declare #sql as varchar( max) ='';
declare #cln as varchar( max) ='';
select #cln =( SELECT Label + ' nvarchar(50) , ' from format FOR XML PATH('')
);
set #sql = 'create table tablename ( '+ #cln + ' );
sp_executesql #sql ;
#sandeep rawat. Thanks, I modified some and i can now got it ;)
declare #sql as nvarchar(max) ='';
declare #cln as nvarchar(max) ='';
select #cln =(SELECT REPLACE(label, ' ', '') + ' nvarchar(50),' from format FOR XML PATH('') );
set #cln = substring(#cln,1,len(#cln)-1)
set #sql = 'create table new_table ('+ #cln + ')';
print #sql
print len(#sql)
exec sp_executesql #sql ;
This will help you
select FormatID,Label into New_table_Name from Table_Name where 1=0;
There was little misunderstanding.
Hope it will help you.
create table #Tbl_Format
(FormatId int identity(1,1),
Lebel Varchar(64)
)
insert into #Tbl_Format
values('Id'),('Name'),('DOB')
Declare #Query nvarchar(512)
SET #Query= (SELECT ', ' + Lebel+' NVARCHAR(64)'
FROM #Tbl_Format
FOR XML PATH(''))
SET #Query='CREATE TABLE Table_Name ('+Substring(#Query,2,LEN(#Query))+')'
EXEC (#Query)
Thanks