SQL Oracle - Double CONSTRAINT NOT NULL - oracle

I need to create a table in PL/SQL and this table need to have a CONSTRAINT on two attribut. I explain:
One of this two objects "com_name" and "com_nickname" need to be checked, if the both are it's ok, but at least one need to be filled.
I'm a beginner and I can't understand how can I make it work
*
CONSTRAINT ch_com_name_nickname CHECK (com_name = NOT NULL
OR com_nickname = NOT NULL)
*
This is not working.

The correct syntax is column_name IS NOT NULL. You don't need the =.

Why do u want do it by CONSTRAINT?!
This kind of task solves not like that.
You can simply check it in your logic (in procedure or function).

Related

how to use triggers to inherit values from another "parent" record in same table

I feel like this is one of those "if you're careful you can do it" scenarios that Oracle just doesn't want to let me do.
My problem is that I have a single configuration table that I want to enable inheritance via Triggers. Think an Employee table with a SUPERVISOR ID column, and 'inherited' SUPERVISOR NAME that self populates if the ID is changed.
I'd like to do a simple self-lookup to capture a value from another row at time of INS/UPD. But Oracle is rejecting as a mutating trigger error.
My code is essentially:
TRIGGER UPD_CHILD_RECORD
BEFORE INSERT OR UPDATE
ON MYSCHEMA.FAKE_EMPLOYEE
FOR EACH ROW
WHEN (NEW.SUPERVISOR_ID IS NOT NULL)
BEGIN
IF INSERTING OR UPDATING
THEN
:NEW.SUPERVISOR_NAME = (
SELECT MAX(NAME)
FROM MYSCHEMA.FAKE_EMPLOYEE
WHERE EMPLOYEE_ID = :NEW.SUPERVISOR_ID
);
END IF;
END UPD_CHILD_RECORD
;
thanks.
This is a normal behavior. Oracle protects you from inconsistent data that you may get accessing a table which is already being updated.
Imagine this scenario.
You submit two update statements and have a trigger that selects from that same table. Let's assume that the first statement is successfully applied and the data gets changed. Now it's time for the second statement. What output would you expect from the select statement in the trigger? Should it return data as it was before the first update, or should it include the changes made? You probably think that Oracle should return the new data. But first, Oracle does not really know your intentions, and second, that would mean that your query is dependent on row order, which contradicts the relational algebra.
The solution for your problem is quite simple. You do not need the SUPERVISOR_NAME column at all. To get supervisor's name, simply join the table with itself and get the desired result, something like:
select t1.ID, t1.SUPERVISOR_ID, t2.NAME from FAKE_EMPLOYEE t1
left join FAKE_EMPLOYEE t2 on t1.SUPERVISOR_ID = t2.ID;

PL/SQL Trigger on INSTERT OR UPDATE : ":NEW" IS NULL => ambiguous?

I'm quite a newbie in PL/SQL and I'm trying to do quite complex data integrity checks via triggers.
I've already understood how to avoid problems when calling a table inside a trigger that is used on the same table (via a temporary external table) but now I'm facing a really mind-blowing problem : I thought that ":NEW" was referencing the value in my table AFTER an update but things don't look that simple... It is the new value SET by the update or insert... which looks to be NULL if nothing has been specified, even if the corresponding field value is NOT NULL after the update... wich is driving me crazy.
My trigger is set when inserting or updating several variables :
CREATE OR REPLACE TRIGGER TRG_INS_UP_INSTRUMENT_EVENT
AFTER INSERT OR UPDATE OF EVENT_ID, DATE_BEGIN,DATE_END,INSTR_ID,TYPE_EVENT_ID ON AIS_INSTRUMENT_EVENT
But now... If there already is a line with non-null fields and I do an
UPDATE AIS_INSTRUMENT_EVENT SET INSTR_ID='642' WHERE EVENT_ID='6479'
I actually get a ":NEW.DATE_BEGIN" which is NULL... event thought nor the older or newer values are NULL (because I just didn't update it).
How can I distinguish - in my trigger - the case when the DATE_BEGIN is updated and SET voluntary to NULL from the case in which nothing has been specified (and this field must thus remain the same but not necessarily NULL...). I have to many possible combination to check one by one...
Thanks in advance for your help!
What you are saying is not true. :new contains the full row regardless whether the column is referenced in the UPDATE statement:
CREATE TABLE test (test INTEGER, last_changed DATE);
CREATE OR REPLACE TRIGGER TRG_INS_UP_TEST
AFTER INSERT OR UPDATE OF test, last_changed ON test
FOR EACH ROW
BEGIN
dbms_output.put_line('LAST CHANGED IS ' || :new.last_changed);
END;
INSERT INTO test (test, last_changed) VALUES (1, SYSDATE);
COMMIT;
UPDATE test SET test = test + 1;
DBMS Output:
LAST CHANGED IS 01.09.17
To achieve what you want the mechanism works slightly different. You have to look at two different use cases:
1.) You want the trigger not to fire unless a certain column is mentioned. This use cases is by the reference in the trigger declaration (INSERT OR UDATE OF "column_name"). If the INSERT/UPDATE statement only affects columns that are not mentioned the trigger will not fire.
2.) You want the trigger not to fire unless a certain row is modified. So you want the trigger to only if fire is a value has actually changed. This is done by the WHEN restriction of the trigger. It is usually used in conjunction with DECODE, like so:
CREATE OR REPLACE TRIGGER TRG_INS_UP_TEST
AFTER INSERT OR UPDATE OF test, last_changed ON test
FOR EACH ROW
WHEN (DECODE(new.test,old.test,0,1)=1 OR DECODE(new. last_changed,old. last_changed,0,1)=1)
BEGIN
...
END;
So to answer your original question: If you want to the trigger too only fire in cases where the column DATE_BEGIN is set to NULL you will have to declare your trigger using both approaches
CREATE OR REPLACE TRIGGER TRG_INS_UP_INSTRUMENT_EVENT
AFTER INSERT OR UPDATE OF DATE_BEGIN ON AIS_INSTRUMENT_EVENT
FOR EACH ROW
WHEN (DECODE(new.DATE_BEGIN,old. DATE_BEGIN,0,1)=1 AND new.DATE_BEGIN IS NULL)
The limitation to certain columns ("INSERT OR UPDATE OF DATE_BEGIN") is not strictly necessary but it is good practice since it improves performance since it excludes the trigger from firing at all.
Sorry I think I made a to quick conclusion... The bug was mine. I've tested on a "Toy" table and, indeed, the :NEW was not null, even when not set by the UPDATE. I found the bug in the meantime. All this is too new to me ;-).
Sorry for disturbing.

