Getting the following error: (line 21 is declare statement).
Msg 544, Level 16, State 1, Procedure insert_employee_details, Line 21
Cannot insert explicit value for identity column in table 'DB_Actions' when IDENTITY_INSERT is set to OFF.
Here is what I have tried:
SET IDENTITY_INSERT DB_Actions ON;
But then I get this error:
Msg 8107, Level 16, State 1, Line 31
IDENTITY_INSERT is already ON for table 'StacysDB.dbo.Employee_Details'. Cannot perform SET operation for table 'DB_Actions'.
This doesn't makes to sense to me. First it doesn't work because identity insert is off. Then when I try to turn it on it says its already on.
I know this is a common error so I tried a confirmed solution by re-creating the table and setting identity insert to on before inserting the values, and then back to off and inserting the values, but i got the same error (Msg 8107).
Thanks for any help.
--1
--CREATE TABLE DB_Actions
--(
--Id numeric(5,0) IDENTITY(1,1) PRIMARY KEY,
--Table_Name varchar(20),
--Action_Name varchar(10),
--User_Name varchar(50),
--Done_ON datetime,
--Record_Id numeric(5,0)
--);
--2
--INSERT TRIGGER
--CREATE TRIGGER insert_employee_details
--ON Employee_Details
--FOR INSERT
--AS
--DECLARE #id int, #name varchar(20)
--SELECT #id = Emp_Id, #name = Emp_First_Name FROM inserted
--INSERT INTO DB_Actions(Id, Table_Name, Action_Name, User_Name,
--Done_ON, Record_Id)
--VALUES(#id,
-- 'Employee_Details',
-- 'INSERT',
-- #name,
-- getdate(),
-- #id
--);
INSERT INTO Employee_Details(Emp_Id, Emp_First_Name, Emp_Middle_Name, Emp_Last_Name, Emp_Address1, Emp_Address2, Emp_Country_Id, Emp_State_Id, Emp_City_Id, Emp_Zip, Emp_Mobile, Emp_Gender, Desig_Id, Emp_DOB, Emp_JoinDate, Emp_Active)
VALUES(9000, 'A', 'B', 'C', 'D', 'E', 2, 3, 4, 44444, 4444444, 1, 3333, getdate(), getdate(), 0);
IDENTITY_INSERT option is not meant to be used in your day-to-day processing. IDENTITY_INSERT is only meant as temporary workaround. For example, you need to restore values that were accidentally deleted. If you have to insert explicit values into identity field as part of your regular processing, you have to rethink your solution.
The easiest way to achieve this is to store explicit values in another field and leave identity to SQL server to manage. In your solution this would mean removing DB_Actions.ID field from the trigger insert statement (you are already storing ID in record_id).
Also, inserted table can return more than one record, so you should not try to store that in a single variable. Instead insert using a select statement. This will also make your code more readable.
So, change your trigger as follows to resolve the issue:
CREATE TRIGGER insert_employee_details
ON Employee_Details
FOR INSERT
AS
INSERT INTO DB_Actions(Table_Name, Action_Name, User_Name, Done_ON, Record_Id)
SELECT 'Employee_Details', 'INSERT', Emp_First_Name, getdate(), Emp_Id
FROM inserted;
If you are on SQL Server 2012 or later, alternative solution would be to use SEQUENCE object instead of identity. SEQUENCE gives you more flexibility and resolves a lot of issues with identity property.
Note also that User_Name field in DB_Actions is probably meant for the system user performing the action, rather than the employee name from the table (you can always get the latter by querying the original table with the record id). So instead of Emp_First_Name field consider using USER_NAME() built in function.
Related
I'm getting ready to release a stored procedure that gets info from other tables, does a pre-check, then inserts the good data into a (new) table. I'm not used to working with keys and new tables as much, and my insert into this new table I'm creating is having this error message having to do with the insert/key:
Msg 545, Level 16, State 1, Line 131
Explicit value must be specified for identity column in table 'T_1321_PNAnnotationCommitReport' either when IDENTITY_INSERT is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity column.
BEGIN
...
BEGIN
IF NOT EXISTS (SELECT * FROM sys.tables where name = N'T_1321_PNAnnotationCommitReport')
BEGIN
CREATE TABLE T_1321_PNAnnotationCommitReport (
[id] [INT] IDENTITY(1,1) PRIMARY KEY NOT NULL, --key
[progressnote_id] [INT] NOT NULL,
[form_id] [INT] NOT NULL,
[question_id] [INT],
[question_value] [VARCHAR](max),
[associatedconcept_id] [INT],
[crte_date] [DATETIME] DEFAULT CURRENT_TIMESTAMP,
[create_date] [DATETIME] --SCHED_RPT_DATE
);
print 'test';
END
END --if not exists main table
SET IDENTITY_INSERT T_1321_PNAnnotationCommitReport ON;
...
INSERT INTO dbo.T_1321_PNAnnotationCommitReport--(progressnote_id,form_id,question_id,question_value,associatedconcept_id,crte_date, create_date) **I tried with and without this commented out part and it's the same.
SELECT progressnote_id,
a.form_id,
question_id,
questionvalue,
fq.concept_id,
getdate(),
a.create_date
FROM (
SELECT form_id,
progressnote_id,
R.Q.value('#id', 'varchar(max)') AS questionid,
R.Q.value('#value', 'varchar(max)') AS questionvalue,
create_date
FROM
#tableNotes t
OUTER APPLY t.form_questions.nodes('/RESULT/QUESTIONS/QUESTION') AS R(Q)
WHERE ISNUMERIC(R.Q.value('#id', 'varchar(max)')) <> 0
) a
INNER JOIN [CKOLTP_DEV]..FORM_QUESTION fq ON
fq.form_id = a.form_id AND
fq.question_id = a.questionid
--select * from T_1321_PNAnnotationCommitReport
SET IDENTITY_INSERT T_1321_PNAnnotationCommitReport OFF;
END
Any ideas?
I looked at some comparable inserts we do at work, insert into select and error message, and insert key auto-incremented, and I think I'm doing what they do. Does anyone else see my mistake? Thanks a lot.
To repeat my comment under the question:
The error is literally telling you the problem. You turn change the IDENTITY_INSERT property to ON for the table T_1321_PNAnnotationCommitReport and then omit the column id in your INSERT. If you have enabled IDENTITY_INSERT you need to supply a value to that IDENTITY, just like the error says.
We can easily replicate this problem with the following batches:
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
SomeValue varchar(20));
GO
SET IDENTITY_INSERT dbo.MyTable ON;
--fails
INSERT INTO dbo.MyTable (SomeValue)
VALUES('abc');
GO
If you want the IDENTITY value to be autogenerated, then leave IDENTITY_INSERT set to OFF and omit the column from the INSERT (like above):
SET IDENTITY_INSERT dbo.MyTable OFF; --Shouldn't be needed normally, but we manually changed it before
--works, as IDENTITY_INSERT IS OFF
INSERT INTO dbo.MyTable (SomeValue)
VALUES('abc');
If you do specifically want to define the value for the IDENTITY, then you need to both set IDENTITY_INSERT to ON and provide a value in the INSERT statement:
SET IDENTITY_INSERT dbo.MyTable ON;
--works
INSERT INTO dbo.MyTable (ID,SomeValue)
VALUES(10,'def');
GO
SELECT *
FROM dbo.MyTable;
IDENTITY_INSERT doesn't mean "Get the RDBMS to 'insert' the value" it means that you want to want to tell the RDBMS what value to INSERT. This is covered in the opening sentence of the documentation SET IDENTITY_INSERT (Transact-SQL):
Allows explicit values to be inserted into the identity column of a table.
(Emphasis mine)
I have a stored procedure that inserts some values to a specific table:
CREATE PROCEDURE AccessoiresAddOrder
#AccessoireID int,
#Qte int,
#ClientID int,
#Price Varchar(50),
#Totalprice Varchar(50),
#Date DateTime,
#ValueInt
AS
INSERT INTO [dbo].[Accessoires_Orders] ([AccessoireID], [Qte], [ClientID], [Price], [Totalprice],[Date])
VALUES (#AccessoireID, #Qte, #ClientID, #Price, #Totalprice, #Date)
INSERT INTO [dbo].[Accessoires_ordervalue] (Value)
VALUES (#Value)
The Acccessoires_Orders table has a column ID which is "auto identity". Is there any way to get that value during the procedure execution?
int ID = Max(Accessoires_Orders.ID) + 1
Why ?
Because this procedure can be executed by multiple users at the same exact time.
You can use either:
SELECT #MyNewID = SCOPE_IDENTITY()
SELECT #MyNewID = ##IDENTITY
SCOPE_IDENTITY() will always give you the value created by the query itself. ##IDENTITY can give you that value as well, but if a trigger executes after your insert, it will return the ID generated by the trigger instead.
Since you want the ID you created in the query, you'd be best off using SCOPE_IDENTITY().
You can use ##IDENTITY variable that returns you the last identity value returned within the current session - see: MSDN ##IDENTITY. However, if there are e.g. some triggers behind the table you can receive an identity value created by this trigger in a completely different table...
So generally recommended way is to rather use the SCOPE_IDENTITY() - see: MSDN SCOPE_IDENTITY()
After inserting a record to a able with an Identity column, the latest value inserted in the last row will be available in ##Identity variable.
You can select value of ##Identity and return is from your stored procedure. Following is a sample code for that.
INSERT INTO Production.Location (Name, CostRate, Availability, ModifiedDate)
VALUES ('Damaged Goods', 5, 2.5, GETDATE());
GO
SELECT ##IDENTITY AS 'Identity';
GO
I've got a few tables linked together where data should be inserted to using a stored procedure. The tables are:
create table contactpersoon
(
contactpersoonnr integer identity(1,1),
klantnr integer,
naam varchar(50) not null,
telefoonnr varchar(10) not null,
emailadres varchar(50) not null,
constraint pk_contactpersoon
primary key(contactpersoonnr, klantnr),
constraint fk_contactpersoon_klantnr
foreign key(klantnr) references klant(klantnr)
)
create table klant
(
klantnr integer identity(1,1) primary key,
bedrijfsnaam varchar(50) not null
)
create table Logins
(
GebruikersNaam varchar(30),
Wachtwoord varchar(30),
Klantnr int,
MdwNr int,
constraint pk_logID primary key(GebruikersNaam),
constraint fk_klantnr foreign key(klantnr) references klant(klantnr),
constraint fk_mdwnr foreign key(mdwnr) references medewerker(mdwnr)
)
Stored procedure for adding data to these tables:
IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = 'spKlantAanmaken')
DROP PROCEDURE spKlantAanmaken
GO
Create Procedure spKlantAanmaken
(
#bedrijfsnaam as varchar(255),
#contactnaam as varchar(255),
#telnr as integer,
#email as varchar(255),
#gebruikersnaam as varchar(255),
#wachtwoord as varchar(255)
)
AS
Begin transaction
Declare #klantnr integer
Declare #contactpersoonnr integer
Insert into Klant Values (#klantnr, #bedrijfsnaam);
Insert into contactpersoon values(#contactpersoonnr, #klantnr, #contactnaam, #telnr, #email);
Insert into Logins values (#gebruikersnaam, #wachtwoord ,#klantnr, NULL);
Select * from contactpersoon
IF ##ERROR <> 0
BEGIN
ROLLBACK
RAISERROR ('Error tijdens uitvoeren van stap 2.', 16, 1)
RETURN
END
COMMIT
GO
I don't know if it is necessary to use these identity values in the inserts.
If I try this stored procedure I get the following error:
Msg 8101, Level 16, State 1, Procedure spKlantAanmaken, Line 923
An explicit value for the identity column in table 'Klant' can only be
specified when a column list is used and IDENTITY_INSERT is ON.
If I remove the identity values from the insert I get this error:
Msg 213, Level 16, State 1, Procedure spKlantAanmaken, Line 923
Column name or number of supplied values does not match table definition.
What am I doing wrong?
When you use Identity, the columns on which the identity is applied need not be in your INSERT statement VALUES. So edit your code like below
EDIT
It also seems you are missing out the columns you are trying to insert into
Insert into Klant (bedrijfsnaam) Values (#bedrijfsnaam)
Insert into contactpersoon (klantnr, contactnaam, telnr, email) Values (#klantnr, #contactnaam, #telnr, #email)
It seems all the answers saying the same thing so hope your issued is solved
Since you have identity columns, you must specify the list of columns to insert into, in your INSERT statement, and not supply a value for the identity column - like this:
Instead of
Insert into Klant Values (#klantnr, #bedrijfsnaam);
use
Insert into Klant(bedrijfsnaam) Values (#bedrijfsnaam);
and do this for all your INSERT operations.
This is a generally accepted "Best Practice" for any time you insert something into a table - it is recommend to always explicitly specify the list of columns in your table that you're inserting into (to avoid annoying errors and surprises).
Avoid the identity columns klantnr, contactpersoonnr in the INSERT query and explicitly define your column names:
So the below code will work in your case:
Insert into Klant(bedrijfsnaam) Values (#bedrijfsnaam);
Insert into contactpersoon(klantnr, naam, telefoonnr, emailadres) values(#klantnr, #contactnaam, #telnr, #email);
Just specify the column names AND the contents in the INSERT statement like:
INSERT INTO klant (bedrijfsnaam) VALUES ('XYZ');
If you don't specify the column name list, the SQL interpreter implies, you want the identity column, too. In this case you would want to set data for 2 columns, but only provide one content element, which explains the latter error message.
Edit these two lines in your SP
Insert into Klant (bedrijfsnaam)
Values (#bedrijfsnaam);
Insert into contactpersoon(klantnr,naam,telefoonnr,emailadres)
values(#klantnr, #contactnaam, #telnr, #email);
Provide a column list, excluding the identity columns in the insert statements
On a table, there's a delete trigger that performs some operations and then at the end, executes a select statement, so when you do something like...
delete from mytable where id=1
it returns a recordset.
Is there a way to save the results of that recordset into a temp table or something? I tried something like this:
declare #temptable table (returnvalue int);
insert into #temptable (returnvalue)
delete from mytable where id=1;
But apparently that syntax doesn't work.
Well,
I can not imagine a situation that you need to return the recordset of the line you will delete using a trigger returning a recordset. But I am not here to judge your requests.
Well, you can use the OUTPUT to show the row data that will be excluded and enter this data into a temporary table. Follow the example below.
However you should know that: SQL Server does not guarantee the order in Which rows are processed and returned by DML statements using the OUTPUT clause. It is up to the application to include an WHERE clause Appropriate que can guarantee the Desired semantics, or Understand que When multiple rows may qualify for the DML operation, there is guaranteed in order. The Following example uses the subquery and you assume uniqueness is a characteristic of the column in order to DatabaseLogID in Place the Desired ordering semantics. See the link.
Example:
CREATE TABLE Person
(
PersonID int,
LastName varchar(255),
FirstName varchar(255)
);
GO
--DECLARE #MyTablePerson TABLE
--(
-- PersonID int,
-- LastName varchar(255),
-- FirstName varchar(255)
--);
--GO
--CREATE TRIGGER TRG_DLT_Person
--ON Person
--INSTEAD OF DELETE
--AS
--BEGIN
-- Some code you want to do before delete
-- DELETE Person
-- FROM DELETED D
--END
--GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(1,
'Kilmister',
'Lemmy');
GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(2,
'Gilmour',
'David');
GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(3,
'Rose',
'Axl');
GO
insert into Person
(PersonID,
LastName,
FirstName)
values
(4,
'Bullock',
'Sandra');
GO
--
select * from Person;
GO
delete from Person
--output deleted.* INTO #MyTablePerson
output deleted.*
WHERE PersonID = 4 OR PersonID = 2;
GO
select * from Person;
GO
select * from #MyTablePerson;
GO
I put the example I'm showing in a this environment, but in this environment believe that are not supported for temporary tables.
SQL Fiddle
Regardless of this being a bad practice due to it being difficult for anyone interacting with the table to know that it will happen and deal with it when it does, and regardless of it being possible to capture, one pretty solid reason to not return result sets from a trigger is that doing so will be disallowed as of one of the next versions of SQL Server, so you would have to re-code the functionality anyway. The MSDN page for the disallow results from triggers Server Configuration Option states:
Important
This feature will be removed in the next version of Microsoft SQL Server. Do not use this feature in new development work, and modify applications that currently use this feature as soon as possible. We recommend that you set this value to 1.
If you are merely returning something like SELECT IdField FROM deleted; from the trigger, then you should (well, really need to) use the OUTPUT clause instead.
That being said, doing the following will do what you want:
CREATE TABLE #TempResults
(
ReturnValue INT
);
INSERT INTO #TempResults (ReturnValue)
EXEC('DELETE FROM mytable WHERE id = 1;');
You can test with the following:
SET NOCOUNT ON;
IF (OBJECT_ID('dbo.DeleteTriggerWithResults') IS NOT NULL)
BEGIN
DROP TABLE dbo.DeleteTriggerWithResults;
END;
CREATE TABLE dbo.DeleteTriggerWithResults
(
Col1 INT NOT NULL IDENTITY(1, 1),
Col2 DATETIME DEFAULT (GETDATE())
);
GO
CREATE TRIGGER dbo.tr_DeleteTriggerWithResults_d
ON dbo.DeleteTriggerWithResults
AFTER DELETE
AS
BEGIN
SELECT Col1
FROM deleted;
END;
GO
INSERT INTO dbo.DeleteTriggerWithResults DEFAULT VALUES;
GO 30
SELECT * FROM dbo.DeleteTriggerWithResults;
And then run the test:
DECLARE #TempResults TABLE (Col1 INT);
INSERT INTO #TempResults (Col1)
EXEC('
DELETE TOP (10)
FROM dbo.DeleteTriggerWithResults;
');
SELECT * FROM #TempResults;
Returns:
Col1
-------
10
9
8
7
6
5
4
3
2
1
(forgive me - I'm new to both StackOverflow & SQL)
Tl;dr - When using ##identity (or any other option such as scope_identity or output variable), is it possible to also use a view? Here is an example of a stored procedure using ##identity:
--SNIP--
DECLARE #AID INT
DECLARE #BID INT
INSERT INTO dbo.A (oct1)
VALUES
(#oct1)
SELECT #AID = ##IDENTITY;
INSERT INTO dbo.B (duo1)
VALUES
(#duo2)
SELECT #BID = ##IDENTITY
INSERT INTO dbo.tblAB (AID, BID)
VALUES
(#AID, #BID)
GO
Longer:
When inserting into a table, you can capture the current value of the identity seed using ##identity. This is useful if you want to insert into table A and B, capture the identity value, then insert into table AB relating A to B. Obviously this is for purposes of data normalization.
Let's say you were to abstract the DB Schema with a few that performs inner joins on your tables to make the data easier to work with. How would you populate the cross reference tables properly in that case? Can it be done the same way, if so, how?
Avoid using ##IDENTITY or SCOPE_IDENTITY() if your system is using Parallel plans as there is a nasty bug. Please refer -
http://connect.microsoft.com/SQL/feedback/ViewFeedback.aspx?FeedbackID=328811
Better way to fetch the inserted Identity ID would be to use OUTPUT clause.
CREATE TABLE tblTest
(
Sno INT IDENTITY(1,1) NOT NULL,
FirstName VARCHAR(20)
)
DECLARE #pk TABLE (ID INT)
INSERT INTO tblTest(FirstName)
OUTPUT INSERTED.Sno INTO #pk
SELECT 'sample'
SELECT * FROM #pk
EDIT:
It would work with Views as well. Please see the sample below. Hope this is what you were looking for.
CREATE VIEW v1
AS
SELECT sno, firstname FROM tbltest
GO
DECLARE #pk TABLE (ID INT)
INSERT INTO v1(FirstName)
OUTPUT INSERTED.Sno INTO #pk
SELECT 'sample'
SELECT ID FROM #pk
##IDENTITY returns the last IDENTITY value produced on a connection, regardless of the table that produced the value, and regardless of the scope of the statement that produced the value.
SCOPE_IDENTITY() returns the last IDENTITY value produced on a connection and by a statement in the same scope, regardless of the table that produced the value. SCOPE_IDENTITY(), like ##IDENTITY, will return the last identity value created in the current session, but it will also limit it to your current scope as well
Although the issue with either of these is fixed by microsoft , I would suggest you should go with "OUTPUT", and yes, it can be used with view as well