PL/SQL ORA-01400: cannot insert NULL into when using CASE - oracle

I have the following PL/SQL Block:
declare
vDayType varchar2(10);
TYPE Holidays is table of varchar2(5);
hd Holidays := Holidays('01.01','15.01','19.01','28.05','04.07','08.10','11.11','22.11','25.12');
begin
for s in (select distinct saleDate from Sales) loop
vDayType := case when TO_CHAR(s.saleDate, 'dd.mm') member of hd then
'Holiday'
when to_char(s.saleDate, 'd') IN (1,7) then
'Weekend'
else
'Weekday'
end;
insert into times (saleDay, dayType) values (s.saleDate, vDayType);
end loop;
end;
/
This pulls data from a OLTP Table named SALES and inserts it into the dimension table named TIMES. It incorporates a CASE statement to "calculate" days that are Holidays, Weekdays, or Weekends. Unfortunately, when I run this code, I'm getting the following error:
ERROR at line 1:
ORA-01400: cannot insert NULL into ("CM420A01"."TIMES"."SALEDAY")
ORA-06512: at line 14
I believe it is inserting NULL because I have it set to only SELECT DISTINCT values from saleDate in the SALES OLTP Table. I'm assuming it's still trying to insert the dayType from the CASE statement, even when it's not inserting a saleDay because of the DISTINCT statement, which is thus inserting NULL into the saleDay column and causing the error.
Any tips/tricks to recover from this issue so it'll run without error?

Added WHERE SALES.vehicleStatus = 'sold' clause due to NULL values that were being inserted for vehicles what were listed as pending or available and didn't yet have saleDate's......

Related

Error unique constraint and during execution of trigger oracle

I have form in which user add information of new born baby with his/her head family name. When add information into table then getting following errors
ORA-00001: unique constraint (PK) violated
ORA-06512: at trigger_name, line 21
ORA-04088: error during execution of trigger
Trigger:
CREATE OR REPLACE TRIGGER "DB_NAME"."TRG_NBB"
AFTER INSERT ON baby
FOR EACH ROW
WHEN (new.status = 'A') DECLARE
v_1 tab_1.col_1%type;
v_2 tab_2.col_2%type;
v_3 tab_2.col_3%type;
v_4 tab_2.col_4%type;
v_5 tab_2.col_5%type;
v_6 date;
newmofid number;
BEGIN
select max(nvl(col_2,0))+1 into newmofid from tab_2;
SELECT distinct col_1,col_2,to_char(col,'DD-MM-YYYY') INTO v_1,v_2,v_6
from table
where tcid = :new.tcid;
SELECT col_4,col_5,col_3 into v_4,v_5,v_3
from tab_2
where col_1 = v_1
and col_2 = v_2;
INSERT INTO tab_2 (all_columns)
VALUES(variable_names);
DBMS_OUTPUT.PUT_LINE('New Born Baby successfully added to member table');
END trg_nbb;
/
ALTER TRIGGER "DB_NAME"."TRG_NBB" ENABLE;
When I execute this sql query It's take 4 to 5 seconds and increment in values very quickly
select max(nvl(col_2,0))+1 into newmofid from tab_2;
Result:
6030819791
Again execute takes 3 to 4 seconds
Result:
6030819798
How to solve this problem?
Thanks
I suspect it is MAX + 1 that causes problems:
select max(nvl(col_2,0))+1 into newmofid from tab_2;
Such a principle is in most cases wrong and will fails, especially in a multi-user environment where two (or more) users at the same time fetch the same MAX + 1 value, do some processing, and - at the time of insert - one of them succeeds (because it is the first), but the rest of them fail because such a value already exists in the table.
I suggest you switch to a sequence, e.g.
create sequence seq_baby;
and then, in your form, do
select seq_baby.nextval into newmofid from dual;

concurrency in oracle plsql

