Browse the values of multiple fields and insert them into the same column - oracle

I am trying to create a functionality for my oracle apex application using duplicated row. Let's say that i have a vehicle table.
CREATE TABLE vehicles
(
brand VARCHAR2(50),
model VARCHAR2(50),
comment VARCHAR2(50)
);
I first created a PL/SQL procedure that is currently working, which duplicate the row n amount of times, selected by the user. For example, if the user has 5 of the same vehicles, he can enter "5", and the application will insert 5 times the same vehicle, with different primary keys of course.
What I am trying to achieve, is to give the possiblity for the user to add a custom comment on every row. For example, he would have 5 of the same cars, with different comment on each one.
Here's the code of my procedure when the user is inserting the cars :
FOR i IN 1..:P27_NUMBER_VEHICLES -- this represents the numbers of cars the users want to insert
LOOP
INSERT INTO vehicles(brand, model)
VALUES(:P27_BRAND, :P27_MODEL);
END LOOP;
END;
This creates 5 of the same vehicles, but i want the user to be able to add 5 different comments on each vehicle, here's an example of my insertion form, the comment field appears dynamically based on the numbers that the user want to insert
I've now tried to update my procedure with the comment, i tried to give a name that could be dynamically changed by the procedure, I named it :P27_COMMENT_0 / 1 / 2 / 3 / 4 / 5. So i tried to add :P27_COMMENT_ || i (the index of the loop) to try to browse the differents fields automatically, here's the code :
BEGIN
FOR i IN 1..:P27_NUMBER_VEHICLES
LOOP
INSERT INTO vehicles(brand, model, comment)
VALUES(:P27_BRAND, :P27_MODEL, :P27_COMM_ || i);
END LOOP;
END;
This unfortunately doesn't work.
I hope you guys could have a solution for me. To summarize everything, I am trying to duplicate a row witha loop, but still being able to add a custom entry for each one on a specific column.
Do not hesitate to ask for more details, as I may have forgot some important things.
Thank you in advance,
Thomas
UPDATE
Here's how it is showing, I can't write anything on it :

In my opinion is easier to use APEX_ITEM, as follow:
Add an Interactive Report region or sub region after your items.
Set the IR SQL query as:
SELECT
APEX_ITEM.TEXT(
p_idx => 1,
p_attributes=> 'placeholder="Comment '|| LEVEL ||'"') AS Name
FROM dual
CONNECT BY LEVEL <= TO_NUMBER (:P16_NUM)
;
:P16_NUM is an item which holds the number of items to display. Do not forget to add it in Page Items to Submit in the Source section of the IR.
Add a submit button.
In Processing create a new process an iterate through the items as:
BEGIN
apex_debug.enable;
for i in apex_application.g_f01.FIRST..apex_application.g_f01.LAST loop
apex_debug.info('####### Comment '||i||' '||apex_application.g_f01(i));
end loop;
END;
Finally your INSERT:
INSERT INTO vehicles(brand, model, comment)
VALUES(:P27_BRAND, :P27_MODEL, apex_application.g_f01(i));
When you check that everything works fine, change the process code:
BEGIN
for i in apex_application.g_f01.FIRST..apex_application.g_f01.LAST loop
INSERT INTO vehicles(brand, model, comment)
VALUES(:P27_BRAND, :P27_MODEL, apex_application.g_f01(i));
end loop;
END;
EDIT
Select the column where the inputs are, on the right find the Security section and disable Escape special characters.

Related

Oracle automatically insert record in multirecord block part 2

