Oracle Apex 5.1 Custom Master Details - oracle

Oracle Apex 5.1 Custom Master Details Problem
I have created a page with two region,
1. dept (some text field, deptno, dname)
2. emp (interactive grid , ename, deptno)
And one submit Button
Flow : User input manually department name on 'dname' text field
And add some employees row on grid and finally when user click on submit button then first create a department with deptno (auto increment)
And then insert grid row with deptno which created first region.
That means every deptno has some corresponding employee and both task will create same times.
(after click on submit button)
If possible and you understand about this please give me a solution.

On your page create a hidden item that will have your new department id, named P1_depno in this example.
When you have created the Interactive Grid with the Attributes > Edit > Enabled set to Yes an Interactive Grid - Automatic Row Processing (DML) process (most probably named - Save Interactive Grid Data) should have been added to your page (if you don't have it simply create a new process with the above type).
Before this process create a new PL/SQL process (named Insert Department in my example) that will insert the department using this code:
declare
v_depno number;
begin
select deptno_sequence.nextval into :P1_depno from dual; --get the depno from your sequence
insert into dept (deptno, dname)-- insert the new department
values (:P1_depno,:P1_dname);
end;
Very important this process must run before the Save Interactive Grid Data process.
Now edit the Save Interactive Grid Data process and at Target Type select PL/SQL Code and add the following code:
begin
--insert the employees
case :APEX$ROW_STATUS
when 'C' then -- C for Create but you also can check for U (Update) and D (Delete)
insert into emp ( empno, ename, deptno )
values ( :EMPNO, :ENAME, :P1_depno )
returning rowid into :ROWID;
end case;
end;

Related

How to delete multiple rows using shuttle page item in Oracle Apex?

My requirement is that users must be able to select from a shuttle page item the employee numbers that they need to delete from a table. In the back end I have a plsql code that is supposed to delete the selected employee numbers as follows:
BEGIN
delete from employees where empno in (:P7_EMPLOYEE_NUMBER);
END;
Additionally I will have more logic in the code to do other stuff, however I am not able to delete multiple rows, if I select 1 employee number in the shuttle item, I am able to delete the record successfully, when I try to delete more than one record I keep getting the following error:
Ajax call returned server error ORA-01722: invalid number for Execute Server-Side Code
I changed the code to:
BEGIN
delete from employees where empno in (to_number(:P7_EMPLOYEE_NUMBER));
END;
and I keep getting the same error message.
How can I make this work?
The API APEX_STRING has a number of utility functions to deal with multi-value page items (select lists, checkbox, shuttles). To convert a colon separated list to an array, use APEX_STRING.SPLIT.
DELETE
FROM
employees
WHERE empno IN
(SELECT column_value
FROM table(apex_string.split(: P7_EMPLOYEE_NUMBER,':'))
);
A shuttle item contains colon-separated values, which means that you have to split it to rows, e.g.
delete from employees
where empno in (select regexp_substr(:P7_EMPLOYEE_NUMBER, '[^:]+', 1, level)
from dual
connect by level <= regexp_count(:P7_EMPLOYEE_NUMBER, ':') + 1
);
The APEX engine will always submit multi-values items as a single colon-delimited string, for example: 1:2:3:4
You need to split the string into multiple values so that you can process them.
There are multiple ways to do this:
Using an the APEX_STRING.SPLIT or the APEX_STRING.SPLIT_NUMBERS API in a subquery
delete from employees
where empno in (select column_value
from apex_string.split_numbers(:P7_EMPLOYEE_NUMBER, ':'));
Using the APEX_STRING API with the MEMBER OF function
delete from employees
where empno member of apex_string.split_numbers(:P7_EMPLOYEE_NUMBER, ':');
Note that the member of needs to have the same type. In this case empno is a number so you must use the split_numbers API.
Using a regular expression to split the values
delete from employees
where empno in (select regexp_substr(:P7_EMPLOYEE_NUMBER, '[^:]+', 1, level)
from dual
connect by level <= regexp_count(:P7_EMPLOYEE_NUMBER, ':') + 1
);
I prefer using option 2 as it's less code and easier to read.

I cant find solution for the trigger mutating in PL/SQL

So i am a newbie in PL/SQL, And i want to create a trigger in which a specific record salary can not be updated or deleted while other records of the table can. Suppose the record i want not to be able to update or delete its salary is EMPNO = 7839, The trigger gets created but when i update any records in EMP table it gives me error that ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it, Can someone give me a solution for this?
This is the code:
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
DECLARE
ROW_NUM NUMBER;
BEGIN
SELECT COUNT(*) INTO ROW_NUM FROM EMP WHERE EMPNO = 7839;
IF UPDATING('ROW_NUM') THEN
RAISE_APPLICATION_ERROR('-20000','CANT UPDATE/DELETE SALARY OF EMPNO = 7839');
END IF;
END PRACTICE_TRIGGER;
/
You can convert your code into this one :
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
IF :OLD.EMPNO = 7839 AND :OLD.SAL != NVL(:NEW.SAL,0) THEN
RAISE_APPLICATION_ERROR('-20000','CAN''T UPDATE SALARY OR DELETE THE ROW FOR EMPNO = '||:OLD.EMPNO);
END IF;
END;
/
Where
No query is not needed. Just new and old versions of the concerned
SAL values should be equal for an employee in order to keep that value(7839) to
be kept within the table. For DELETING case, the :NEW values for the columns will be NULL.
Those conditions are valid for both DELETING and UPDATING, so no
need to repeat them within the code. But a column cannot be be deleted, deletion of the whole record will be the case
Repeating the trigger name at the end is optional, so might be
removed.
Demo
For starters, your query is selecting the number of records, not the record identifier - it will never return "7839", only "1" or "0" for the number of records found. Also, you can't reference the table to which the trigger belongs from within the trigger (that's your mutating table error). Lastly, 'ROW_NUM' is not a column in your table, it is a variable in your trigger, so "IF UPDATING('ROW_NUM') would always be false, assuming it compiles at all.
The most basic form of what you're looking for would be this:
CREATE OR REPLACE TRIGGER PRACTICE_TRIGGER
BEFORE DELETE OR UPDATE OF SAL ON EMP
FOR EACH ROW
BEGIN
-- check to see if record being updated is restricted, then raise error
IF :OLD.EMPNO = 7839 THEN
RAISE_APPLICATION_ERROR('-20000','CANT UPDATE/DELETE SALARY OF EMPNO = 7839');
END IF;
END PRACTICE_TRIGGER;
/
That said, one obvious flaw in this approach is that the trigger as written doesn't prevent someone from changing the employee id, so theoretically if someone changed that first then the restriction on salary change would not work. A more effective approach would be a boolean column (true/false) that would identify locked records and a check to see if that flag was set. i would also recommend using a table API package to perform the actual DML operations rather than direct SQL commands, and avoid the use of triggers altogether if possible.