I have a PL/SQL package in Oracle that its important function is :
function checkDuplicate(in_id in varchar2) return boolean is
cnt number;
begin
select count(*)
into cnt
from tbl_Log t
where t.id = in_id
if (cnt > 0) then
// It means the request is duplicate on in_id
return false;
end if;
insert into tbl_log (id,date) values(in_id , sysdate);
return true;
end;
When two requests call this function concurrently, both of them passed this function and two the same in_id inserted in tbl_log.
Note: tbl_log doesn't have a PK for performance issues.
Are there any solutions?
" both of them passed this function and two the same in_id inserted in tbl_log"
Oracle operates at the READ COMMITTED isolation level, so the select can only find committed records. If one thread has inserted a record for a given value but hasn't committed the transaction another thread looking for the same value will come up empty.
"Note: tbl_log doesn't have a PK for performance issues. "
The lessons of history are clear: tables without integrity constraints inevitably fall into data corruption.
"I want to recognize the duplication with this function ... Are there any solutions?"
You mean apart from adding a primary key constraint? There is no more efficient way of trapping duplication than a primary key. Maybe you should look at the performance issues. Plenty of applications mange to handle millions of inserts and still enforce integrity constraints. You should also look at the Java layer: why have you got multiple threads submitting the same ID?
Note: tbl_log doesn't have a PK for performance issues.
There is no PK nor unique index on this column in order to "avoid performance issues", but there are hundreds or thousands queries like SELECT ... WHERE t.id = .. running against this table. These queries must use a full table scan due to lack of index on this column !!!!
This can cause much bigger performance issues in my opinion.
Since the values of this columns are UUIDs, then there is a very little chance of conflicted values. In this case I would prefer not to use any locks.
Just use an unique constraint (index) on this column to prevent from inserting two duplicate values.
ALTER TABLE tbl_log ADD CONSTRAINT tbl_log_id_must_be_unique UNIQUE( id );
and then use this implementation of your function:
create or replace function checkDuplicate(in_id in varchar2) return boolean is
begin
insert into tbl_log (id,"DATE") values(in_id , sysdate);
return true;
exception when dup_val_on_index then
return false;
end;
/
In the vast majority of cases the function simply inserts a new record to the table without any delay because values are UUIDs.
In seldom cases of duplicated values, when the value is already commited in the table, the insert will immediatelly fail, without any delay.
In very very rare cases (almost impossible) when two threads are trying to simultanously insert the same UUID, the second thread will be held on INSERT command and will wait some time until the first thread will commit or rollback.
As per your condition, since you are reluctant to use Primary key data integrity enforcement( which will lead to data corruption anyhow ), i would suggest that you can use MERGE statment and keep an audit log for the latest thread updating the table. This way you will be able to eliminate the entry of duplicate record as well as keep a track of when and from which thread (latest info) the id got updated. Hope the below snippet helps.
---Create dummy table for data with duplicates
DROP TABLE dummy_hist;
CREATE TABLE dummy_hist AS
SELECT LEVEL COL1,
'AVRAJIT'
||LEVEL COL2,
SYSTIMESTAMP ACTUAL_INSERTION_DT,
SYSTIMESTAMP UPD_DT,
1 thread_val
FROM DUAL
CONNECT BY LEVEL < 100;
--Update upd_dt
UPDATE dummy_hist SET upd_dt = NULL,thread_val = NULL;
SELECT * FROM dummy_hist;
--Create function
CREATE OR REPLACE
FUNCTION checkDuplicate(
in_id IN VARCHAR2,
p_thread_val IN NUMBER)
RETURN BOOLEAN
IS
cnt NUMBER;
BEGIN
MERGE INTO dummy_hist A USING
(SELECT in_id VAL FROM dual
)B ON (A.COL1 = B.VAL)
WHEN MATCHED THEN
UPDATE
SET a.upd_dt = systimestamp,
a.thread_val = p_thread_val
WHERE a.col1 = b.val WHEN NOT MATCHED THEN
INSERT
(
a.col1,
a.col2,
a.actual_insertion_dt,
a.UPD_DT,
a.thread_val
)
VALUES
(
b.val,
'AVRAJIT',
SYSTIMESTAMP,
NULL,
p_thread_val
);
COMMIT;
RETURN true;
END;
/
--Execute the fucntion
DECLARE
rc BOOLEAN;
BEGIN
FOR I IN
(SELECT LEVEL LVL FROM DUAL CONNECT BY LEVEL BETWEEN 8 AND 50
)
LOOP
rc:=checkduplicate(I.LVL,3);
END LOOP;
END;
/

