Can you easily and efficiently copy or edit the INSERTED table in a trigger? - sql

I'm writing a trigger in which I need to check the incoming data and potentially change it. Then later in the trigger I need to use that new data for further processing. A highly simplified version looks something like this:
ALTER TRIGGER [db].[trig_update]
ON [db].[table]
AFTER UPDATE
AS
BEGIN
DECLARE #thisprofileID int
IF (Inserted.profileID IS NULL)
BEGIN
SELECT #thisprofileID=profileID
FROM db.otherTable
WHERE userid = #thisuserID;
UPDATE db.table
SET profileID = #thisprofileID
WHERE userid = #thisuserID;
-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
END
IF ({conditional})
BEGIN
UPDATE db.thirdTable
SET [profileID] = Inserted.profileID
...{20+ other fields}
FROM Inserted ...{a few joins}
WHERE {various criteria}
END
END
The problem that we're running into is that the update statement fails because Inserted.profileID is null, and thirdTable.profileID is set to not allow nulls. table.profileID will never stay null; if it is created as null then this trigger should catch it and set it to a value. But even though we're updated 'table', Inserted still has the null value. So far it makes sense to me why this is happening.
I'm unsure how to correct the problem. In the area with commented Xs I tried running an update query against the Inserted table to update profileID, but this resulted in an error because the pseudo-table apparently can't be updated. Am I incorrect in this presumption? That would be an easy solution.
The next most logical solution to me would be to INSERT INTO a table variable to make a copy of Inserted and then use that in the rest of the trigger, but that fails because the table variable is not defined. Defining that table variable would require more fields than I care to count, and will present a major maintenance nightmare any time that we need to make changes to the structure of 'table'. So assuming this is the best approach, is there an easy way to copy the data and structure of Inserted into a table variable without explicitly defining the structure?
I don't think that a temp table (which I could otherwise easily insert into) would be a good solution because my limited understanding is that they are far slower than a table variable that lives only inside the trigger. I assume that temp table also must be public, and cause problems if our trigger fires twice and both instances need the temp table.

Related

Temp table doesn't store updated values

