How to get a value of identity column using OUTPUT - SQL Server - sql

I have a table and a trigger
create table test(id int not null identity(1,1) primary key, data int);
create trigger insteadTestInsert ON test INSTEAD OF INSERT
AS
BEGIN
INSERT INTO test(data) select data FROM inserted;
END;
When trigger is enabled, the following query
declare #tmp_table table(id int, int_val int);
insert into test(data)
output inserted.* into #tmp_table
values (10);
select * from #tmp_table;
returns id = 0, int_val = 10 .
If I disable(or drop) the trigger, the query returns the proper value of id.
How to make OUTPUT insert proper results into table variable?

From MSDN
Columns returned from OUTPUT reflect
the data as it is after the INSERT,
UPDATE, or DELETE statement has
completed but before triggers are
executed.
So the problem is that before your trigger was executed inserted didn't have id set.
Even if remove content of the trigger like this
create trigger insteadTestInsert on test instead of insert
as
begin
return;
end
you will see that inserted is still populated although nothing was inserted into the table. Basically inserted in output statement matches inserted inside of the trigger.

This one actually works.
declare #tmp_table table(seq int identity, id int, int_val int);
insert into test(data)
output inserted.data into #tmp_table(int_val)
values (11),(12),(13);
update #tmp_table set id = seq + ##identity - ##rowcount
select * from #tmp_table;
select top 2 * from test order by id desc;
The restriction is that you must NOT have any other triggers on the table test that would "corrupt" the ##identity variable.

This is a hack, but it works:
declare #tmp_table table(id int, int_val int);
insert into test(data)
output ##IDENTITY + 1, inserted.data into #tmp_table
values (10);
select * from #tmp_table;
See http://msdn.microsoft.com/en-us/library/ms190315.aspx for details on using ##IDENTITY.

Related

Update table column with primary key after insert (in one statement)

Using an update statement, how can i directly set a column value to the primary key of an insert statement? I need this as one statement.
I know the following is wrong, but it helps get my idea through:
update AppNationalities
set CountrySelectionID = (
select [INSERTED.pkID] from
(
insert into CountrySelections
output INSERTED.pkID
values(CountryID, 'test', 0)
)
)
Put the values into a temporary table:
declare #ids table (pkID int);
insert into CountrySelections
output INSERTED.pkID into #ids
values (CountryID, 'test', 0);
update AppNationalities
set CountrySelectionID = i.id
from #ids i;
I don't think the output clause is allowed in the FROM clause of an UPDATE or DELETE statement.

Microsoft SQL Server - default value provided by stored procedure

Is it possible to have a non-null column where the value is generated at insert by calling a stored procedure the parameters of which are values passed to insert into the row?
For example, I have table User:
| username | name | surname | id |
Insert looks like this:
INSERT INTO USER (username, name, surname)
VALUES ('myusername', 'myname', 'mysurname');
The id column is populated with an (integer) value retrieved by calling stored procedure mystoredproc with parameters myusername, myname, mysurname.
A further question is, would this stored procedure be called on each row, or can it be called in a grouped fashion. For example, I'd like my stored procedure to take the name and append a random integer to it so that that if I insert 100 users with the name 'David', they will get the same id and the stored procedure will be called only once. A bit of a bad example on the second point.
Good day,
Is it possible to have a non-null column where the value is generated at insert by calling a stored procedure
Option 1: please check if this work for you
Specify Default Value for the Column and use "NOT NULL"
create trigger on the table AFTER INSERT
Inside the trigger, you can use the virtual table "inserted" in order to get the inserted values.
Using these values (using the inserted table) you can update the column using the logic you need for all the rows at once
** there is no need to use external SP probably, but you can execute SP from trigger if needed
** All executed by a trigger is in the same transaction as the original query.
would this stored procedure be called on each row
NO! The trigger will be executed once for all rows you insert in the same statement. The inserted table includes all the rows which were inserted. In your update section (step 4) you can update all the rows which were inserted in once and no need to execute something for each row
** If you do use external SP which is executed from the trigger then you can pass it all the inserted table as one using Table-Valued Parameter
------------------- update ---------------
Here is a full example of using this logic:
drop table if exists T;
CREATE TABLE T (id int identity(2,2), c int NOT NULL default 1)
GO
CREATE TRIGGER tr ON T AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
UPDATE T SET T.c = T2.C + 1
FROM inserted T2
INNER JOIN T T1 ON T1.id = T2.id
END
INSERT T(c) values (1) -- I insert the value 1 but the trigger will change it to 1+1=2
select * from T
GO
-- test multiple rows:
INSERT T(c) values (10),(20),(30),(40)
select * from T
GO
DECLARE #rc INT = 0,
#UserID INT = ABS(CHECKSUM(NEWID())) % 1000000 + 1;
WHILE #rc = 0
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.Users WHERE UserId= #UserId)
BEGIN
INSERT dbo.Users(UserId) WHERE Username = #UserName SELECT #UserId;
SET #rc = 1;
END
ELSE
BEGIN
SELECT #UserId = ABS(CHECKSUM(NEWID())) % 1000000 + 1,
#rc = 0;
END
END

