Oracle SQL: how to enforce only one value may be 'checked'? - sql

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;

Related

Automatically fill row with value based on inserted id

I have a table where the user is able to insert the ID of a Node that corresponds to a title elsewhere in the database. I want this tile to be automatically inserted into the row after the user has chosen the id.
This is my table:
I need to have the "SommerhusNavn" column be automatically filled with values based on the "SommerhusId" inserted.
I am using a third party to handle the CRUD functionality, where the user picks the ID from a dropdown. I already know in which table the title for the ID is located, I'm just not sure how to fill the row with the insert statement. Would I need to run a separate query for this to happen?
Edit:Solution
CREATE TRIGGER [dbo].[BlokeredePerioderInsert]
ON dbo.BlokeredePerioder
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE BlokeredePerioder SET SommerhusNavn = text FROM umbracoNode AS umbNode
where SommerhusId = umbNode.id
END
GO
Yes, you need to run additional UPDATE query. Let's assume that you have the TitlesTable, with columns ID and Title. Then it should look like:
UPDATE MyTable SET SommerhusNavn = Title FROM TitlesTable AS A
WHERE SommerhusId = A.ID
AND SommerhusNavn IS NOT NULL --not necessary
Perhaps i'm not understanding, but why can't you use send the value across in the initial update?
Can you use a trigger on the database side?
Alternatively, you'll need to send a update across, following the insert.

Unable to do update table from Apex 5 button Error mentions Cannot Insert NULL

Hi People of the Palace
I am not having luck here and tried a lot of things and seems as if I am not able to get the value from the field which needs to update.
There are 2 fields within Apex 5 which I want to update if something if the value is changed from default. :QUANTITY which is a text field inside of a Tabular form and :DISC which is also a text field in the same row.
There can be single or multiple rows to that needs to be updated and this is usually where you set the option "updated rows and columns only"
The table SALES_TEMP does has columns ID, NAME, QUANTITY_TO_SELL, DISCOUNT.
The PL/SQL code that is assigned in the process to do this update is as follows.
BEGIN
update SALES_TEMP
set QUANTITY_TO_SELL=:QUANTITY, DISCOUNT=:DISC;
end;
When I try and update the fields, it will return with
Cannot insert NULL into QUANTITY_TO_SELL
and similar with the DISCOUNT field.
Now I know there is nothing wrong with the query because if I do this:
BEGIN
update SALES_TEMP
set QUANTITY_TO_SELL='2', DISCOUNT='5';
end;
It does in fact update the table, but it will then do this update to all rows in the table because I have no where clause.
I have had a look through the different options and cannot seem to find why it does not select the data from the fields. My main issue is, I have an exact same query running doing an insert which works.
Also from Apex's Sql Command line option if I run.
update SALES_TEMP
set QUANTITY_TO_SELL=:QUANTITY, DISCOUNT=:DISC;
I get a popup requesting values for :QUANTITY and :DISC and it then updates the columns so something tells me that this is not getting the values from these text fields.
The SQL command to add to populate the fields are
select ID, NAME, QUANTITY_TO_SELL as QUANTITY, DISCOUNT as DISC from SALES_TEMP;
Obviously each gets assigned as :ID, :NAME :QUANTITY and :DISC in apex.
Seeing as you are using Tabular form I suggest you ensure that the following is set.
On the procedure (in processing), Ensure you have the Tabular form selected.
Ensure the Condition "When button is pressed" is set to use the button you want to assign this process to.
in Oracle update clause should ALWAYS have where clause, because oracle will update all data in the table without the WHERE clause.
The cannot insert null error occurs when you are trying to insert in one of the table attributes NULL, where parameter set to NOT NULL, so in your case try to use NVL function, to avoid this issue, I ques that the NAME atribute should not be null.
try to modify your code like this:
update SALES_TEMP
set QUANTITY_TO_SELL = :QUANTITY,
DISCOUNT = :DISC,
NAME = nvl(:NAME,'empty')
where ID = :ID;
in this case it will update only one column.
The Oracle/PLSQL NVL function lets you substitute a value when a null value is encountered.

Change column value after INSERT if the value fits criteria?

I have never really worked with Triggers before in MSSQL but I think it'll be what I need for this task.
The structure of the table is as such:
ID|****|****|****|****|****|****|****|TOUROPERATOR
The Tour Operator Code is the code that tells us what company owned the flight we carried out for them. Two of those codes (there are 24 in total) are outdated. Our users requested that those two be changed but the tour operator code is pulled from a database we don't control. The FlightData table however, we do control. So I was thinking a trigger could change the tour operator code if it was one of the two outdated ones, to the correct ones instead respectively when they were inserted.
So I went into good ol' SQL Management Studio and asked to make a trigger. It gave me some sample code and here is my Pseudo Code below:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER ChangeProvider
ON FlightData
AFTER INSERT
AS
BEGIN
IF(TheInsertedValue == Criteria)
UPDATE FlightData
SET TheInsertedValue = NewValue
ENDIF
END
GO
I am not that good with this type of Database Programming so excuse my mistakes.
How would I go about doing this?
You could add a computed column to your table instead of adding a trigger.
Then the new column could just use a case statement to either show
the original TourOperator column value or the new value you wanted.
You'd add a new column to your table like this
TourOperatorCorrect = CASE WHEN TourOperator = 'Whatever value' THEN 'ChangedValue'
--I just want to use what I have already in the TourOperator column
ELSE TourOperator
END AS VARCHAR(50)
Basics of computed columns are here - https://msdn.microsoft.com/en-ie/library/ms188300.aspx
Your misconception here is that the trigger runs once per inserted value - it is in fact run once per insert statement, so you can and will find more than one row inserted at once.
You'll find that your inserted values are in the pseudo table inserted, which has the same structure as your FlightData table in this case. You write a select statement against that, specifying any criteria you wish.
However, it's not immediately clear what your logic is - does the FlightData table you are updating in your trigger only have one row? Do you update every row in the table with the newest inserted value? It is hard to understand what you are trying to now, and what the purpose of the table and this trigger are - let alone what you would want to do if you inserted more than one row at once.
When inserted table contains mutiple rows,your code will fail,so change code to work with inserted table as whole
UPDATE F
SET f.TheInsertedValue = i.value
from inserted i
join
Flighttable F
on f.matchingcolumn=i.matchingcolumn
and i.somevalue='criteria'