Oracle DB. Insert Trigger with Merge statament inside. Table is mutating

I have two back-end systems (the old one and the new one) that shares an Oracle DB.
In the older system, to save customers data, there are two tables
customers_A
ID NAME ETC
1 PETE ....
customers_B
ID NAME ETC
1 JOSH ...
2 ROSS ...
In the new system I've created a new table called All_Costumer, to join those tables.
This new table contains customer ID's of type A and B respectively.
All_Customers
ID ID_CUSTOMER_A ID_CUSTOMER_B
A19E----D2B0 1 null
A19E----D2B1 null 1
A19E----D2B2 null 2
So, when the new system creates a new customer of type A, data are inserted on customer_A and All_Customers tables, with customer of type B as well.
Currently, the old system is working too, and when a new customer of type A is created, data is inserted only on customer_A table, but I need that data in All_Customers too.
To solve this, I've created a TRIGGER with a MERGE INTO statement inside, to insert a row in All_Customers if doesn't exist on this table (when a new customer of type A are created by the older system)
CREATE OR REPLACE TRIGGER customers_trg
AFTER INSERT
ON customer_A
FOR EACH ROW
DECLARE
variables that doesn't matters
BEGIN
MERGE INTO all_customers
USING (SELECT :new.id id FROM customer_A where id = :new.id) customer
ON (all_customers.id_customer_a = customer.id)
WHEN NOT MATCHED THEN
INSERT (id, id_customer_a)
VALUES (SYS_GUID(), :new.id, null);
COMMIT;
END;
But when I try to create a new customer from the older system, I get this error:
ORA-04091: table **customer_A** is mutating, trigger/function may not see it
Any idea to solve this?
I've tried adding PRAGMA AUTONOMOUS_TRANSACTION; on DECLARE section, but didn't work.
Note: I can't modify the old system
The immediate issue is that you're querying table_a in a trigger against that table; but you don't need to. Your merge query
SELECT :new.id id FROM customer_A where id = :new.id
can simply do
SELECT :new.id id FROM dual
i.e. the clause becomes:
...
USING (SELECT :new.id id FROM dual) customer
ON (all_customers.id_customer_a = customer.id)
...
You also can't commit in a trigger - unless it's autonomous, which this shouldn't be. You said you'd tried that, but it breaks if the insert is rolled back, since the merged row will still exist. So hopefully that commit is just a hang-over from trying and rejecting that approach.
But it works in this db<>fiddle, anyway.
If you weren't adding the GUID you could get the same effect with a view:
create or replace view all_customers (id_customer_a, id_customer_b) as
select id, null from customers_a
union all
select null, id from customers_b;
db<>fiddle