How to get a inserted id in other table from inside a trigger?

I have 3 tables tbl_Users, tbl_Protocol and tbl_ProtocolDetails and inside of my trigger on Users, I have to inserted into Protocol and then insert into ProtocolDetails, but I don't know how work the inserted scope.
Something like that:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #UserId = Int
DECLARE #ProtocolId = Int
DECLARE #UserDetail = NVARCHAR(255)
SELECT
#UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
FROM INSERTED
INSERT INTO tbl_Protocol (user_id, inserted_date)
VALUES (#UserId, GetDate())
-- Return Inserted Id from tbl_Protocol into #ProtocolDetail then
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
VALUES (#ProtocolId, #UserDetail)
END
Your trigger has a MAJOR flaw in that you seems to expect to always have just a single row in the Inserted table - that is not the case, since the trigger will be called once per statement (not once for each row), so if you insert 20 rows at once, the trigger is called only once, and the Inserted pseudo table contains 20 rows.
Therefore, code like this:
Select #UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
From INSERTED;
will fail, since you'll retrieve only one (arbitrary) row from the Inserted table, and you'll ignore all other rows that might be in Inserted.
You need to take that into account when programming your trigger! You have to do this in a proper, set-based fashion - not row-by-agonizing-row stlye!
Try this code:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
-- declare an internal table variable to hold the inserted "ProtocolId" values
DECLARE #IdTable TABLE (UserId INT, ProtocolId INT);
-- insert into the "tbl_Protocol" table from the "Inserted" pseudo table
-- keep track of the inserted new ID values in the #IdTable
INSERT INTO tbl_Protocol (user_id, inserted_date)
OUTPUT Inserted.user_id, Inserted.ProtocolId INTO #IdTable(UserId, ProtocolId)
SELECT user_id, SYSDATETIME()
FROM Inserted;
-- insert into the "tbl_ProtocolDetails" table from both the #IdTable,
-- as well as the "Inserted" pseudo table, to get all the necessary values
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
SELECT
t.ProtocolId,
i.user_detail + '#' + i.user_explanation
FROM
#IdTable t
INNER JOIN
Inserted i ON i.user_id = t.UserId
END
There is nothing in this trigger that would handle a multiple insert/update statement. You will need to either use one scenario that will handle multiple records or check how many records were effected with a IF ##ROWCOUNT = 1 else statement. In your example, I would just use something like
insert into tbl_Protocol(user_id, inserted_date)
select user_id, user_detail + '#' + user_explanation
From INSERTED;
As for your detail table, I see Marc corrected his answer to include the multiple lines and has a simple solution or you can create a second trigger on the tbl_Protocol. Another solution I have used in the past is a temp table for processing when I have very complicated triggers.

Insert trigger in SQL Server

I am trying to write a trigger that inserts in a log table the date, name of the table I inserted in, the name of the operation (insert, delete, update) and the number of insertion.
Here is my code:
CREATE TRIGGER [dbo].[Inser]
ON [dbo].[Avion]
AFTER INSERT
AS
BEGIN
DECLARE #codAv int
DECLARE #NumeAv varchar(100)
DECLARE #MotorAv varchar(100)
SELECT #codAv = INSERTED.codA FROM INSERTED
SELECT #NumeAv = INSERTED.NumeA FROM INSERTED
SELECT #MotorAv = INSERTED.Motor FROM INSERTED
SELECT ##ROWCOUNT AS INSERTED;
INSERT INTO LogM (DataM, Numele, Tipul, Numar)
VALUES(GETDATE(), 'Avion', 'Inserare', ##ROWCOUNT);
PRINT 'INSERT trigger fired.'
END
I have a stored procedure where I have something like 20 insertion of this type:
INSERT INTO Avion (Motor,NumeA)
VALUES ('Junkers','Focke-Wulf');
all separated from one another. When I run that code, the table LogM will be populated with 20 new rows of this type:
5 Nov 27 2016 8:58PM Avion Inserare 1.00
I want to make my trigger to count all the insertion stored in a procedure, then insert in LogM only one entry, with the ROWCOUNT not being 1.00, but the number of insertion I made in that stored procedure, and I don't have any idea how can I do this.
Thank you.
Instead of passing ##ROWCOUNT you can not just pass the ID?
##ROWCOUNT works to return the number of rows affected, as you enter one at a time, it will always be one.
CREATE TRIGGER [dbo].[Inser]
ON [dbo].[Avion]
AFTER INSERT
AS
BEGIN
DECLARE #codAv int
DECLARE #NumeAv varchar(100)
DECLARE #MotorAv varchar(100)
SELECT #codAv=INSERTED.codA FROM INSERTED
SELECT #NumeAv=INSERTED.NumeA FROM INSERTED
SELECT #MotorAv=INSERTED.Motor FROM INSERTED
INSERT INTO LogM
(DataM,Numele,Tipul,Numar)
VALUES(GETDATE(),'Avion','Inserare',#codAv);
PRINT 'INSERT trigger fired.'
END

Getting the identity of the row I just inserted

I've got two tables that are linked with foreign keys. I need to do a few inserts on one table and then use the identity column values are part of my next batch of insert statements. This all has to be done through SQL.
I've been reading about SCOPE_IDENTITY() but all the examples I'm seeing are using ASP/PHP/Whatever to do the substitution in the next batch of inserts, I dont have this option.
Any pointers appreciated.
Use
SCOPE_IDENTITY() - Function
instead of
##IDENTITY Variable -
otherwise you get the result of the last inserted identity - if there is a trigger on the table than does inserting somewhere you get the wrong result back (the value inserted by the trigger and not by your statement). Scope_Identity returns the identity of your statement and that is what you normally want.
IN SQl Server 2008 you can also use the OUTPUT clause.
DECLARE #output TABLE (myid INT)
INSERT mytable (field1, field2)
OUTPUT inserted.myid INTO #output
VALUES ('test', 'test')
SELECT * FROM #output
What makes this espcially valuable is if you use it to insert a bunch of records instead of one you can get all the identities, You can also return any of the other fields you might need.
something like:
DECLARE #output TABLE (myid INT, field1 datetime)
INSERT mytable (field1, field2)
OUTPUT inserted.myid, inserted.field1 INTO #output
Select '20100101', field3 from mytable2
SELECT * FROM #output
You can do the same with SQL or TSQL. Just assign the identify column as soon as you do the insert.
-- Variable to hold new Id
Declare #NewId int
--Insert some values in to create a new ID
Insert Into dbo.MyTable1 (Col1)
Values (#NewValue)
-- Grab the new Id and store it in the variable
Select #NewId = Scope_Identity()
--Insert the new Id in to another table
Insert Into dbo.AnotherTable (Col1)
Values (#NewId)
You can use
SELECT SCOPE_IDENTITY() [Iden]
After the insert statement in the same block. This should work.