Using a tsql trigger to keep data synced up in two tables - tsql

I have a Products table which contains an attribute that will get updated via an ERP update by an end user. When that happens I need the update to be replicated in another table. I am not at all experienced with creating T-SQL triggers but I believe it will accomplish my objective.
Example:
In the IC_Products table:
Productkey = 456
StockLocation = ‘GA-07-A250’
In the IC_ProductCustomFields table (will start out the same because I will run a script to make it so):
Productkey = 456
CustomFieldKey = 13
Value = ‘GA-07-A250’
When the IC_Products.StockLocation column gets updated then I want the value in new IC_ProductCustomFields.Value to also get updated automatically and immediately.
If a new record is created in IC_Products then I want a new record to also be created in IC_ProductCustomFields.
I would like to know how to write the trigger script as well as how to implement it. I am using SQL Server 2005.

You want something like this:
CREATE TRIGGER [dbo].[tr_Products_SyncCustomFields] ON [dbo].[IC_Products]
FOR INSERT, UPDATE
AS
-- First, we'll handle the update. If the record doesn't exist, we'll handle that second
UPDATE IC_ProductCustomFields
SET Value = inserted.StockLocation
FROM IC_ProductCustomFields cf
INNER JOIN inserted -- Yes, we want inserted. In triggers you just get inserted and deleted
ON cf.Productkey = inserted.Productkey AND CustomFieldKey = 13;
-- Now handle the insert where required. Note the NOT EXISTS criteria
INSERT INTO IC_ProductCustomFields (Productkey, CustomFieldKey, Value)
SELECT Productkey, CustomFieldKey, Value
FROM inserted
WHERE NOT EXISTS
(
SELECT *
FROM IC_ProductCustomFields
WHERE Productkey = inserted.Productkey AND CustomFieldKey = 13
);
GO
You could, I think, do separate triggers for insert and update, but this will also have the side-effect of restoring your (supposed?) invariants if the custom fields ever get out of sync; even in an update, if the custom field doesn't exist, this will insert the new record as required to bring it back into compliance with your spec.

Related

Is INSTEAD OF UPDATE trigger the best option

I have to check when a table is inserted to/updated to see if a column value exists for the same HotelID and different RoomNo in the same table. I'm thinking that an INSTEAD OF trigger on the table would be a good option, but I read that it's a bad idea to update/insert the table the trigger executes on inside the trigger and you should create the trigger on a view instead (which raises more questions for me)
Is it ok to create a trigger like this? Is there a better option?
CREATE TRIGGER dbo.tgr_tblInterfaceRoomMappingUpsert
ON dbo.tblInterfaceRoomMapping
INSTEAD OF INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #txtRoomNo nvarchar(20)
SELECT #txtRoomNo = Sonifi_RoomNo
FROM dbo.tblInterfaceRoomMapping r
INNER JOIN INSERTED i
ON r.iHotelID = i.iHotelID
AND r.Sonifi_RoomNo = i.Sonifi_RoomNo
AND r.txtRoomNo <> i.txtRoomNo
IF #txtRoomNo IS NULL
BEGIN
-- Insert/update the record
END
ELSE
BEGIN
-- Raise error
END
END
GO
So it sounds like you only want 1 row per combo of HotelID and Sonifi_RoomNo.
CREATE UNIQUE INDEX UQ_dbo_tblInterfaceRoomMapping
ON dbo.tblInterfaceRoomMapping(HotelID,Sonifi_RoomNo)
Now if you try and put a second row with the same values, it will bark at you.
It's (usually) not okay to create a trigger like that.
Your trigger assumes a single row update or insert will only ever occur - is that guaranteed?
What will be the value of #txtRoomNo if multiple rows are inserted or updated in the same batch?
Eg, if an update is performed against the table resulting in 1 row with correct data and 1 row with incorrect data, how do you think your trigger would cope in that situation? Remember triggers fire once per insert/update, not per row.
Depending on your requirments you could keep the instead of trigger concept, however I would suggest a separate trigger for inserts and for updates.
In each you can then insert / update and include a where not exists clause to only allow valid inserts / updates, ignoring inserting or updating anything invalid.
I would avoid raising an error in the trigger, if you need to handle bad data you could also insert into some logging table with the reverse where exists logic and then handle separately.
Ultimately though, it would be best for the application to check if the roomNo is already used.

Postgres delete before insert in single transaction