I've been trying to create a temp table and update it but when I go to view the temp table, it doesn't show any of the updated rows
declare global temporary table hierarchy (
code varchar(5)
description varchar(30);
INSERT INTO session.hierarchy
SELECT code, 30_description
FROM table1
WHERE code like '_....';
SELECT *
FROM session.hierarchy;
This is a frequently asked question.
When using DGTT with Db2 (declare global temporary table), you need to know that the default is to discard all rows after a COMMIT action. That is the reason the table appears to be empty after you insert - the rows got deleted if autocommit is enabled. If that is not what you want, you should use the on commit preserve rows clause when declaring the table.
It is also very important to the with replace option when creating stored procedures, this is often the most friendly for development and testing, and it is not the default. Otherwise, if the same session attempts to repeat the declaration of the DGTT the second and subsequent attempts will fail because the DGTT already exists.
It can also be interesting for problem determination sometimes to use on rollback preserve rows but that is less often used.
When using a DGTT, one of the main advantages is that you can arrange for the population of the table (inserts, updates ) to be unlogged which can give a great performance boost if you have millions of rows to add to the DGTT.
Suggestion is therefore:
declare global temporary table ... ( )...
not logged
on commit preserve rows
with replace;
For DPF installations, also consider using distribute by hash (...) for best performance.

creating a trigger to update a date in a table before a specific value is changed

I am tryign to create a trigger which will update a table so that when something is inserted into the table the given end date will change to set to the day before the start date of the new entry. i have created this trigger which complies but when checking it doesn't work. can anyone see the porblem with it?
CREATE TRIGGER
updatetrigger12345
BEFORE INSERT ON
shares_amount
FOR EACH ROW
BEGIN
UPDATE OF
shares_amount
SET
date_end = :NEW.date_start-1
WHERE
share_issue_id = :NEW.share_issue_id
AND
share_id= :NEW.share_id
;
END
;
/
Bad, bad, bad. You do not want to attempt to update the table on whose trigger is being executed. You just need to update the ":new" fields of interest. Maybe something like this:
CREATE TRIGGER
updatetrigger12345
BEFORE INSERT ON
shares_amount
FOR EACH ROW
BEGIN
:NEW.date_end := :NEW.date_start-1;
END
;
/
Here is a tutorial. In addition, I would try to really stay away from using trigger if not needed. There certainly are situations when they make sense; however, they can be abused and cause a lot of confusion and problems down the road. Please read this classic AskTom article The Trouble With Triggers.

SQL trigger help on UPDATE of row and column

I am having problems getting a SQL trigger to do what I want. I need to populate a field in a table when another field is updated. More specifically, when an employee ID is entered I need to generate Unicode to make a barcode work. I have written code to generate the unicode, I just need the trigger to function.
This is the skeleton of what I need:
CREATE TRIGGER Unicode
AFTER UPDATE of unique.id
ON table [dbo].table
FOR EACH ROW???
AS
BEGIN
SELECT (unique.id2
now I need to set unique.id2 to the unicode function that I wrote
I feel like I am on the wrong track with this.
Here is an image of the database I am working with (I did not create it) http://i.imgur.com/4wkfY.png. Running SQL server 2008
When PersonalDataID[2] is updated (employee ID). I need to generate code for PersonalDataID[32].
Thank you for any help you can give!
To become proficient in SQL Server, the first thing you need to do is become comfortable with the fact that most operations work best when applied as a set. Thinking in terms of "for each row" will almost always lead you down the wrong path.
In this case, since an update can affect multiple rows, you need to deal with all of those rows at once. You can do this by using the inserted pseudo-table. I looked at your picture but your sample code is quite a mess and I have no idea how to correlate the two. So here is a quick example that you will have to tailor to your actual schema.
CREATE TRIGGER dbo.UpdateUnicodeColumn
ON dbo.table
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET BarcodeColumn = (your calculation, a function, maybe?)
FROM dbo.table AS t
INNER JOIN inserted AS i
ON t.key = i.key;
END
GO
Here's the syntax. Just replace somefield with whatever field you need entered and DestinationTable with the table you want to add the Unicode record to.
CREATE TRIGGER Unicode ON SomeTable
AFTER UPDATE
AS
INSERT INTO DestinationTable
(somefield)
SELECT somefield FROM INSERTED
GO
I don't think you need 'FOR EACH ROW???' :P
CREATE TRIGGER Unicode
ON table [dbo].table
AFTER UPDATE of unique.id
AS
Begin
(SELECT unique.id2...)
Overall reference the answer above.

Oracle - Problem creating trigger that updates another table

I've read the Oracle docs on creating triggers and am doing things exactly how it shows, however this just isn't working. My goal is to update the TPM_PROJECT table with the minimum STARTDATE appearing in the TPM_TRAININGPLAN table. Thus, every time someone updates the STARTDATE column in TPM_TRAININGPLAN, I want to update teh TPM_PROJECT table. Here's what I'm trying:
CREATE TRIGGER Trigger_UpdateTrainingDelivery
AFTER DELETE OR INSERT OR UPDATE OF STARTDATE
ON TPM_TRAININGPLAN
FOR EACH ROW WHEN (new.TRAININGPLANTYPE='prescribed')
BEGIN
UPDATE TPM_PROJECT SET TRAININGDELIVERYSTART = (SELECT MIN(TP.STARTDATE) FROM TPM_TRAININGPLAN TP WHERE TP.PROJECTID = new.PROJECTID AND TP.TRAININGPLANTYPE='prescribed')
WHERE PROJECTID = new.PROJECTID
END;
The trigger is created with no errors, but I do get a warning:
Warnings: --->
W (1): Warning: execution completed with warning
<---
Of course Oracle isn't nice enough to actually tell me what the warning is, I simply am shown that there is one.
Next, if I update the training plan table with:
UPDATE TPM_TRAININGPLAN
set STARTDATE = to_date('03/12/2009','mm/dd/yyyy')
where TRAININGPLANID=15916;
I get the error message:
>[Error] Script lines: 20-22 ------------------------
ORA-04098: trigger 'TPMDBO.TRIGGER_UPDATETRAININGDELIVERY' is invalid and failed re-validation
Script line 20, statement line 1, column 7
Any ideas what I'm doing wrong? Thanks!
A few issues in no particular order.
First, in the body of a row-level trigger, you need to use :new and :old to reference the new and old records. The leading colon is necessary. So your WHERE clause would need to be
WHERE PROJECTID = :new.PROJECTID
Second, if you are running your CREATE TRIGGER in SQL*Plus, you can get a list of the errors and warnings using the SHOW ERRORS command, i.e.
SQL> show errors
You could also query the DBA_ERRORS table (or ALL_ERRORS or USER_ERRORS depending on your privilege level) but that's not something you normally need to resort to.
Third, assuming the syntax errors get corrected, you're going to get a mutating table error if you use this logic. A row level trigger on table A (TPM_TRAININGPLAN in this case) cannot query table A because the table may be in an inconsistent state. You can work around that, as Tim shows in his article, by creating a package with a collection, initializing that collection in a before statement trigger, populating the data in the collection in a row-level trigger, and then processing the modified rows in an after statement trigger. That's a decent amount of complexity to add to the system, however, since you'll have to manage multiple different objects.
Generally, you'd be better off implementing this logic as part of whatever API you use to manipulate the TPM_TRAININGPLAN table. If that is a stored procedure, it makes much more sense to put the logic to update TPM_PROJECT in that stored procedure rather than putting it in a trigger. It is notoriously painful to try to debug an application that has a lot of logic embedded in triggers because that makes it very difficult for developers to follow exactly what operations are being performed. Alternately, you could remove the TRAININGDELIVERYSTART column from TPM_PROJECT table and just compute the minimum start date at runtime.
Fourth, if your trigger fires on inserts, updates, and deletes, you can't simply reference :new values. :new is valid for inserts and updates but it is going to be NULL if you're doing a delete. :old is valid for deletes and updates but is going to be NULL if you're doing an insert. That means that you probably need to have logic along the lines of (referencing Tim's package solution)
BEGIN
IF inserting
THEN
trigger_api.tab1_row_change(p_id => :new.projectid, p_action => 'INSERT');
ELSIF updating
THEN
trigger_api.tab1_row_change(p_id => :new.projectid, p_action => 'UPDATE');
ELSIF deleting
THEN
trigger_api.tab1_row_change(p_id => :old.projectid, p_action => 'DELETE');
END IF;
END;
As Justin Cave have suggested, you can calculate the minimum start date when you need it. It might help if you create an index on (projectid, startdate);
If you really have a lot of projects and training plans, another solution could be to create a MATERIALIZED VIEW that has all the data that you need:
CREATE MATERIALIZED VIEW my_view
... add refresh options here ...
AS
SELECT t.projectid, MIN(t.start_date) AS min_start_date
FROM TPM_TRAININGPLAN t
GROUP BY t.projectid;
(sorry, don't have Oracle running, the above code is just for the reference)

Should I use the template from MS SQL Management Studio to create new triggers?

If you create a new trigger in MS SQL Management Studio by using the GUI, it gives you this template:
--====================================
-- Create database trigger template
--====================================
USE <database_name, sysname, AdventureWorks>
GO
IF EXISTS(
SELECT *
FROM sys.triggers
WHERE name = N'<trigger_name, sysname, table_alter_drop_safety>'
AND parent_class_desc = N'DATABASE'
)
DROP TRIGGER <trigger_name, sysname, table_alter_drop_safety> ON DATABASE
GO
CREATE TRIGGER <trigger_name, sysname, table_alter_drop_safety> ON DATABASE
FOR <data_definition_statements, , DROP_TABLE, ALTER_TABLE>
AS
IF IS_MEMBER ('db_owner') = 0
BEGIN
PRINT 'You must ask your DBA to drop or alter tables!'
ROLLBACK TRANSACTION
END
GO
Should I use this template?
I dont know anything about triggers, but I think I need to use them. The purpose in this case is that on an insert to the table, I need to update one of the fields.
Please help me get started!
OK to begin with that is the wrong template if you want an ordinary trigger that one is a trigger on making structural changes to the table itself.
If you decide to do a trigger that affects data (as opposed to structure), there are several things you need to know. First and by far the most critical, triggers operate on sets of data not one row at time. You must write any trigger to handle multiple row inserts.updates or deletes. If you end up with any code setting the value in inserted or deleted to a variable, there is a 99% chance it will not work properly if multiple records are involved.
What is inserted or deleted you ask? That is the next thing you need to know about triggers, there are two pseudotables (inserted and deleted) that are only available in a trigger (or an output clause) which contain the new information being inserted or the updated values (in the inserted table) and the old information being deleted or being changed by an update (in the deleted table). So an insert has values in inserted, a delete has values in deleted and an update has values in both. Use these in your trigger to pull the values you need to change.
Since you don't know anything about triggers, I would say no, don't use the template.
Read the books online page for Create Trigger and write the trigger by hand.
There is probably more in that template code than you actually need. Read the manual and keep it simple.
If you don't know anything about triggers then I would strongly suggest that you read up on them before implementing them. Get Triggers right and they can make your life a lot easier; get it wrong and Triggers will cause you a lot of trouble.
I would suggest starting off with this tutorial
http://www.sqlteam.com/article/an-introduction-to-triggers-part-i
You can use the above SQL as a template or you can simply write your own. I would suggest you write your own as you'll understand what you are doing. Obviously only do this after you have done some serious reading on triggers. Check out MSDN too