My table looks like this:
+-------------------+
|Name |
+-------------------+
|Name1 |
|Name2 |
|Name3 |
|Name4 |
|Name1Jr |
|Name2Jr |
|Name4Jr |
+-------------------+
My multirow block looks like:
What I wanted to know is how can I insert a record that has the same name with Jr after I insert a name. For example, I inserted Name2, it will also insert Name2Jr into the multirow block. Like this:
Note: I need to get the value of the automatically inserted data from database.
I tried in WHEN-NEW-RECORD-INSTANCE trigger(the answer of Sir #Littlefoot on my last question):
if :system.trigger_record = 1 and :test.name is null then
-- do nothing if it is the first record in a form
null;
else
duplicate_record;
if substr(:test.name, -2) = 'Jr' then
-- you've duplicated a record which already has 'Jr' at the end - don't do it
:test.name := null;
else
-- concatenate 'Jr' to the duplicated record
:test.name := :test.name || 'Jr';
end if;
end if;
And now, what I want is to know if there is a way to do it on WHEN-VALIDATE-RECORD trigger. The problem there is that the duplicate_record can't be used in WHEN-VALIDATE-RECORD trigger. How to do it using procedure, function or something? Thanks in advance!
DUPLICATE_RECORD is a restricted procedure and you can't use it in WHEN-VALIDATE-RECORD trigger (or any other of the same kind).
As you have to navigate to the next record (if you want to copy it), even if you put that restricted procedure into another PL/SQL program unit, everything will just propagate and - ultimately - raise the same error. So ... you're out of luck.
Even if you wrote a (stored) procedure which would insert that "Jr" row into the database somewhere behind the scene, you'd have to fetch those values to the screen. As EXECUTE_QUERY is the way to do it, and as it is (yet another) restricted procedure, that won't work either.
If you planned to clear data block and fill it manually (by using a loop), you'd have to navigate to next (and next, and next) record with NEXT_RECORD, and that's again a restricted procedure. Besides, if it was a data block (and yes, it is), you'd actually create duplicates for all records once you'd save changes so - either it would fail with unique constraint violation (which is good), or you'd create duplicates (which is worse).
BTW what's wrong with WHEN-NEW-RECORD-INSTANCE? What problems do you have when using it?
What We need is
a Data Block (BLOCK1) with
Query Data Source Name is mytable,
and Number of Records Displayed set to more than 1(let it be 5 as in your case).
a Text item named as NAME same as the column name of the table
mytable, and Database Item property set to Yes.
a Push Button with Number of Records Displayed set to 1,
Keyboard Navigable and Mouse Navigate properties are set to No
and has the following code (inside WHEN-BUTTON-PRESSED trigger):
commit;
Query_Block1;
Where Query_Block1 is beneath Program Units node with this code :
PROCEDURE Query_Block1 IS
BEGIN
execute_query;
last_record;
down;
END;
A POST-INSERT trigger beneath BLOCK1 node with code :
insert into mytable(name) values(:name||'Jr');
An ON-MESSAGE trigger at the form level with the code (to suppress the messages after commit):
if Message_Code in (40400, 40401) then
null;
end if;
And WHEN-NEW-FORM-INSTANCE trigger with the code :
Query_Block1;

PL/SQL: ORA 01422 fetch returns more than requested number of rows

I am developing an order transaction where a user can order a product. Once they clicked the 'add to cart' button, it will be able to save on the database in how many times they want with the same order id. Order id is like a transaction id.
My problem is that whenever I want to display the items that customer ordered, it displays an error or ORA 01422. How can I resolve this error?
Here is my code
DECLARE
order_item_id NUMBER;
BEGIN
order_item_id := :MOTOR_PRODUCTS_ORDER.M_ORDERID;
SELECT MOTOR_ID,
MOTOR_QTY_PURCHASED,
UNITPRICE
INTO :MOTOR_ORDER_ITEMS.MOTOR_ID,
:MOTOR_ORDER_ITEMS.MOTOR_QTY_PURCHASED,
:MOTOR_ORDER_ITEMS.UNITPRICE
FROM MOTOR_ORDERS
WHERE motor_order_id = order_item_id;
END;
As krokodilo says, this error is caused because your query returns multiple rows. Depending on what you want to do, you have a couple of options.
If you want multiple values then either use a loop and process them one row at a time (if you are going to be performing a dml operation use bulk collect). If you only want a single row then narrow your result set down with an extra where clause, or use MAX to ensure you only get one value back.
If there is more than one row which will be returned from a query you'll need to use a cursor. One way to do this is with a cursor FOR loop:
DECLARE
order_item_id NUMBER;
BEGIN
order_item_id := :MOTOR_PRODUCTS_ORDER.M_ORDERID;
FOR aRow IN (SELECT MOTOR_ID, MOTOR_QTY_PURCHASED, UNITPRICE
FROM MOTOR_ORDERS
WHERE motor_order_id = order_item_id)
LOOP
-- Do something here with the values in 'aRow'. For example, you
-- might print them out:
DBMS_OUTPUT.PUT_LINE('MOTOR_ID=' || aRow.MOTOR_ID ||
' MOTOR_QTY_PURCHASED=' || aRow.MOTOR_QTY_PURCHASED ||
' UNITPRICE=' || aRow.UNITPRICE);
END LOOP;
END;
Best of luck.
This looks like a Forms question; is it? If so, my suggestion is to let Forms do that job for you.
According to what you posted, there are two blocks:
MOTOR_PRODUCTS_ORDER (a master block, form type)
MOTOR_ORDER_ITEMS (a detail block, tabular type)
I guess that there is a master-detail relationship between them. If there's none, I'd suggest you to create it. Although you can make it work without such a relationship, it'll be much more difficult. If you are unsure of how to do it, start from scratch:
delete MOTOR_ORDER_ITEMS block (detail)
create it once again, this time by following the Data Block Wizard
Set MOTOR_PRODUCTS_ORDER to be its master block
Relationship is on ORDER_ID column/item
Let's presume that by this point everything is set up. Retrieving items that belong to that ORDER_ID is now very simple:
navigate to master block
enter query mode
enter value into an item that represents ORDER_ID
execute query
End of story. Forms triggers & procedures (which were created by the Wizard) will do its job and retrieve both master and detail records.
No need for additional coding; if you're skilled developer, you can create such a form in a matter of minutes. Won't be beautiful, but will be effective & reliable.
There's really no use in doing it manually, although it is possible. Your code works if there's a single item for that ORDER_ID. For two or more items, as you already know, it'll fail with TOO-MANY-ROWS error.
Basically, if you insist, you should use a loop:
you'd enter ORDER_ID into the master block
as you need to move through the detail block, i.e. use NEXT_RECORD, which is a restricted procedure, you can't use number of triggers (open Forms Online Help System and read about them) so a "Show items" button (with its WHEN-BUTTON-PRESSED trigger) might be just fine
a cursor FOR loop would be your choice
for every row it fetches, you'd populate block items and
navigate to next record (otherwise, you'd keep overwriting existing values in the 1st tabular block row)
As I said: possible, but not recommended.

Oracle APEX Selection list

I have a form on a table with some values (text fields) and a select list. The select list is declared in shared components and shows me values from another table. Also I have some process (after submit) to modify and create new table entry. Everything works fine without propagating values from select list. There are no errors from the process. It looks like the process didn't get a value for :P22_WORKER_LIST from the selection list. It should work when I push the create or save buttons but nothing happened. Every instruction in BEGIN END block runs great without this one.
Process:
BEGIN
<some instructions>
UPDATE "WORKER" SET ACCOUNT_LOGIN = :P22_LOGIN
WHERE SURNAME = :P22_WORKER_LIST;
END;
Thank you for suggestion with the session state. After submiting page it turned out that the value for my :P22_WORKER_LIST is a WORKER_ID instead a SURNAME.
BEGIN
<some instructions>
UPDATE "WORKER" SET ACCOUNT_LOGIN = :P22_LOGIN
WHERE WORKER_ID = :P22_WORKER_LIST;
END;

Trying to create a trigger that prevents updates based on value from another table

I am trying to prevent users from updating one table based on a date value from another table.
Table A contains rows that I would like to make un editable if a date value in the Table B is older than sysdate.
I need to somehow tell the trigger to check the row and use a foreign key in Table A's row to query its corresponding rows in Table B and then do this:
raise_application_error(-20000, 'It is too late to change this record');
Thank You
Assuming this is a homework assignment, you'd want something like this (I'm guessing at table structures and cardinalities since you don't specify).
CREATE OR REPLACE TRIGGER trigger_name
AFTER UPDATE ON a
FOR EACH ROW
DECLARE
l_dt_b b.dt_col%type;
BEGIN
SELECT dt_col
INTO l_dt_b
FROM b
WHERE b.b_key = :new.b_key;
IF( l_dt_b < sysdate )
THEN
RAISE_APPLICATION_ERROR( -20001, 'Too late' );
END IF;
END;
If this is for a real system, however, trigger-based validation is problematic. It is not safe in a multi-user system, for example. In session 1, I may have modified the row in B but not yet committed the change. You can then query the row in session 2, see the old value, and allow the UPDATE. We can both commit our changes and nothing will detect that we have data in an invalid state.

Oracle trigger causing infinite loop

I have a students table in my Oracle database which has a field called RECORD_NUMBER. This field is 8 characters long and I want to create a trigger to pad the left part out with 0s as it's inserted. This is what I have so far:
create or replace
TRIGGER STUDENTS_RECORD_NUMBER_TRG
BEFORE INSERT OR UPDATE OF RECORD_NUMBER ON TBL_STUDENTS
FOR EACH ROW
BEGIN
WHILE length(:new.RECORD_NUMBER) < 9
LOOP
:new.RECORD_NUMBER := LPAD(:new.RECORD_NUMBER,8,'0');
END LOOP;
NULL;
END;
However, when I try to insert a row the database connection locks up and I have to restart Oracle to use it again. Is it possible that this trigger is causing an infinite loop?
If record_number is a varchar2(8), then length(:new.record_number) will always be less than 9 and your loop will iterate endlessly. But you don't need a loop here, just call LPAD
create or replace TRIGGER STUDENTS_RECORD_NUMBER_TRG
BEFORE INSERT OR UPDATE OF RECORD_NUMBER
ON TBL_STUDENTS
FOR EACH ROW
BEGIN
:new.RECORD_NUMBER := LPAD(:new.RECORD_NUMBER,8,'0');
END;
This assumes, of course, that it really makes sense to pad the data that is physically stored in the database rather than doing something like applying the LPAD in a view layer. Generally, I would expect that you'd be better served putting this sort of presentation logic in a view since views are great for implementing presentation logic. But this trigger should do what you've asked.
Leave the entry as a number. If it needs to be displayed with padded zeroes then do so as part of the display logic.

Resources