ORACLE Mutating Table error in one trigger, but not another; Why?

Okay, I have two tables - ORDERS and ORDERLINES - which have essentially the same problem, with triggers on each to address the issue. The issue is that in addition to the PK with table-level uniqueness, on a field called RECID, there is another field, RECNO, which needs to be unique with relation to another field.
The tables are FK related as follows:
ORDERS.WAREHOUSEID > WAREHOUSES.CUSTOMERID > CUSTOMERS
and
ORDERSLINES.ORDERID > ORDERS
On ORDERS and ORDERSLINES I have BEFORE INSERT triggers to assign the realm-specific unique RECNO.
In ORDERS, RECNO needs to be unique within the realm of a CUSTOMERS record.
In ORDERLINES, RECNO needs to be unique within the realm of an ORDERS record.
The trigger on ORDERS works perfectly fine. When a new order is inserted, it is assigned the next unique RECNO within the customer it belongs to.
The trigger on ORDERLINES on the other hand, which should assign the next unique RECNO within the order it belongs to, throws the dreaded {ORA-04091: table ORDERLINES is mutating, trigger/function may not see it} exception.
Here is the trigger that works:
CREATE OR REPLACE TRIGGER ORDERS_BI
BEFORE INSERT ON ORDERS
FOR EACH ROW
DECLARE
CUSTID WAREHOUSES.CUSTOMERID%TYPE;
BEGIN
SELECT MIN(CUSTOMERID) INTO CUSTID FROM WAREHOUSES
WHERE NVL(WARE_ID, '-') = NVL(:NEW.WAREHOUSEID, '-');
SELECT NVL(MAX(RECNO), 0) + 1
INTO :NEW.RECNO
FROM deploy.ORDERS O
LEFT JOIN deploy.WAREHOUSES W
ON NVL(W.REC, '-') = NVL(O.WAREHOUSEID, '-')
WHERE NVL(W.CUSTOMERID, '-') = NVL(CUSTID, '-');
END;
And here is the trigger that does NOT work:
CREATE OR REPLACE TRIGGER ORDERLINES_BI
BEFORE INSERT ON ORDERLINES
FOR EACH ROW
DECLARE
nORDERID ORDERLINES.ORDERID%TYPE;
BEGIN
SELECT MIN(ORDERID) INTO nORDERID FROM REVORDERS
WHERE ORDERID = :NEW.ORDERID;
SELECT NVL(MAX(RECNO), 0) + 1
INTO :NEW.RECNO
FROM deploy.ORDERLINES L
LEFT JOIN deploy.ORDERS O
ON O.ORDERID = L.ORDERID
WHERE O.ORDERID = nORDERID;
END;
Can SOMEONE please explain WHY the first one works, and the second one doesn't?
And is there some way I can re-write the second to make it work?
I looked at your code first, rather than your explanation. My first thought was "this person is trying to fake a sequence." This obviously isn't the answer to your question but it's the reason you're getting into trouble in the first place.
The obvious solution when you're having problems faking sequences is to use a real one.
As Nicholas has already noted ORA-04091 occurs when you try to read from the table from which a trigger is fired. There are various ways to avoid this, most of which avoid trying to do something slightly funky. However, they don't influence the root cause of the error; that is you're doing something wrong. This error is normally indicative of one or both of two things:
You're putting far too much logic into a trigger
Your data-model is flawed.
The solution to the first is to move the logic to a package, which has the added benefit of removing a layer of obfuscation. The solution to the second is to normalise your database properly.
In your case, from what information you've provided, your data-model seems to be okay, though as I've said I disagree with the implementation.
This leaves you with four options to solve your problem, which I detail in order I would do them
Remove your triggers.
Replace your current logic with a sequence.
Remove all your trigger logic into a procedure.
Hack around your error.
I'm not going to discuss point 3 as you can do that yourself. Nicholas has partially covered point 4 and I'm not going to advocate something I disagree with. This leaves points 1 and 2. You say
In ORDERS, RECNO needs to be unique within the realm of a CUSTOMERS
record.
This is not how you've implemented it. Your code makes RECNO consecutive within the realm of a CUSTOMERS record. The primary key of both ORDERS and ORDERLINES are by definition unique within the realm of a CUSTOMERS record.
In itself, this implies that option 1 is best for you. Remove the triggers entirely; the primary keys of the table are already doing everything you need. This also invalidates option 2; if you add a sequence then it will basically be a separate primary key.
There is no reason I can think of that you would need an order to be consecutively unique within each customer; why bother doing so?
You are getting that error because the second trigger is trying to read table while it is being modified. This can also happen when a trigger on a parent table causes an insert on a child table referencing a foreign key.
As a quick work around create view and try to use instead of trigger.
Also take a look at Tom's example of how to deal with mutating issues.
Besides, if leave the second trigger as it is, any inserts into your_table select .. from table will raise mutating error. For example:
This insert will work
insert into ORDERLINES(column1, column2... columnN)
values(val1, val2,..., valN)
But this one wont.
insert into ORDERLINES(column1, column2... columnN)
select val, val..val from table

