Database Triggers: On Insert - sql

This is a simple example.
I want to insert data in Table1 (Name, Age, Sex). This table has an automatically increasing serial#(int) on insertion of data.
I want to put a trigger on Table1 insert, so that after inserting data, it picks up the serial#(int) from Table1 and puts Serial# and Name to Table2 and Serial# and some other data in Table3.
Is it possible via triggers?
or, should I pick (last) Serial from table1 and call insert on other tables by increasing it manually, in same SP I used to insert in Table1?
Which approach is better?
EDIT 1:
Suppose table:
Serial | UID | Name | Age | Sex | DateTimeStamp
(int | uniqueidentifier | nvarchar | smallint | nchar | DateTime )
Default NewID() and Default GetDate() as UID and DateTimeStamp, would INSERTED table have Datetime-Of-Insertion in DatetimeStamp field? Meaning, I originally didn't enter any of Serial, GUID or DatetimeStamp, will they occur in INSERTED table?
EDIT 2:
Can you point me towards good books/articles on triggers. I read mastering SQL server 2005, didn't get much out of it. Thanks!

Sure you can do this with a trigger - something like:
CREATE TRIGGER trg_Table1_INSERT
ON dbo.Table1 AFTER INSERT
AS BEGIN
INSERT INTO dbo.Table2(SerialNo, Name)
SELECT SerialNo, Name
FROM Inserted
INSERT INTO dbo.Table3(SomeOtherCol)
SELECT SomeOtherCol
FROM Inserted
END
or whatever it is you need to do here....
It's important to understand that the trigger will be called once per statement - not once per row inserted. So if you have a statement that inserts 10 rows, your trigger gets called once, and the pseudo-table Inserted will contain those 10 rows that have been inserted in the statement.

Yes, this is possible with triggers.
When you use an INSERT trigger, you have access to the INSERTED logical table that represents the row to be inserted, with the value of the new ID it in.

Yes, it is possible by trigger, but keep in mind that TRIGGER doesn't take any input and doesn't provide any output, so you only can collect your desired data by querying in the trigger, however to satisfy insertion into your Table2 and Table3
CREATE TRIGGER tr_YourDesiredTriggerName ON Table1
FOR INSERT
AS
BEGIN
-- Inserting data to Table2
INSERT INTO Table2( Serial, Name)
SELECT i.Serial, i.Name
FROM Table1 AS t1 INNER JOIN Inserted AS i ON t1.Serial = i.Serial
AND i.Serial NOT IN ( SELECT t2.Serial FROM Table2 AS t2 )
-- Inserting data to Table3
INSERT INTO Table3( Serial, OtherData) -- select from other table
SELECT i.Serial, OtherData
FROM OtherTable AS ot INNER JOIN Inserted AS i ON ot.Serial = i.Serial
AND i.Serial NOT IN ( SELECT t3.Serial FROM Table3 AS t3 )
END

If you don't have control over the source of the insert then use a trigger. If you do have control over the source of the inserts then you can modify your insert process to also add the secondary table rows.
Will you also have control over future inserts? What if another insert gets created for this table? If you use a trigger then the secondary inserts would also get handled automatically. If not then the second insert process could possibly leave out the secondary inserts. Maybe that would be good or bad depending on your application.

Related

Could insert statements match data from another table

I am trying to do an insert on a table from another table with matched values and IDs.
lets say on table1 there are values of names and IDS. Like John and 55.
I am trying to only insert the 55 into table2 since John is already on table2 but just missing his ID.
I know I can do update statement to update the value for John to 55, but my tables have over 3000 values, and it will be hard to do one at a time:
Anyway I can write a query to enter a value into the other table as long as the names match together?
what I tried so far:
insert into desired_table (id,version,source_id,description,r_id)
SELECT HI_SEQUENCE.nextval,'0', select min (id)
from table
where name in (select name from table2 where table2_name is not null),
table2_name,
table2.r_id from table2 where name is not null;
Issue with this statement is it inserts multiple values, but it only inserts it into where the min ID is.
Anyway I can adjust this and have it pull more than one ID?
Use Merge statement (https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver15)
Merge into Table1 d
Using Table 2 s
on d.name=s.name
when matching then update
age=s.age
when not matching then insert
(col1, col2)
values (s.col1, s.col2);
You might want a trigger to automate the above task.
Create Trigger sample after insert on
Table1 for each row
Begin
Update table2 set table2.age = :NEW.AGE where
table2.id=:NEW.Id
END;
Got this working by generating insert statements and running them with insert all