PostgreSQL DB: v 9.4.24
create table my_a_b_data ... // with a_uuid, b_uuid, and c columns
NOTE: the my_a_b_data keeps the references to a and b table. So it keeps the uuids of a and b.
where: the primary key (a_uuid, b_uuid)
there is also an index:
create unique index my_a_b_data_pkey
on my_a_b_data (a_uuid, b_uuid);
In the Java jdbc-alike code, in the scope one single transaction: (start() -> [code (delete, insert)] ->commit()]) (org.postgresql:postgresql:42.2.5 driver)
delete from my_a_b_data where b_uuid = 'bbb';
insert into my_a_b_data (a_uuid, b_uuid, c) values ('aaa', 'bbb', null);
I found that the insert fails, because the delete is not yet deleted. So it fails because it can not be duplicated.
Q: Is it is some kind of limitation in PostgreSQL that DB can’t do a delete and insert in one transaction because PostgreSQL doesn’t update its indexes until the commit for the delete is executed, therefore the insert will fail since the id or key (whatever we may be using) already exists in the index?
What would be possible solution? Splitting in two transactions?
UPDATE: the order is exactly the same. When I test the sql alone in the SQL console. It works fine. We use JDBI library v 5.29.
there it looks like this:
#Transaction
#SqlUpdate("insert into my_a_b_data (...; // similar for the delete
public abstract void addB() ..
So in the code:
this.begin();
this.deleteByB(b_id);
this.addB(a_id, b_id);
this.commit();
I had a similar problem to insert duplicated values and I resolved it by using Insert and Update instead of Delete. I created this process on Python but you might be able to reproduce it:
First, you create a temporary table like the target table where you want to insert values, the difference is that this table is dropped after commit.
CREATE TEMP TABLE temp_my_a_b_data
(LIKE public.my_a_b_data INCLUDING DEFAULTS)
ON COMMIT DROP;
I have created a CSV (I had to merge different data to input) with the values that I want to input/insert on my table and I used the COPY function to insert them to the temp_table (temp_my_a_b_data).
I found this code on this post related to Java and COPY PostgreSQL - \copy command:
String query ="COPY tmp from 'E://load.csv' delimiter ','";
Use the INSERT INTO but with the ON_CONFLICT clause which you can decide to do an action when the insert cannot be done because of specified constrains, on the case below we do the update:
INSERT INTO public.my_a_b_data
SELECT *
FROM temp_my_a_b_data
ON CONFLICT (a_uuid, b_uuid,c) DO UPDATE
SET a_uuid = EXCLUDED.a_uuid,
b_uuid = EXCLUDED. c = EXCLUDED.c;`
Considerations:
I am not sure but you might be able to perform the third step without using the previous steps, temp table or copy from. You can just a loop over the values:
INSERT INTO public.my_a_b_data VALUES(value1, value2, null)
ON CONFLICT (a_uuid, b_uuid,c) DO UPDATE
SET a_uuid = EXCLUDED.a_uuid,
b_uuid = EXCLUDED.b_uuid, c = EXCLUDED.c;

How do I make a trigger to update a column in another table?

So I am working on adding a last updated time to the database for my app's server. The idea is that it will record the time an update is applied to one of our trips and then the app can send a get request to figure out if it's got all of the correct up to date information.
I've added the column to our table, and provided the service for it all, and finally manage to get a trigger going to update the column every time a change is made to a trip in it's trip table. My problem now comes from the fact that the information that pertains to a trip is stored across a multitude of other tables as well (for instance, there are tables for the routes that make up a trip and the photos that a user can see on the trip, etc...) and if any of that data changes, then the trip's update time also needs to change. I can't for the life of me figure out how to set up the trigger so that when I change some route information, the last updated time for the trip(s) the route belongs to will be updated in it's table.
This is my trigger code as it stands now: it updates the trip table's last updated column when that trip's row is updated.
CREATE OR REPLACE FUNCTION record_update_time() RETURNS TRIGGER AS
$$
BEGIN
NEW.last_updated=now();
RETURN NEW;
END;
$$
LANGUAGE PLPGSQL;
CREATE TRIGGER update_entry_on_entry_change
BEFORE UPDATE ON mydatabase.trip FOR EACH ROW
EXECUTE PROCEDURE record_update_time();
--I used the next two queries just to test that the trigger works. It
--probably doesn't make a difference to you but I'll keep it here for reference
UPDATE mydatabase.trip
SET title='Sample New Title'
WHERE id = 2;
SELECT *
FROM mydatabase.trip
WHERE mydatabase.trip.id < 5;
Now I need it to update when the rows referencing the trip row with a foreign key get updated. Any ideas from someone more experienced with SQL triggers than I?
"mydatabase" is a remarkably unfortunate name for a schema.
The trigger function could look like this:
CREATE OR REPLACE FUNCTION trg_upaft_upd_trip()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
UPDATE mydatabase.trip t -- "mydatabase" = schema name (?!)
SET last_updated = now()
WHERE t.id = NEW.trip_id -- guessing column names
RETURN NULL; -- calling this AFTER UPDATE
END
$func$;
And needs to be used in a trigger on every related table (not on trip itself):
CREATE TRIGGER upaft_upd_trip
AFTER UPDATE ON mydatabase.trip_detail
FOR EACH ROW EXECUTE PROCEDURE trg_upaft_upd_trip();
You also need to cover INSERT and DELETE (and possibly COPY) on all sub-tables ...
This approach has many potential points of failure. As alternative, consider a query or view that computes the latest last_updated from sub-tables dynamically. If you update often this might be the superior approach.
If you rarely UPDATE and SELECT often, your first approach might pay.

EJB3.0 Updating a field in a table with the same value

I have a database table 'MyTable' that has a trigger upon update of field 'Status' in it.
Below is a dummy-code of what i'm trying to do:
MyTable table = new Mytable();
table.setTableId(1);
table.setStatus ("NEW");
em.persist (table); //At this point the trigger did not kick in since this inserted a new record
...
MyTable table2 = em.find(MyTable.class, 1);
table2.setStatus ("NEW");
em.merge(table2)//Even though im updating the record with the same status with the same value, i still want the trigger to kick. However the trigger is not being activated.
...
MyTable table3 = em.find(MyTable.class, 1);
table3.setStatus ("OLD");
em.merge(table3)//The trigger is being activated here since the status is different the status value when it was inserted the first time.
Long story short, how can i make the changes done to 'transfer2' to trigger an update even though the status is the same?
-Thanks
Use em.flush(); to synchronize the persistence context to the underlying database. Your pending queries shall be send to database, but you still can have a complete rollback.
JPA does not update object that have no changes.
You could try changing to something else (flush), then changing it back.
You could also use a JPQL update query to update it.
Depending on your JPA provider you could probably force it to update fields that have not changed, but this would lead to very bad performance.
Could you please try updating the enity and commit the transaction, without using merge.
Like
em.getTransaction().begin();
MyTable table2 = em.find(MyTable.class, 1);
table2.setStatus ("NEW");
//em.merge(table2)//Even though im updating the record with the same status with the
// same value, i still want the trigger to kick. However the trigger is not being activated.
em.getTransaction().commit();

How do I add a "last updated" column in a SQL Server 2008 R2 table?

I have a table in my SQL Server 2008 R2 database, and would like to add a column called LastUpdated, that will automatically be changed every time the row is updated. That way, I can see when each individual row was last updated.
It seems that SQL Server 2008 R2 doesn't have a data type to handle this like earlier versions did, so I'm not sure of the best way to do it. I wondered about using a trigger, but what would happen when the trigger updated the row? Will that fire the trigger again, etc?
To know which row was last updated, you need to create a new column of type DATETIME/DATETIME2 and update it with a trigger. There is no data type that automatically updates itself with date/time information every time the row is updated.
To avoid recursion you can use the UPDATE() clause inside the trigger, e.g.
ALTER TRIGGER dbo.SetLastUpdatedBusiness
ON dbo.Businesses
AFTER UPDATE -- not insert!
AS
BEGIN
IF NOT UPDATE(LastUpdated)
BEGIN
UPDATE t
SET t.LastUpdated = CURRENT_TIMESTAMP -- not dbo.LastUpdated!
FROM dbo.Businesses AS t -- not b!
INNER JOIN inserted AS i
ON t.ID = i.ID;
END
END
GO
In modern versions you can trick SQL Server into doing this using temporal tables:
Maintaining LastModified Without Triggers
But this is full of caveats and limitations and was really only making light of multiple other similar posts:
A System-Maintained LastModifiedDate Column
Tracking Row Changes With Temporal
Columns
How to add “created” and “updated” timestamps without triggers
Need a datetime column that automatically updates
It's not that easy, unfortunately.
You can add a new DATETIME (or DATETIME2) field to your table, and you can give it a default constraint of GETDATE() - that will set the value when a new row is inserted.
Unfortunately, other than creating an AFTER UPDATE trigger, there is no "out of the box" way to keep it updated all the time. The trigger per se isn't hard to write, but you'll have to write it for each and every single table that should have that feature.....