Oracle: complex constraint involving other records

Is there any way to make oracle checks other records of a table by a constraint?
Let's take an example:
I've got a table called ENI_TRASC_VOCI_PWR_FATT and I want that every record having tvp_regione not null has a similar record having tvp_regione = null.
For similar record I need to check it has the same value on the TVP_CODICE_ASSOGGETAMEN colum.
The only method I can think of is to use a fast commit on refresh materialised view, defined with a query something like:
select
tvp_codice_assoggetamen,
count(*) rows_per_tca,
count(tvp_regione) tvp_regione_per_tca
from
eni_trasc_voci_pwr_fatt
group by
tvp_codice_assoggetamen
/
Place regular check constraints on the MV table, such that tvp_regione_per_tca = 1 if rows_per_tca = 2 (your requirement is not quite clear to me).
That is generally the only safe way of implementing such a multi-row constraint in Oracle, short of locking the table for changes before modifying it and using code to check.
According to Oracle docs :
Conditions of check constraints cannot contain the following
constructs:
Subqueries and scalar subquery expressions
So You'll probably have to use a trigger instead:
create or replace trigger trg
before insert or update on ENI_TRASC_VOCI_PWR_FATT
for each row
begin
-- do whatever queries you need - I didn't understand what you want
if <some condition> then
raise_application_error(-20000,'no good');
end if;
end;
But be carefull ! a trigger is not like a constraint - think what will happen if two users update the table and so on ....

Peoplecode, SQLEXEC not retrieving correct data

<-------PeopleCode------>
Hi,
I have a SQL query that i have tried executing using both SQLEXEC and SQL.fetch() but the problem is, when I am passing the values to parameters (:1,:2...) it does not return a row but when I hardcode the values in the where clause of the query itself, it retrieves the correct value.
Can anybody help?
My query looks similar to the following sample query :
Select * from PS_rec1 where emplid=:1 and plan_type=:2
it returns no data till i hardcode the values.
I have checked the values at the back end and some data is there to be fetched. Moreover, the same query retrieves data when ran in TOAD.
Have you tried outputting your binds to a log file just before you use them in your SQL statement?
If the binds aren't working, but literals are, then perhaps your binds don't contain the values that you expect them to.
You could also try explicitly setting the binds to the values that you're expecting just before the SQL statement. This will prove that the way you're passing in the binds is working correctly.
It required another update to the same record to get the values fetched in SQL exec.
M not sure what was the problem but i guess it might be that the previous update did not write the changes to the db even after an explicit commit.
Ok, you need to put your exact SQLExec statement in the question.
But, do you really have "Select * ..." in a SQLExec? How many columns are in your table? Since you mention the where clause, is your statement
SQLExec("select * from PS_rec where emplid=:1 and plan_type=:2", &var1, &var2, &vartocontainthewholerow);
Which will work in a SQL tool (toad) but probably does not work in AE or any type of Peoplecode program.
Now if your table has three columns, should you not have something like this:
SQLExec("select emplid, plan_type, column3 from PS_rec where emplid = :1 and plan_type=:2", &emplidIn, &plan_typeIn, &emplidOut, &plan_typeOut, &column3Out);
Notice that with three columns in the table that emplid and plan_type are two of them, you need to list all the columns you want, not asterisks '*'. Kind of silly to select the emplid and plan_type though.

Resources