Trigger with subquery

I'm trying to create a trigger that controls cycles in a self-referencing table.
Unfortunately I've got an error. Could anyone tell me what I'm doing wrong?
CREATE OR REPLACE TRIGGER CHECK_CYCLE
BEFORE INSERT OR UPDATE ON DEPARTMENTS
FOR EACH ROW
BEGIN
IF :NEW.PARENT_ID =
(SELECT ID, PARENT_ID, NAME, LEVEL,
CONNECT_BY_ISLEAF AS ISLEAF,
PRIOR NAME AS PARENT_NAME,
CONNECT_BY_ROOT NAME AS ROOT
FROM DEPARTMENTS
START WITH PARENT_ID IS NULL
CONNECT BY PRIOR ID = PARENT_ID)
THEN
RAISE_APPLICATION_ERROR(20000, 'Sorry.');
END IF;
END;
Error(9,25): PLS-00103: Encountered the symbol "AS" when expecting one of the following:
, from
This seems to be the parser getting confused. If you remove the AS ROOT completely then it reverts to the expected PLS-00405: subquery not allowed in this context error. No idea why it is confused - you can run that query standalone - but since you can't do what you're attempting it's probably not worth worrying about too much.
Your subquery also has three columns and will return multiple rows, so comparing with the current row's scalar PARENT_ID isn't going to work. You could run your subquery separately and select the value you're actually interested in into a local variable, and check against that.
But you have a before-insert trigger and you're querying the table the trigger is against, so you'll get a mutating table exception anyway when you try to insert (or at least, if you try to insert multiple rows at once).
If I understand what you're trying to achieve, you can use an after-insert trigger instead:
create or replace trigger check_cycle
after insert or update on departments
declare
l_hascycle pls_integer;
begin
select max(connect_by_iscycle)
into l_hascycle
from departments
start with parent_id is null
connect by nocycle prior id = parent_id;
if l_hascycle = 1 then
raise_application_error(-20000, 'Sorry.');
end if;
end;
/
This uses the CONNECT_BY_ISCYCLE pseudocolumn, which will be zero for all rows if there is no cycling, and one for any column that cycles. Selecting and checking the max() of that means you'll have a single value of 0 or 1 in the local l_hascycle variable, and you can use that to decide whether to throw an exception.
Inserting a couple of test rows on top of existing non-cycling data such as:
ID PARENT_ID NAME
---------- ---------- ------------
1 Test
2 1 Test
... where the first new row is OK and the second would cause a cycle:
insert into departments (id, parent_id, name) values (3, 2, 'OK');
1 row inserted.
insert into departments (id, parent_id, name) values (2, 3, 'Cycles');
ORA-20000: Sorry.
ORA-06512: at "SCHEMA.CHECK_CYCLE", line 11
ORA-04088: error during execution of trigger 'SCHEMA.CHECK_CYCLE'
The first insert is still in effect (but not yet committed), the second was implicitly rolled back:
select * from departments:
ID PARENT_ID NAME
---------- ---------- ------------
1 Test
2 1 Test
3 2 OK
This won't catch cycles in data that is not connected to an existing tree culminating in a null parent, because of the start with clause; so for exampel with data you could insert 4,5 and 5,4 without raising an exception, as there is no route from either 4 or 5 to a row with a null parent. But that's a different issue and something you might want to test for separately.
It also can't see data from uncommitted changes in other sessions, so it's possible for two sessions to simultaneously insert rows that are valid independently but which still would form a cycle once both are committed.
I think I found the error in your query, it's in this line -
CONNECT_BY_ROOT NAME AS ROOT
which is not built correctly.
Try changing it to something like
CONNECT_BY_ROOT AS ROOT

