I have the following insert stored procedure:
CREATE Procedure dbo.APPL_ServerEnvironmentInsert
(
#ServerEnvironmentName varchar(50),
#ServerEnvironmentDescription varchar(1000),
#UserCreatedId uniqueidentifier,
#ServerEnvironmentId uniqueidentifier OUTPUT
)
WITH RECOMPILE
AS
-- Stores the ServerEnvironmentId.
DECLARE #APPL_ServerEnvironment TABLE (ServerEnvironmentId uniqueidentifier)
-- If #ServerEnvironmentId was not supplied.
IF (#ServerEnvironmentId IS NULL)
BEGIN
-- Insert the data into the table.
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId
)
OUTPUT Inserted.ServerEnvironmentId INTO #APPL_ServerEnvironment
VALUES
(
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId
)
-- Get the ServerEnvironmentId.
SELECT #ServerEnvironmentId = ServerEnvironmentId
FROM #APPL_ServerEnvironment
END
ELSE
BEGIN
-- Insert the data into the table.
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentId,
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId
)
VALUES
(
#ServerEnvironmentId,
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId
)
END
GO
I could have simplified the above as:
CREATE Procedure dbo.APPL_ServerEnvironmentInsert
(
#ServerEnvironmentName varchar(50),
#ServerEnvironmentDescription varchar(1000),
#UserCreatedId uniqueidentifier,
#ServerEnvironmentId uniqueidentifier OUTPUT
)
WITH RECOMPILE
AS
-- Ensure #ServerEnvironmentId IS NOT NULL
SELECT ISNULL(#ServerEnvironmentId, newid())
-- Insert the data into the table.
INSERT INTO APPL_ServerEnvironment WITH(TABLOCKX)
(
ServerEnvironmentId,
ServerEnvironmentName,
ServerEnvironmentDescription,
DateCreated,
UserCreatedId
)
VALUES
(
#ServerEnvironmentId,
#ServerEnvironmentName,
#ServerEnvironmentDescription,
GETDATE(),
#UserCreatedId
)
GO
But by doing so, i loose the performance improvements of the newsequentialid() over newid(). newsequentialid() can not be set in code as newid(), it can only be supplied as a default value on a table column level.
Any ideas anyone on simplifying the original query, but utilising newsequentialid()? Or, is the original query the most simplified solution in achieving this?
Since the newsequentialid() can only be used as a column default value, you could change your original query to:
insert just the #ServerEnvironmentId if no value had been supplied, thus generating a new sequential ID and retrieving it from the OUTPUT clause
then update that row defined by either the #ServerEnvironmentId passed in originally, or the new ID you just created by inserting a "dummy row" into your table
Not sure if that would be any faster / more efficient - you would have to do some measurements on that.
Yes. Consider giving the new merge statement a try. It should be 100% compatible with the column default of newsequentialid(), and it will get the SQL down to a single concise statement. I hope this helps.
My original idea was correct. It is the simplest and most readable solution possible.
Related
This is the sample procedure I am using.
create procedure PRO_ProcName
#description varchar(max),
#txn_no varchar
as
begin
declare #txn table (
id bigint,
description varchar(max),
txn_no varchar
);
declare #txn_id bigint;
insert into transactions
(
description,
txn_no
)
output
inserted.description,
inserted.txn_no
into #txn
values
(
#description,
#txn_no
)
select #txn_id = id from #txn;
end
I am getting an error like:
Column name or number of supplied values does not match table definition.
I know that it is because I have id field in my temporary table and it is not getting inserted in the insert into statement. I cannot give value for id because it is the auto increment primary key.
How can I tackle this situation and get id of inserted record into a variable?
The inserted table represents the data that exists in the target table after the insert - so it also includes the auto-generated values, whether they where generated by a default value definition or by an identity definition on the columns - so you need to add inserted.id to the output clause.
However, there are two more things wrong in your procedure.
The first and most important is the fact that you didn't specify a length to the #txn_no varchar parameter. SQL Server will implicitly specify the length of 1 char in this case.
The second is the fact that you are not specifying the columns list of #txn in the output clause.
Here is a improved version of your code with all these issues fixed:
create procedure PRO_ProcName
#description varchar(max),
#txn_no varchar(255) -- Note: I don't know the actual length you need
as
begin
declare #txn table (
id bigint,
description varchar(max),
txn_no varchar
);
declare #txn_id bigint;
insert into transactions
(
description,
txn_no
)
output
inserted.id,
inserted.description,
inserted.txn_no
into #txn(id, description, txn_no)
values
(
#description,
#txn_no
)
select #txn_id = id from #txn;
end
I cannot give value for id because it is the auto increment primary key.
No it isn't. You haven't declared it to be anything of the sort. So we need to fix that first:
declare #txn table (
id bigint IDENTITY PRIMARY KEY,
description varchar(max),
txn_no varchar
);
And then we fix it by specifying a column list in your INTO clause:
output
inserted.description,
inserted.txn_no
into #txn (description, txn_no)
It's always a good habit to specify column lists anyway.
Or if I've misinterpreted your question, and the id should be coming from transactions, then you just add inserted.id as another column in your OUTPUT clause. inserted represents that state of the table after the insert. So you can include columns from it in your OUTPUT clause even if you didn't specify them in the INSERT.
In my SQL Server 2012 environment, I've created a series of stored procedures that pass pre-existing temporary tables among themselves (I have tried different architectures here, but wasn't able to bypass this due to the nature of the requirements / procedures).
What I'm trying to do is to, within a stored procedure check if a temporary table has already been created and, if not, to create it.
My current SQL looks as follows:
IF OBJECT_ID('tempdb..#MyTable') IS NULL
CREATE TABLE #MyTable
(
Col1 INT,
Col2 VARCHAR(10)
...
);
But when I try and run it when the table already exists, I get the error message
There is already an object named '#MyTable' in the database
So it seems it doesn't simply ignore those lines within the If statement.
Is there a way to accomplish this - create a temp table if it doesn't already exist, otherwise, use the one already in memory?
Thanks!
UPDATE:
For whatever reason, following #RaduGheorghiu's suggestion from the comments, I found out that the system creates a temporary table with a name along the lines of dbo.#MyTable________________________________________________0000000001B1
Is that why I can't find it? Is there any way to change that? This is new to me....
Following the link here, http://weblogs.sqlteam.com/mladenp/archive/2008/08/21/SQL-Server-2005-temporary-tables-bug-feature-or-expected-behavior.aspx
It seems as though you need to use the GO statement.
You meant to use IS NOT NULL i think... this is commonly used to clear temp tables so you don't get the error you mentioned in your OP.
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable
CREATE TABLE #MyTable
(
Col1 INT,
Col2 VARCHAR(10)
);
The big difference is the DROP TABLE statement after you do your logical check. Also, creating your table without filling data doesn't make it NULL
DROP TABLE #MyTable
CREATE TABLE #MyTable
(
Col1 INT,
Col2 VARCHAR(10)
);
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL
SELECT 1
Try wrapping your actions in a begin...end block:
if object_id('tempdb..#MyTable') is null
begin
create table #MyTable (
Col1 int
, Col2 varchar(10)
);
end
This seems odd, but it works when I try it
IF(OBJECT_ID('tempdb..#Test') IS NULL) --check if it exists
BEGIN
IF(1 = 0)--this will never actually run, but it tricks the parser into allowing the CREATE to run
DROP TABLE #Test;
PRINT 'Create table';
CREATE TABLE #Test
(
ID INT NOT NULL PRIMARY KEY
);
END
IF(NOT EXISTS(SELECT 1 FROM #Test))
INSERT INTO #Test(ID)
VALUES(1);
SELECT *
FROM #Test;
--Try dropping the table and test again
--DROP TABLE #Test;
I have a table called dbo.mtestUnique with two column id and desc, I have a unique index on "desc" , two process inserting data to this table at a same time, how can I avoid inserting duplicate value and violating the unique index?
not exists and left join doesn't work.
to replicate this you can create a table on a test database:
CREATE TABLE mtestUnique
(
id INT ,
[DESC] varchar(50),
UNIQUE([DESC])
)
and then run the following script on two different queries on SSMS.
SET XACT_ABORT ON;
DECLARE #time VARCHAR(50)
WHILE (1=1)
BEGIN
IF OBJECT_ID('tempdb..#t') IS NOT NULL
DROP TABLE #t
SELECT #time = CAST(DATEPART(HOUR , GETDATE()) AS VARCHAR(10)) + ':' + RIGHT('00' +CAST(DATEPART(MINUTE , GETDATE())+1 AS VARCHAR(2)),2)
SELECT MAX(id) + 1 id , 'test' + #time [DESC]
INTO #t
FROM dbo.mtestUnique
-- to insert as exact same time
WAITFOR TIME #time
INSERT INTO dbo.mtestUnique
( id, [DESC] )
SELECT *
FROM #t t
WHERE NOT EXISTS (
SELECT 1
FROM dbo.mtestUnique u
WHERE u.[DESC] = t.[Desc]
)
END
I even put the insert in a TRAN but no luck.
thanks for your help in advance.
The only way to prevent a unique constraint violation is to not insert duplicate values for the column. If you have the unique constraint, it will throw an error when you try to insert a duplicate description, but it will not control what descriptions are attempted to be inserted.
With that said, if you only need a unique identifier I would highly recommend using the ID instead. Set it to an auto incriminating integer and do not insert in manually. Just provide the description and SQL Server will populate the ID for you avoiding duplicates.
I have an updateable view using an instead of trigger for insert/update. That trigger uses Merge. I'm finding that the Merge statement isn't applying the default constraints from the underlying physical table, although the merge documentation suggests it should.
The following example demonstrates:
create table tblTest
(
id uniqueidentifier not null primary key default newid(),
forename varchar(20),
surname varchar(20) not null default 'xxyyzz'
)
go
create view vwTest as select * from tblTest
go
create Trigger vwTest_trigger_insteadof_insert_update On vwTest
Instead of Insert, Update As
begin
set nocount on
Merge tblTest t
Using
inserted i On (t.id = i.id)
When Matched Then
Update
Set
t.forename = i.forename,
t.surname = i.surname
When Not Matched By Target Then
Insert
(
id,
forename,
surname
)
Values
(
i.id,
i.forename,
i.surname
)
OUTPUT $action, Inserted.*, Deleted.*
;
end
go
--Inserts to physical table work as expected
insert into tblTest (id) values (newid())
insert into tblTest (surname) values ('smith')
--Inserts into updateable view fail as no defaults are set
--from the underlying physical table
insert into vwTest (id) values (newid())
insert into vwTest (surname) values ('jones')
I see someone had something similar in Using default values in an INSTEAD OF INSERT trigger and solved it by copying the rows in inserted into a temporary and then altering the temp table to add in the default constraints from the physical table. I'm not sure I could tolerate the performance issues of these additional steps.
Simple enough. In order for a default value to be used you either have to use the DEFAULT keyword, or not include it in your insert. Even a NULL value counts. In this case you are specifying the value in the insert in your trigger. If you were to change that part of it from
When Not Matched By Target Then
Insert
(
id,
forename,
surname
)
Values
(
i.id,
i.forename,
i.surname
)
to
When Not Matched By Target Then
Insert
(
id,
forename,
surname
)
Values
(
i.id,
i.forename,
DEFAULT
)
You would see the default value for surname start to appear. Unfortunately that doesn't really help you much with what you are trying to do. The best solution I can think of (and it's not great) is to put your defaults into the trigger using isnulls.
When Not Matched By Target Then
Insert
(
id,
forename,
surname
)
Values
(
ISNULL(i.id,newid()),
i.forename,
ISNULL(i.surname,'xxyyzz')
)
I realize that isn't great from a maintenance point of view but it will work. I did a post on the DEFAULT keyword here with a fair amount of detail if you are interested.
Is there a way to prevent specifying explicit values in a column? The identity attribute prevents specifying explicit values (as long as IDENTITY_INSERT is off), which is the type of behavior I want.
create table testguid (
ID uniqueidentifier default newsequentialid() not null primary key
,somedate datetime
)
[constraint or trigger here?]
insert into testguid (somedate) values (getdate()) -- this is ok
insert into testguid (ID, somedate) values (newid(), getdate()) -- this is not ok
I want the database to insert values, but I want to prevent any way of specifying them.
You could use an INSTEAD OF TRIGGER to basically re-write the INSERT statement, leaving out the ID column.
CREATE TRIGGER dbo.testguid_beforeinsert
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.testguid(somedate --, other columns
) SELECT GETDATE() --, other columns
FROM inserted;
END
GO
Probably want something similar for UPDATE, too.