Enforce inserting a record into a secondary table every time a record is inserted into a primary table

In a SQL Server 2008 R2 setup, I have table1 (id, col1-1, col1-2, ...) and table2 (id, table1_id, col2-1, col2-2, ...), where table2.table1_id points to table1.id (i.e. a foreign key to primary key relation).
How do I enforce the rule that every time somebody insert a record into table1, they are also required to create a record into table2?
I would guess that the obvious mechanism to use here are triggers, but this can lead to the chicken and the egg problem since the trigger will require a record in table2 in order to create a record in table1, but in order to create a record in table2 first I need the table1.id value to already exists. How to overcome this?
I need the enforcement to be automatic and built-in into the database. A frequently running job to check for records in table1 without corresponding records in table2 will not make the cut and the rule must be enforced at real time when somebody is actually creating a record in table1.
You could simply use OUTPUT clause to do that. Here is a simple example
CREATE TABLE Parent(Id INT, Col VARCHAR(45));
CREATE TABLE Child(Id INT, Col VARCHAR(45));
INSERT INTO Parent(Id, Col)
OUTPUT INSERTED.Id, INSERTED.Col INTO Child
VALUES(1, 'SomeValue');
SELECT *
FROM Parent P
JOIN Child C ON P.Id = C.Id;
Note that the extended support for SQL Server 2008/2008 R2 ends on July 9, 2019. You should upgrade as soon as possible.
Chicken and the egg problem is preventable, but I tested on Windows SQL 2016:
CREATE TRIGGER [dbo].[Table1_Insert]
ON [dbo].[Table1RR] AFTER INSERT
AS
IF rowcount_big() = 0 RETURN --leave trigger if no record (-s) inserted in the Table1
SET NOCOUNT ON
ALTER TABLE dbo.Table2RR NOCHECK CONSTRAINT FK_Table2_Table1 -- resolve chicken and the egg problem
INSERT INTO dbo.Table2RR select id,null,null from inserted order by id
ALTER TABLE dbo.Table2RR CHECK CONSTRAINT FK_Table2_Table1
GO
ALTER TABLE [dbo].[Table1RR] ENABLE TRIGGER [Table1_Insert]
GO

How to use multiple identity numbers in one table?