Trying to delete a row based upon condition defined in my trigger (SQL)

I am trying to create a row level trigger to delete a row if a value in the row is being made NULL. My business parameters state that if a value is being made null, then the row must be deleted. Also, I cannot use a global variable.
BEGIN
IF :NEW.EXHIBIT_ID IS NULL THEN
DELETE SHOWING
WHERE EXHIBIT_ID = :OLD.EXHIBIT_ID;
END IF;
I get the following errors:
ORA-04091: table ISA722.SHOWING is mutating, trigger/function may not see it
ORA-06512: at "ISA722.TRG_EXPAINT", line 7
ORA-04088: error during execution of trigger 'ISA722.TRG_EXPAINT'
When executing this query:
UPDATE SHOWING
SET EXHIBIT_ID = NULL
WHERE PAINT_ID = 5104
As already indicated this is a terrible idea/design. Triggers are very poor methods for enforcing business rules. These should be enforced in the application or better (IMO) by a stored procedure called by the application. In this case not only is it a bad idea, but it cannot be implemented as desired. Within a trigger Oracle does not permit accessing the table the trigger fired was fired on. That is what mutating indicates. Think of trying to debug this or resolve a problem a week later. Nevertheless this non-sense can be accomplished by creating view and processing against it instead of the table.
-- setup
create table showing (exhibit_id integer, exhibit_name varchar2(50));
create view show as select * from showing;
-- trigger on VIEW
create or replace trigger show_iiur
instead of insert or update on show
for each row
begin
merge into showing
using (select :new.exhibit_id new_eid
, :old.exhibit_id old_eid
, :new.exhibit_name new_ename
from dual
) on (exhibit_id = old_eid)
when matched then
update set exhibit_name = new_ename
delete where new_eid is null
when not matched then
insert (exhibit_id, exhibit_name)
values (:new.exhibit_id, :new.exhibit_name);
end ;
-- test data
insert into show(exhibit_id, exhibit_name)
select 1,'abc' from dual union all
select 2,'def' from dual union all
select 3,'ghi' from dual;
-- 3 rows inserted
select * from show;
--- test
update show
set exhibit_name = 'XyZ'
where exhibit_id = 3;
-- 1 row updated
-- Now for the requested action. Turn the UPDATE into a DELETE
update show
set exhibit_id = null
where exhibit_name = 'def';
-- 1 row updated
select * from show;
-- table and view are the same (expect o rows)
select * from show MINUS select * from showing
UNION ALL
select * from showing MINUS select * from show;
Again this is a bad option yet you can do. But just because you can doesn't mean you should. Or that you'll be happy with the result. Good Luck.
You have written a trigger that fires after or before a row change. This is in the middle of an execution. You cannot delete a row from the same table in that moment.
So you must write an after statement trigger instead that only fires when the whole statement has run.
create or replace trigger mytrigger
after update of exhibit_id on showing
begin
delete from showing where exhibit_id is null;
end mytrigger;
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=dd5ade700d49daf14f4cdc71aed48e17
What you can do is create an extra column like is_to_be_deleted in the same table, and do this:
UPDATE SHOWING
SET EXHIBIT_ID = NULL, is_to_be_deleted = 'Y'
WHERE PAINT_ID = 5104;
You can use this parameter to implement your business logic of not showing the null details.
And later you can schedule a batch delete on that table to clean up these rows (or maybe archive it).
Benefit: you can avoid an extra unnecessary trigger on that table.
Nobody, will suggest you to use trigger to do this type of delete as it is expensive.