table or view doesn't exist error

I'm using Oracle XE, in which i was making my own custom trigger. For that, I've made two tables INSERTED and ORDER_INFO in the SYSTEM schema, both have the same column name ORDER_ID, ORDER_DATE. In my scenario, client will be placing his/her order then, order information will be stored into INSERTED table, then by using this trigger, it'll insert into another table ORDER_INFO after satisfying the condition.
this is what i got till now,
CREATE TRIGGER tri_check
AFTER INSERT ON inserted FOR EACH ROW
DECLARE
BEGIN
IF :new.order_date < (SYSDATE + 2)
THEN
RAISE_APPLICATION_ERROR(-20000, 'You cannot take an order to be delivered less than 2 days from now');
ELSE
INSERT INTO orders_info (order_id, order_date)
VALUES (:new.order_id, :new.order_date);
END IF;
END;
While executing the above query, i'm getting this error
ERROR at line 7: PL/SQL: ORA-00942: table or view does not exist
5. IF :new.order_date < (SYSDATE + 2)
6. THEN
7. RAISE_APPLICATION_ERROR(-20000, 'You cannot take an order to be delivered less than 2 days from now');
8. ELSE
9. INSERT INTO orders_info (order_id, order_date)
Need Help !!
It's because you do
INSERT INTO orders_info
instead of
INSERT INTO ORDER_INFO

Bulk Update from one table to another

So I tried the bulk update in order to copy values from uemte_id column in pp_terminal table to uemte_id column (null at start) in mm_chip table. These two tables have no columns in common.This is what I used:
declare
type ue_tab is table of
pp_terminal.uemte_id%type;
ue_name ue_tab;
cursor c1 is select uemte_id from pp_terminal;
begin
open c1;
fetch c1 bulk collect into ue_name;
close c1;
-- bulk insert
forall indx in ue_name.first..ue_name.last
update mm_chip set uemte_id = ue_name(indx);
end;
/
And this is the error message I get:
Error report:
ORA-00001: unique constraint (DPOWNERA.IX_AK7_MM_CHIP) violated
ORA-06512: at line 13
00001. 00000 - "unique constraint (%s.%s) violated"
*Cause: An UPDATE or INSERT statement attempted to insert a duplicate key.
For Trusted Oracle configured in DBMS MAC mode, you may see
this message if a duplicate entry exists at a different level.
*Action: Either remove the unique restriction or do not insert the key.
Do you see any obvious misstakes?
What you're trying to do is:
select a row from the first table
update every row in the second table with that value
select another row from the first table
update every row in the second table with that value
and so forth until the loop finishes
I'm guessing that's not what you really want to do. It's failing because you have a unique constraint so you're not allowed to have multiple rows in the second table with the same value.
Below is one way to update each row of one table based on the value of an arbitrary row in a second table, without reusing any rows from the second table. It would perform better if you could do it entirely in SQL, but I couldn't come up with a way to do that.
CREATE TABLE test4 AS
(SELECT LEVEL AS cola, CAST(NULL AS number) AS colb
FROM DUAL
CONNECT BY LEVEL <= 100);
CREATE TABLE test5 AS
(SELECT 100 + LEVEL AS colc
FROM DUAL
CONNECT BY LEVEL <= 99);
DECLARE
CURSOR cur_test4 IS
SELECT *
FROM test4
FOR UPDATE ;
CURSOR cur_test5 IS
SELECT * FROM test5;
r_test5 cur_test5%ROWTYPE;
BEGIN
OPEN cur_test5;
FOR r_test4 IN cur_test4 LOOP
FETCH cur_test5 INTO r_test5;
IF cur_test5%NOTFOUND THEN
EXIT;
END IF;
UPDATE test4
SET colb = r_test5.colc
WHERE CURRENT OF cur_test4;
END LOOP;
CLOSE cur_test5;
END;

Resources