I have an web application that creates printable forms, these forms have a unique number on them, the problem is I have 2 forms that separate numbers need to be created for them.
ie)
Form1- Numbered 2000000-2999999
Form2- Numbered 3000000-3999999
dbo.test2 - is my form information table
Tsel - is my autoinc table for the 3000000 series numbers
Tadv - is my autoinc table for the 2000000 series numbers
What I have done is create 2 tables with just autoinc row (one for 2000000 series numbers and one for 3000000 series numbers), I then created a trigger to add a record to the coresponding table, read back the autoinc number and add it to my table that stores the form information including the just created autoinc number for the right series of forms.
Although it does work, I'm concerned that the numbers will get messed up under load.
I'm not sure the ##IDENTITY will always return the right value when many people are using the system. (I cannot have duplicates and I need to use the numbering form show above.
See code below.
**** TRIGGER ****
CREATE TRIGGER MAKEANID2 ON dbo.test2
AFTER INSERT
AS
SET NOCOUNT ON
declare #someid int
declare #someid2 int
declare #startfrom int
declare #test1 varchar(10)
select #someid=##IDENTITY
select #test1 = (Select name1 from test2 where sysid = #someid )
if #test1 = 'select'
begin
insert into Tsel Default values
select #someid2 = ##IDENTITY
end
if #test1 = 'adv'
begin
insert into Tadv Default values
select #someid2 = ##IDENTITY
end
update test2
set name2=(#someid2) where sysid = #someid
SET NOCOUNT OFF
The best way to keep the two IDs in sync is to create a persisted Computed Column based on the actual identity column. Where Col1 is the identity column and Col2 is the persisted computed column that is the result of some formula based on Col1. You can then even Create Indexes on Computed Columns.
test this out:
CREATE TABLE YourTable
(Col1 int not null identity(2000000,1)
,Col2 AS (Col1-2000000+3000000) PERSISTED
,Col3 varchar(5)
)
GO
insert into YourTable (col3) values ('a')
insert into YourTable (col3) SELECT 'b' UNION SELECT 'c'
SELECT * FROM YourTable
OUTPUT:
Col1 Col2 Col3
----------- ----------- -----
2000000 3000000 a
2000001 3000001 b
2000002 3000002 c
(3 row(s) affected)
EDIT After OPs comments, I'm still not 100% sure what you are after.
I never used SQL Server 2000 (we skipped that version), and I don't really want to look up how to do everything in that version, it is so limited without the OUTPUT clause and ROW_NUMBER(), CTEs, etc.
I can think of three methods to do:
1) You could just create a sequence table, where you have 2 rows one for A and one for B, each time you need to insert one, look up, increment, and save the value of the type of seq you need and then insert with that value. for example if you are inserting a type "A" row, do this:
INSERT INTO test2
(col1, col2, col3,...)
SELECT
ISNULL(MAX(NextSeq),0)+1, col2, col3,...
FROM YourSequenceTable WITH (UPDLOCK, HOLDLOCK)
WHERE SequenceType='A'
UPDATE YourSequenceTable
SET NextSeq=ISNULL(NextSeq,0)+1
WHERE SequenceType='A'
2) change your table structure to just save the data in Tsel or Tadv and have a trigger insert into a third common table table where you can have your additional "common" identity. common table would be like
CommonTable
ID int not null indentity(1,1) primary key
TselID int null FK to Tsel.PK
TadvID int null FK to Tadv.PK
3) if you need a single table, try this, which is a real hack. Change your Tsel and Tadv tables to contain all the necessary columns and from the application INSERT INTO Tsel when the value is select and have a trigger grab that identity value and then INSERT that into test2, then remove the data from tsel. Then, from the application when the value is adv just INSERT INTO Tadv an have a trigger on that table insert the data into test2, and remove the data from Tadv. You need to have all data columns in Tsel and Tadv so the trigger can copy the values to test2, but the trigger will remove the rows from there (the identity will be sequential even if the original rows are removed).
your Tsel trigger would look like:
CREATE Trigger MAKEANID2_Tsel ON dbo.Tsel
AFTER INSERT
AS
--copy data from Tsel into test2., test2 can still have its own identity value
INSERT INTO test2
(PK, col1, col2, col3,...)
SELECT
col0, col1, col2, col3,....
FROM INSERTED
--remove rows from Tsel, which were just copied and not needed anymore.
DELETE Tsel
WHERE PK IN (SELECT PK FROM INSERTED)
GO
YOu are right to worry about ##identity, it is not a recommended peice of code, if somone else adds a differnet trigger that inserets an identity and that one triggers first, that is the value you will get.
But you have much bigger problems. Your trigger is deisgned to work on only one record ata time. This is a very very very bad thing to do with a trigger. Triggers operate on sets of data and must ALWAYS even if you think therer will never be more than one record inserted ata time) be set up to handle sets of data not one record. Further, you don;t need to ask for the identity, you have the identities of all records inserted inteh batch in a psuedotable availlble in triggers called inserted.
Now reading one of your comments, you say you can't have any missing values at all. Inthat case you cannot under any circustance use an identity column as it will have gaps if any transaction is rolled back. You will have to write your own process to create the numbers based onteh last number and look out for race conditions.

Sql Server trigger insert values from new row into another table

