I want to make a trigger to change the values of BarcodeFormat column.
So I made a trigger, but I am not sure if it works.
Here is the code:
CREATE trigger tr_changedUPC
on Item
after update
as update Item
SET BarcodeFormat=(case when(ISNUMERIC(ItemLookupCode)=1) AND
(LEN(ItemLookupCode)=12) AND (NOT(BarcodeFormat=9)) then 9 else 6 end) from inserted
As you can see, I am making a trigger on item Table. When the value of column Item.ItemLookupCode changes, I want to change the value of column Item.BarcodeFormat, too.
I didn't execute this SQL code yet. So I want you to see this code if it is good.
According to this SQL Fiddle,
CREATE trigger tr_changedUPC
on Item
after update
as update Item
SET BarcodeFormat=(case when(ISNUMERIC(ItemLookupCode)=1) AND
(LEN(ItemLookupCode)=12) AND (NOT(BarcodeFormat=9)) then 9 else 6 end) from inserted
your code should be a little improved in this way, to make columns less ambiguous:
CREATE TRIGGER tr_changedUPC
on Item
after UPDATE
as UPDATE Item
SET Item.BarcodeFormat=(case when(ISNUMERIC(Item.ItemLookupCode)=1) AND
(LEN(Item.ItemLookupCode)=12) AND (NOT(Item.BarcodeFormat=9)) then 9 else 6 end) from inserted;
Your code is fine, according to the results of the fiddle, because the values of column 'BarCodeFormat' are updated accordingly with the update of column 'ItemLookupCode'.
Related
So I have a table in a database which contains the column "SELECTED". The values in this column can only be "CHECKED" or "UNCHECKED". I would like to enforce "CHECKED" can only be used once (like a radiobutton) through a PL/SQL trigger, though I cannot think of how to do this.
First, the idea (in case it didn't become clear):
Initial table "dummy":
ID | SELECTED
--------------
1 | 'UNCHECKED'
2 | 'CHECKED'
3 | 'UNCHECKED'
Then, I execute this query:
UPDATE dummy
SET SELECTED = 'CHECKED'
WHERE ID = 3;
Through a PL/SQL trigger, I'd like to have my table "dummy" to look like this after the execution:
ID | SELECTED
--------------
1 | 'UNCHECKED'
2 | 'UNCHECKED'
3 | 'CHECKED'
I hope you get the idea. I myself have tried to solve this, without success. I came up with the following code:
CREATE OR REPLACE TRIGGER DUMMY_ONE_CHECKED
AFTER INSERT OR UPDATE ON DUMMY
FOR EACH ROW
DECLARE
v_checked_is_present DUMMY.SELECTED%TYPE;
BEGIN
SELECT SELECTED
INTO v_checked_is_present
FROM DUMMY
WHERE SELECTED = 'CHECKED';
IF v_checked_is_present IS NOT NULL THEN
UPDATE DUMMY
SET SELECTED = 'UNCHECKED'
WHERE SELECTED = 'CHECKED';
UPDATE DUMMY
SET SELECTED = 'CHECKED'
WHERE ID = :NEW.ID;
END IF;
END;
However, I get the errors ORA-04091, ORA-06512 and ORA-04088 with the following message:
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
Clearly, this is not the right solution. I wonder how I could accomplish what I would like to do (if possible at all)?
Thank you in advance!
I would not design it that way. The database should enforce the rules, not automatically attempt to fix violations of them.
So, I'd enforce that only one row can be CHECKED at a time, like this:
CREATE UNIQUE INDEX dummy_enforce_only_one ON dummy ( NULLIF(selected,'UNCHECKED') );
Then, I'd make it the responsibility of calling code to deselect other rows before selecting a new one (rather than trying to have a trigger do it).
I know that doesn't answer the text of your question, but it does answer the title of your question: "how to enforce only one value..."
I'm not sure a trigger is the best approach to this problem. The trigger needs to update all the records for every update -- even worse, the rows are in the same table leading to the dreaded mutating table error.
How about a different table structure altogether? The idea is just to keep track of the last time something was "checked" and then use the maximum timestamp:
create table t_dummy (
id int,
checkedtime timestamp(6)
);
create view dummy as
select t_dummy.id,
(case when checkedtime = maxct then 'CHECKED' else 'UNCHECKED') as selected
from t_dummy cross join
(select max(checktime) as maxct from t_dummy) x;
This should be simpler to implement than a trigger.
One way to implement this is to use a COMPOUND TRIGGER. A compound trigger is one which has code which fires at each of the possible triggering points (BEFORE STATEMENT, BEFORE ROW, AFTER ROW, and AFTER STATEMENT). Let's look at how to handle your requirement:
CREATE OR REPLACE TRIGGER DUMMY_CHECKED_TRG
FOR INSERT OR UPDATE ON DUMMY
COMPOUND TRIGGER
TYPE NUMBER_TABLE IS TABLE OF NUMBER;
tblDUMMY_IDS NUMBER_TABLE;
BEFORE STATEMENT IS
BEGIN
tblDUMMY_IDS := NUMBER_TABLE();
END BEFORE STATEMENT;
AFTER STATEMENT IS
BEGIN
IF tblDUMMY_IDS.COUNT > 0 THEN
UPDATE DUMMY d
SET d.SELECTED = 'UNCHECKED'
WHERE d.ID <> tblDUMMY_IDS(tblDUMMY_IDS.LAST) AND
d.SELECTED = 'CHECKED';
END IF;
END AFTER STATEMENT;
AFTER EACH ROW IS
BEGIN
-- If the new value of `SELECTED` on this row is 'CHECKED'
-- save the ID of the row in tblDUMMY_IDS
IF :NEW.SELECTED = 'CHECKED' THEN
tblDUMMY_IDS.EXTEND;
tblDUMMY_IDS(tblDUMMY_IDS.LAST) := :NEW.ID;
END IF;
END AFTER EACH ROW;
END TABLE1_NUM_TRG;
In the BEFORE STATEMENT portion of the trigger we just allocate a table (variable length collection object) to hold ID values. This portion of the trigger is executed once, before any rows have been processed by the trigger.
In the AFTER EACH ROW section of the trigger we look at the SELECTED field of the row, and if it's 'CHECKED' we save its ID value in the table we allocated earlier.
The AFTER STATEMENT section of the trigger is where the real work gets done - and it's only a single SQL statement. The reason we defer the real work of the trigger until the AFTER STATEMENT section is because code which executes here will not raise the dreaded "MUTATING TABLE" exception. What we do is we take the last ID value which we found was associated with a row which had SELECTED = 'CHECKED'. This is the row which we want to remain CHECKED - every other row in the table should be UNCHECKED. So we execute an UPDATE statement, saying in effect "set SELECTED to 'UNCHECKED' on every row in the table whose ID is not the one we've got, and whose current value of SELECTED is CHECKED". Normally this will only update one row - but it will handle the case where in a single SQL statement sets a bunch of rows to CHECKED.
I believe compound triggers became available in 10g, so if you're on that version of Oracle or later you should be good.
Hope this helps.
Best of luck.
As I understand, you want to have only one row in the whole table, which could contain CHECKED value. But your way will not work.
I've just invented a new way how to do this. Maybe, it is a bit complicated way. Change your selected column type to number, and fill it with consequent numbers (for example, with sequence). Then consider column with maximal value as "selected". This gives you a lot of advantages: to change selected row you just need to take next value from a sequence and put it in desired row (you don't need to update all rows), you need only one query for that, and you never meet mutation problem. Disadvantages - quite hard to get selected row and impossible (hard) to "deselect all".
Alternative approach using table with one row only (enforced with PK). The `BUTTON_ID contains the ID of the selected button (1-3 or NULL if all buttons are un-checkedd). The button per row result is presented in a view.
create table button
(
id number primary key check (ID in (1)),
button_id number check (button_id in (1,2,3))
);
create view v_button as
with r3 as (select rownum button_id from dual connect by level <= 3)
select
case when button.button_id = r3.button_id then 'SELECTED' else 'UNSELECTED' end as button_code
from r3 cross join button
;
Initialize with
insert into button values(1,1);
gives
select * from v_button;
BUTTON_CODE
-----------
SELECTED
UNSELECTED
UNSELECTED
Switch simple with an update statement:
update button set button_id = 3;
gives
BUTTON_CODE
-----------
UNSELECTED
UNSELECTED
SELECTED
To de-select all simple set to NULL
update button set button_id = NULL;
BUTTON_CODE
-----------
UNSELECTED
UNSELECTED
UNSELECTED
Would be easier if you knew what needed to be unchecked. But if you can't, then unckeck everything.
UPDATE dummy
SET SELECTED = 'UNCHECKED';
Then check the one that you know should be checked.
UPDATE dummy
SET SELECTED = 'CHECKED'
WHERE ID = 3;
Why not use a boolean for this?
--EDIT-- (boolean example)
UPDATE dummy
SET SELECTED = 0;
Then check the one that you know should be checked.
UPDATE dummy
SET SELECTED = 1
WHERE ID = 3;
I have the following code (in sql server - via 2012): i can't seem to get it right. any suggestions.
Table:
select top 1000 [supplier],
[item],
[reorder_level],
[current_inventory],
[reorder],
from [clinic].[dbo].[emr_suppliers]
I'm working on a trigger and a bit stuck.
CREATE TRIGGER reorder_supplies
ON emr_suppliers
After insert, update
As BEGIN
update emr_suppliers
set reorder = 'yes'
where (emr_suppliers.reorder = emr_suppliers.current_inventory or emr_suppliers.reorder > emr_suppliers.current_inventory)
update emr_suppliers
set reorder = 'no'
where emr_suppliers.reorder < emr_suppliers.current_inventory
END
What the trigger has to do is compare the Current Inventory with the Reorder Level column, and if the value of the Current Inventory is equal to or less than the Reorder Level, it will put a value of Yes in the Reorder column, and if it is not, then it will put a No value instead.
The trigger itself looks syntactically correct.
However, I don't think it's a solution with a decent performance since each and every row of the emr_suppliers table is touched twice, even though there was no data change at all for most of the rows (e.g. after insert of a new row or update of a single value).
I'd use a solution based on the internal inserted table together with a CASE expression:
UPDATE emr_suppliers
SET reorder =
CASE WHEN emr_suppliers.reorder < emr_suppliers.current_inventory THEN 'no'
WHEN emr_suppliers.reorder >= emr_suppliers.current_inventory THEN 'yes'
ELSE reorder -- don't change the value
END
FROM emr_suppliers INNER JOIN inserted ON emr_suppliers.primary_key = inserted.primary_key
I am trying to create a database trigger that will update certain characters in a field for a table when a user inserts data into the table...
Ex.
ID EXCHANGE LEADRT
1 new L-3
2 new 3
3 new 5
So I would want to leave id 1 alone because the format for the LEADRT is correct but ids 2 and 3 are not.
CREATE TRIGGER triggerupdate ON PoleUnits FOR INSERT,
UPDATE AS
if not exists (select * from Poleunits where LEADRT like '%L-%')
update PoleUnits set LEADRT = STUFF (LEADRT, 1, 0,'L-');
Any ideas why I can't get this to work or better suggestions on how to accomplish this?
In insert and update triggers you have access to a specific table called inserted where the rows to be inserted/updated are held. Those are not real tables, they are just logical tables with the same structure as the table on which the trigger fired.
Your current logic works on the original table, thus working with all the existing data, but not with the data you are actually inserting, i.e. it will update everything except the data you actually want updated. Something like this could work:
CREATE TRIGGER triggerupdate ON PoleUnits
FOR INSERT, UPDATE AS
update PoleUnits
set LEADRT = STUFF (PoleUnits.LEADRT, 1, 0,'L-')
from PoleUnits
inner join inserted -- this is basically a self join
on PoleUnits.ID = inserted.ID
where PoleUnits.LEADRT not like '%L-%'
This will only update those rows in PoleUnits that are being inserted, and only if their LEADRT field is not in the L- format.
I have 2 tables (1) product_warehouse and (2) Return_Vendor Invoice.
i have to update the quantity in product_warehouse by trigger according to the value of Return_Vendor Invoice Table.
where Item_code is unique key in both tables.
For example if the product_warehouse contain 3 quantities , and the shopkeeper returns 1 quantity to vendor then it should be 2 in the Product_warehouse. update query will also acceptable.
create or replace trigger tiuda_return_vendor after insert or update or delete
on return_vendor is
begin
update product_wherehouse
set
quantity = quantity - (:new.quantity - nvl(:old.quantity, 0)
where
item_code = nvl(:new.item_code, :old.item_code);
end;
This trigger works in most cases. You can insert update or delete the return line.
Only thing is: when updating, you cannot update the item_code itself, because the update statement doesn't take that into account. You could easily solve that, but I don't know if it's in your requirements. I usually don't change values like that, but rather remove the item and add a new line for a different item.
Updating the quantity works fine. If you update the quantity, the difference between old and new is calculated and that difference is used to modify the stock quantity in the wherehouse.
create or replace
TRIGGER "WR_RETURN_INVOICE_UPDATE_TRG"
AFTER UPDATE ON RETURN_INVOICE
FOR EACH ROW
BEGIN
UPDATE PRODUCT_WAREHOUSE
SET QUANTITY=QUANTITY-:OLD.QUANTITY
WHERE ITEM_CODE=:OLD.ITEM_CODE;
END WR_RETURN_INVOICE_UPDATE_TRG;
Try this one it will works.
UPDATE UPI_ATTRIBUTE SET SITE_INC ='0'
WHERE USER_PROFILING_NAME IN ('CAR_IMPLICIT','CAR_EXPLICIT')
Above is my query that I am using to update the columns in UPI_ATTRIBUTE table. And suppose if I need to rollback the above changes that I am doing with update query, then how can I use the delete query in the above case? Currently SITE_INC is empty. So after updating the table it will have 0 in that. And If I need to rollback the changes then I need to make it empty again.
How can I delete the value 0 from above column after updating.
You cannot "delete" data to rollback an update.
Even if the only change that you'll do is set a flag from "NULL" to "Y" and vice versa, you can't really determine which value to update when you do multiple updates.
update 1 : set from "NULL" to "0"
update 2 : set from "0" to "something else"
update 3 : Rollback changes (rollback to which state)?
If you want to really rollback changes to specific rows (instead of the complete table, which can be done using Flashback), you can audit the changes to "remember" the values and then revert back to them.
Even then, you should consider which point to revert to, when you have multiple updates.
Not sure Oracle have final table/old table function for select/update.
For DB2 for z/OS, it have such function:
SELECT LASTNAME, BONUS FROM FINAL TABLE
(UPDATE EMP SET BONUS = BONUS * 1.3
WHERE JOB = 'CLERK');
Then, the result set of the select include all the updated rows. you can use rownum or other unique value, then you can update it back to any value as you want.