Trigger - After insert and delete example

I have a requirement as a trigger should get fired when any row is inserted or deleted from table FAB which contains num as unique value. and depending upon that num value, another table should be update.
e.g.
FAB table
num code trs
10 A2393 80
20 B3445 780
Reel table
reelnr num use flag
340345 10 500 1
when num 10 from FAB table gets deleted(or any new num gets inserted), the trigger should get fired and should check the reel table which contains that num value and give the reelnr.
How to proceed with this?
you can Use Inserted & Deleted Table in SQL
These two tables are special kind of table that are only available inside the scope of the triggers.
If you tries to use these tables outside the scope of Triggers then you will get Error.
Inserted : These table is use to get track of any new records that are insert in the table.
Suppose there are Six rows are inserted in your table then these table will consist of all the six rows that are inserted.
Deleted : These table is used to track all the Deleted record from your tables.
Last delete rows will be tracked by these table.
For Insert :
CREATE TRIGGER TR_AUDIT_Insert
ON Reel_table
FOR INSERT
AS
BEGIN
INSERT INTO Reel_table (reelnr, num, use, flag)
SELECT
reelnr,
num,
use,
flag
FROM
INSERTED
END
For Delete :
CREATE TRIGGER TR_AUDIT_Delete
ON Product
FOR DELETED
AS
BEGIN
INSERT INTO Reel_table (reelnr, num, use, flag)
SELECT
reelnr,
num,
use,
flag
FROM
DELETED
END
Note :
I don't know from where these three reelnr, use flag values you are getting
So, Please modify this as per your need.
This is the format of the Triggers that normally we use.
You also can do this by using single trigger also
I dont know what is your exact requirement
If you want to achieve by only single Trigger then you can refer this link :
Refer

How to set a default value for one column in SQL based on another column

I'm working with an old SQL 2000 database and I don't have a whole lot of SQL experience under my belt. When a new row is added to one of my tables I need to assign a default time value based off of a column for work category.
For example, work category A would assign a time value of 1 hour, category B would be 2 hours, etc...
It should only set the value if the user does not manually enter the time it took them to do the work. I thought about doing this with a default constraint but I don't think that will work if the default value has a dependency.
What would be the best way to do this?
I would use a trigger on Insert.
Just check to see if a value has been assigned, and if not, go grab the correct one and use it.
Use a trigger as suggested by Stephen Wrighton:
CREATE TRIGGER [myTable_TriggerName] ON dbo.myTable FOR INSERT
AS
SET NOCOUNT ON
UPDATE myTable
SET
timeValue = '2 hours' -- assuming string values
where ID in (
select ID
from INSERTED
where
timeValue = ''
AND workCategory = 'A'
)
Be sure to write the trigger so it will handle multi-row inserts. Do not process one row at a time in a trigger or assume only one row will be in the inserted table.
If what you are looking for is to define a column definition based on another column you can do something like this:
create table testable
(
c1 int,
c2 datetime default getdate(),
c3 as year(c2)
);
insert into testable (c1) select 1
select * from testable;
Your result set should look like this :
c1 | c2 | c3
1 | 2013-04-03 17:18:43.897 | 2013
As you can see AS (in the column definition) does the trick ;) Hope it helped.
Yeah, trigger.
Naturally, instead of hard-coding the defaults, you'll look them up from a table.
Expanding on this, your new table then becomes the work_category table (id, name, default_hours), and you original table maintains a foreign key to it, transforming fom
(id, work_category, hours) to (id, work_category_id, hours).
So, for example, in a TAG table (where tags are applied to posts) if you want to count one tag as another...but default to counting new tags as themselves, you would have a trigger like this:
CREATE TRIGGER [dbo].[TR_Tag_Insert]
ON [dbo].[Tag]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Tag
SET [CountAs] = I.[ID]
FROM INSERTED AS I
WHERE I.[CountAs] IS NULL
AND dbo.Tag.ID = I.ID
END
I can think of two ways:
triggers
default value or binding (this should work with a dependency)
Triggers seem well explained here, so I won't elaborate. But generally I try and stay away from triggers for this sort of stuff, as they are more appropriate for other tasks
"default value or binding" can be achieved by creating a function e.g.
CREATE FUNCTION [dbo].[ComponentContractor_SortOrder] ()
RETURNS float
AS
BEGIN
RETURN (SELECT MAX(SortOrder) + 5 FROM [dbo].[tblTender_ComponentContractor])
END
And then setting the "default value or binding" for that column to ([dbo].ComponentContractor_SortOrder)
Generally I steer away from triggers. Almost all dbms have some sort of support for constraints.
I find them easier to understand , debug and maintain.