How to create and use a multi-select list in APEX ORACLE?

I have a table called Employees with Employee_id and Employee_Name columns. Now i want to create a page with Checkbox in-front of every Employee Name, select the ones that are needed, store them into a temporary table and use them for further operations. The problem i am facing is to how to create that multi select list and store the select values in thee table. Is there an Item for multi select? If not, how should i do it?
There's the Shuttle item. On the left side, you'd display list of all employees. Item buttons allow you to move all (or only some of them) to the right side of the item. Once you submit the page, list of employee IDs is stored into a table column in a form of colon-separated values, for example
6547:8879:5587:9987
This is a simple way of doing that. However, once you have to actually do something with those values, you have to split them to rows. Not a problem, though. Here's a query:
SQL> with emps (shuttle_item) as
2 (select '6547:8879:5587:9987' from dual)
3 select regexp_substr(shuttle_item, '[^:]+', 1, level) one_item
4 from emps
5 connect by level <= regexp_count(shuttle_item, ':') + 1;
ONE_ITEM
---------------------------------------------------------------------
6547
8879
5587
9987
SQL>
Or, you could create a tabular form which also displays all employees and has checkboxes at the beginning of every line. You'd then create a process which - in a loop - stores selected values into a temporary table you mentioned. For example:
-- F01 = row selector. If you check 1st and 3rd row, f01.count = 2 (2 rows checked)
-- f01(1) = 1 (row #1), f01(2) = 3 (row #3)
-- F02 = EMP_ID. f02(1) = EMP_ID that belongs to employee in 1st row,
-- f02(3) = EMP_ID that belongs to emplyee in 3rd row
declare
l_id number;
begin
for j in 1 .. apex_application.g_f01.count
loop
l_id := apex_application.g_f02(apex_application.g_f01(j));
insert into temp_table (emp_id) values (l_id);
end loop;
end;
There is an option for creating multi select list in oracle apex 5.1.
Create a pageItem of type: 'select list'.
Make the 'Allow multi
selection' to 'Yes'.
Write the SQL query for your select list under
the 'List of Values' attribute.
Then the select list will be
displayed based on our query.
Query format is:
select [displayValue],
[returnValue]
from ...
where ...
order by ...
Now once you select multiple value from select list(using ctrl+click), these are stored as ':' separated values in the select list page item.
I've created a video some times ago that covers your problem. It's a step by step tutorial how to create checkboxes and process them.
Video is available here:
https://www.youtube.com/watch?v=T-LXRMWQbPk
Regards
If the list is too big, I recommend to use the Popup LOV item with the Multiple Values switch activated instead the Select list or the Shuttle, because it has an internal search field for the objects list, doing way easier for the user to find the target values. Also, just as the Select List or Shuttle item, you can set a Separator character for the selected fields.

Resources