I have a site using the asp.net membership schema. I'd like to set up a trigger on the aspnet_users table that inserted the user_id and the user_name of the new row into another table.
How do I go about getting the values from the last insert?
I can select by the last date_created but that seems smelly. Is there a better way?
try this for sql server
CREATE TRIGGER yourNewTrigger ON yourSourcetable
FOR INSERT
AS
INSERT INTO yourDestinationTable
(col1, col2 , col3, user_id, user_name)
SELECT
'a' , default , null, user_id, user_name
FROM inserted
go
You use an insert trigger - inside the trigger, inserted row items will be exposed as a logical table INSERTED, which has the same column layout as the table the trigger is defined on.
Delete triggers have access to a similar logical table called DELETED.
Update triggers have access to both an INSERTED table that contains the updated values and a DELETED table that contains the values to be updated.
You can use OLDand NEW in the trigger to access those values which had changed in that trigger. Mysql Ref
In a SQL Server trigger you have available two psdeuotables called inserted and deleted. These contain the old and new values of the record.
So within the trigger (you can look up the create trigger parts easily) you would do something like this:
Insert table2 (user_id, user_name)
select user_id, user_name from inserted i
left join table2 t on i.user_id = t.userid
where t.user_id is null
When writing triggers remember they act once on the whole batch of information, they do not process row-by-row. So account for multiple row inserts in your code.
When you are in the context of a trigger you have access to the logical table INSERTED which contains all the rows that have just been inserted to the table. You can build your insert to the other table based on a select from Inserted.
Create
trigger `[dbo].[mytrigger]` on `[dbo].[Patients]` after update , insert as
begin
--Sql logic
print 'Hello world'
end

Row number in Sybase tables

Sybase db tables do not have a concept of self updating row numbers. However , for one of the modules , I require the presence of rownumber corresponding to each row in the database such that max(Column) would always tell me the number of rows in the table.
I thought I'll introduce an int column and keep updating this column to keep track of the row number. However I'm having problems in updating this column in case of deletes. What sql should I use in delete trigger to update this column?
You can easily assign a unique number to each row by using an identity column. The identity can be a numeric or an integer (in ASE12+).
This will almost do what you require. There are certain circumstances in which you will get a gap in the identity sequence. (These are called "identity gaps", the best discussion on them is here). Also deletes will cause gaps in the sequence as you've identified.
Why do you need to use max(col) to get the number of rows in the table, when you could just use count(*)? If you're trying to get the last row from the table, then you can do
select * from table where column = (select max(column) from table).
Regarding the delete trigger to update a manually managed column, I think this would be a potential source of deadlocks, and many performance issues. Imagine you have 1 million rows in your table, and you delete row 1, that's 999999 rows you now have to update to subtract 1 from the id.
Delete trigger
CREATE TRIGGER tigger ON myTable FOR DELETE
AS
update myTable
set id = id - (select count(*) from deleted d where d.id < t.id)
from myTable t
To avoid locking problems
You could add an extra table (which joins to your primary table) like this:
CREATE TABLE rowCounter
(id int, -- foreign key to main table
rownum int)
... and use the rownum field from this table.
If you put the delete trigger on this table then you would hugely reduce the potential for locking problems.
Approximate solution?
Does the table need to keep its rownumbers up to date all the time?
If not, you could have a job which runs every minute or so, which checks for gaps in the rownum, and does an update.
Question: do the rownumbers have to reflect the order in which rows were inserted?
If not, you could do far fewer updates, but only updating the most recent rows, "moving" them into gaps.
Leave a comment if you would like me to post any SQL for these ideas.
I'm not sure why you would want to do this. You could experiment with using temporary tables and "select into" with an Identity column like below.
create table test
(
col1 int,
col2 varchar(3)
)
insert into test values (100, "abc")
insert into test values (111, "def")
insert into test values (222, "ghi")
insert into test values (300, "jkl")
insert into test values (400, "mno")
select rank = identity(10), col1 into #t1 from Test
select * from #t1
delete from test where col2="ghi"
select rank = identity(10), col1 into #t2 from Test
select * from #t2
drop table test
drop table #t1
drop table #t2
This would give you a dynamic id (of sorts)