Trigger for the nexval in a sequence returing ORA-01403 - sql

I have a page in Apex where the user can connect a row from a table to another (a mail to the address) because we changed how we store addresses so we are connecting the two tables while working on them.
The link between the tables is a row in a third table that has 5 columns (ID||ID_RACC||ID_INDIRIZZO||DATA_INS||USER_INS), where ID is the main ID of the link-table, ID_RACC is the ID of the mail's table and ID_INDIRIZZO is the ID of the addresses' table.
If I try from the shell to run this query:
INSERT INTO INSERT INTO INDIRIZZI_RACCOMANDATE (ID_RACC, ID_INDIRIZZO, USER_INS, DATA_INS)
VALUES (p_id_racc, p_id_indirizzo, v('USER'), SYSDATE);
with p_id_racc, p_id_indirizzo non-empty variables, I don't have any problem.
But if the user select from a Select List the address and click the Save button from a specific page he/she receives
ORA-01403: no data found
the only code that is run from him/her is the above one.
I searched and I found out that the problem could be the trigger that fills the ID column in the table INDIRIZZI_RACCOMANDATE from a sequence.
The trigger code is:
create or replace trigger "BI_INDIRIZZI_RACCOMANDATE"
BEFORE
insert on "INDIRIZZI_RACCOMANDATE"
for each row
begin
if :NEW."ID" is null then
select "INDIRIZZI_RACCOMANDATE_SEQ".nextval into :NEW."ID" from sys.dual;
end if;
end;​
I can't understand how is it possible to have a no_data_found with only a select nextval from a sequence.
Then I can't understand how is it possible that I have this problem only if I run it from that page and don't have it if I run the exact same code from shell.

But if the user select from a Select List the address and click the Save button from a specific page he/she receives ORA-01403: no data found
While in Apex, use :APP_USER:
INSERT INTO INSERT INTO INDIRIZZI_RACCOMANDATE
(ID_RACC, ID_INDIRIZZO, USER_INS, DATA_INS)
VALUES
(p_id_racc, p_id_indirizzo, :APP_USER, SYSDATE);
^^^^^^^^^
this
By the way, v('USER') is suspicious; should have been v('APP_USER'), I presume.

V (an absolutely abhorrent object name Oracle should know better) is defined as a collection indexed by varchar2. Your problem is that 'USER' is not a valid index value for V. When a collection does not contain the referenced index value on a collection Oracle throws NO DATA FOUND. A bad choice but the one they made. That is how you get that error. use v('APP_USER'); See fiddle and below.
declare
type example_att
is table of varchar2(30)
index by varchar2(8);
example_data example_att;
begin
dbms_output.enable;
example_data('A') := 'Abcd';
example_data('E') := 'Efgh';
example_data('I') := 'Ijlk';
dbms_output.put_line( 'value for ''A'' is ' );
dbms_output.put_line( example_data('A') );
-- OOPS
dbms_output.put_line( 'value for ''L'' is ' );
dbms_output.put_line( example_data('L'));
end;
/

I had the same error message for a procedure I would call in a dynamic action. The Procedure takes two arguments where one is the APP_USER.
BOOKMARK_FAVORITE(v('CARD_ID'), v('APP_USER'));
In my Procedure I would then select the User_ID for this specific APP_USER. And here is where the error was. The username I was selecting was not in Uppercase but the APP_USER was (IN_USERNAME). So I had to use the UPPER function:
select au.user_id into IN_USER_ID
from app_user au
where UPPER(au.username) = IN_USERNAME; --IN_USERNAME = APP_USER
^^^^^
Apparantly once the user log's in, the username will be stored in APP_USER in UPPERCASE, irrespective of how user has entered in log-in screen.
Don't know if this will help in your situation though :/

Related

Retrieve data by user input with a message (PL/SQL)

I want to how to retrieve data from the table I have added here by inserting C_Id as the user input with defined variables and exceptions. If any customer is not available it has to display a message showing "No customer found". Please help me to understand this one.
Thank you!
If it is a stored procedure (should be; it accepts a parameter) and you just want to display what you found, then this might be one option:
create or replace procedure p_test (par_c_id in customer_details.c_id%type)
is
l_row customer_details%rowtype;
begin
select *
into l_row
from customer_details
where c_id = par_c_id;
dbms_output.put_line(l_row.customer_name ||', '||
l_row.address ||', '||
to_char(l_row.date_of_joined, 'dd.mm.yyyy')
);
exception
when no_data_found then
dbms_output.put_line('No customer found');
end;
/
Then run it as
set serveroutput on
begin
p_test(121);
end;
/
What does it do?
accepts a parameter
declares local variable which is equal to table's rowtype
select everything into the variable
using dbms_output.put_line, display elements you want
if nothing has been found, exception (no_data_found) is raised and handled in a way that you display appropriate message
Note that such an option works if tool you use (e.g. SQL*Plus, SQL Developer, TOAD, ...) allow displaying result of dbms_output.put_line. Otherwise, if you use e.g. Oracle Apex, procedure will still work, but you won't see anything.

How to resolve "invalid hex number" in by specifically passing id?

I have a "club" checkbox in events form which gets its list of values from "club" table
select club_id, club_name
from club;
When a user selects one or more checkboxes and submits a from, a page process takes the id's of those selected clubs and inserts them and the event_id inside club_event table. But I am getting an error stating "invalid hex number".
This is the script that I have used in my page process.
DECLARE
l_ids apex_t_varchar2;
BEGIN
l_ids := apex_string.split(:P72_CLUBS, ':');
FOR i in 1 .. l_ids.COUNT LOOP
INSERT INTO club_event
VALUES(L_ids(i), :P72_EVENT_ID);
END LOOP;
END;
The error must be occuring because :P72_CLUBS in the above script is passing 'club_name' rather than 'club_id'. I tired it in 'SQL commands' and when I hard code the id of the club it runs fine. When I replace the id with the names like:
l_ids := apex_string.split('club 1:club 2', ':');
I get the same error.
So how can I specifically pass the club_id to :P72_CLUB?

How to write a procedure to display the contents of a table in sql

I have a created a procedure as
create or replace procedure availability(num in number) as
begin
delete from vehicle_count;
insert into vehicle_count from select engine_no,count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
end;
/
The procedure was created successfully but now i have to write a separate query to view the contents of vehicle_count as
select * from vehicle_count;
I tried inserting the select statement into the procedure after insertion but it showed a error stating "an INTO clause is expected in the select statement".
How can I create procedure to select the required contents and display it in a single execute statement?
Table schema
vehicle(vehicle_no,engine_no,offence_count,license_status,owner_id);
vehicle_count(engine_no,engine_count);
Check this (MS SQL SERVER)-
create or alter procedure availability(#num as int) as
begin
delete from vehicle_count;
insert into vehicle_count
output inserted.engine_no,inserted.count_engine_no
select engine_no,count(engine_no) as count_engine_no
from vehicle
where engine_no=#num
group by engine_no;
end;
If you want to use a SELECT into a PL/SQL block you should use either a SELECT INTO or a loop (if you want to print more rows).
You could use something like this:
BEGIN
SELECT engine_no, engine_count
INTO v_engine, v_count
FROM vehicle_count
WHERE engine_no = num;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_engine := NULL;
v_count := NULL;
END;
v_engine and v_count are two variables. You can declare them in your procedure, and they will contain the values you want to print.
You said that the procedure you wrote (actually, you posted here) compiled successfully. Well, sorry to inform you - that's not true. This is not a valid syntax:
insert into vehicle_count from select engine_no,count(engine_no)
----
from? Here?
Consider posting true information.
As of your question (if we suppose that that INSERT actually inserted something into a table):
at the beginning, you delete everything from the table
as SELECT counts number of rows that share the same ENGINE_NO (which is equal to the parameter NUM value), INSERT inserts none (if there's no such NUM value in the table) or maximum 1 row (because of aggregation)
therefore, if you want to display what's in the table, all you need is a single SELECT ... INTO statement whose result is displayed with a simple DBMS_OUTPUT.PUT_LINE which will be OK if you're doing it interactively (in SQL*Plus, SQL Developer, TOAD and smilar tools). Regarding table description, I'd say that ENGINE_NO should be a primary key (i.e. that not more than a single row with that ENGINE_NO value can exist in a table).
create or replace procedure availability (num in number) as
l_engine_no vehicle_count.engine_no%type;
l_engine_count vehicle_count.engine_count%type;
begin
delete from vehicle_count;
insert into vehicle_count (engine_no, engine_count)
select engine_no, count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
-- This query shouldn't return TOO-MANY-ROWS if ENGINE_NO is a primary key.
-- However, it might return NO-DATA-FOUND if there's no such NUM there, so you should handle it
select engine_no, engine_count
into l_engine_no, l_engine_count
from vehicle_count
where engine_no = num;
dbms_output.put_line(l_engine_no ||': '|| l_engine_count);
exception
when no_data_found then
dbms_output.put_line('Nothing found for ENGINE_NO = ' || num);
end;
/
There are numerous alternatives to that (people who posted their answers/comments before this one mentioned some of those), and the final result you'd be satisfied with depends on where you want to display that information.

pass session user value to procedure

I have created a procedure in user A to get the list of tables belonging to session user (who ever runs the procedure) and insert into a table. but when I execute the procedure from user B. It is not getting the values, I mean the session_user value is not being passed to procedure ?? Ienter code heres this an expected behavior ? Is there a way I can pass the session user without actually passing it while execution of procedure(it needs to it by itself) ?
Table :
CREATE TABLE "ABCD"
( "USERNAME" VARCHAR2(1`enter code here`00 BYTE),
"TABLE_NAME" VARCHAR2(100 BYTE)) ;
Procedure I am trying to run :
CREATE OR REPLACE PROCEDURE "SESS_USR_OBJECTS" AS
V_USER varchar2(50):= SYS_CONTEXT('USERENV','SESSION_USER');
cursor SEL_CUR is select OWNER,TABLE_NAME from all_tables where owner in (V_USER);
SEL_REC SEL_CUR%ROWTYPE;
VSQL1 VARCHAR2(100);
BEGIN
OPEN SEL_CUR;
LOOP
FETCH SEL_CUR INTO SEL_REC;
EXIT WHEN SEL_CUR%NOTFOUND;
VSQL1:= 'insert into ABCD VALUES ('''||SEL_REC.OWNER||''','''||SEL_REC.TABLE_NAME||''')';
Dbms_Output.Put_Line(VSQL1);
EXECUTE IMMEDIATE VSQL1;
COMMIT;
END LOOP;
CLOSE SEL_CUR;
END;
When I runt the procedure nothing is being inserted into table.
Yes, user B has access to run the procedure in A.
You are selecting from ALL_TABLES, which is a view that is filtered by SCHEMAID and other (internal) representations of the current user, not SESSION_USER. So, when you execute the procedure, you are selecting from a list of tables that user "A" can see but that are owned by user "B". Apparently, that list is empty in your database.
To make this work the way you want, you need to use invoker's rights on that procedure, like this:
CREATE OR REPLACE PROCEDURE "SESS_USR_OBJECTS" AUTHID CURRENT_USER AS...
That way, when it runs, it will select from user "B"'s version of ALL_TABLES.

History table referencing other values in the table / accessing package table variables

I have a system for tracking usage of computers in a lab. Slightly simplified, it works out to:
Machines are associated with a lab.
Machines have a binary logged_in state, which gets updated automatically when users log in and out.
There is a view keyed on the lab which gathers the total number of seats associated with the lab, and the current number in use for that lab.
What I would like to do is add a history or audit table, which would track changes to lab population over time. I had a trigger on the machine table to store the time and the total lab population in my lab history table every time the machine table changed. The problem is that, in order to get the new total for the lab, I have to examine the other values in the machine table. This results in a table mutating error.
Some things I found on here and elsewhere suggested that I should create a package to track the labs being changed. Use a before trigger to clear the list, a row trigger to store each labid being changed, and an after trigger to update the history table with new values for only those labs whose ids are in the list. I've tried that, but can't figure out how to access the values I've stored in the package table (or even if it is storing them properly in the first place.) As will no doubt be obvious, I'm unfamiliar with PL/SQL packages and table variables - the whole syntax of referring to table entries like arrays struck me as vaguely heretical though incredibly useful if it works. So most of the below is just copied and adapted from other solutions I've found, but they didn't stretch as far as how to actually use my table of changed lablocids, assuming its being created properly in the first place. The following simply tells me that pg_machine_in_use_pkg.changedlablocids does not exist when I try to compile the final trigger.
create or replace package labstats_adm.pg_machine_in_use_pkg
as
type arr is table of number index by binary_integer;
changedlablocids arr;
empty arr;
end;
/
create or replace trigger labstats_adm.pg_machine_in_use_init
before insert or update
on labstats_adm.pg_machine
begin
-- begin each update with a blank list of changed lablocids
pg_machine_in_use_pkg.changedlablocids := pg_machine_in_use_pkg.empty;
end;
/
--
create or replace trigger labstats_adm.pg_machine_in_use_update
after insert or update of in_use,lablocid
on labstats_adm.pg_machine
for each row
begin
-- record lablocids - old and new - of changed machines
if :new.lablocid is not null then
pg_machine_in_use_pkg.changedlablocids( pg_machine_in_use_pkg.changedlablocids.count+1 ) := :new.lablocid;
end if;
if :old.lablocid is not null and :old.lablocid != :new.lablocid then
pg_machine_in_use_pkg.changedlablocids( pg_machine_in_use_pkg.changedlablocids.count+1 ) := :old.lablocid;
end if;
end;
create or replace trigger labstats_adm.pg_machine_lab_history
after insert or update of in_use,lablocid
on labstats_adm.pg_machine
begin
-- for each lablocation we just logged a change to, update that labs history
insert into labstats_adm.pg_lab_history (labid, time, total_seats, used_seats)
select labid, systimestamp, total_seats, used_seats
from labstats_adm.lab_usage
where labid in (
select distinct labid from pg_machine_in_use_pkg.changedlablocids
);
end;
/
On the other hand, if there is a better overall approach than the package, I'm all ears.
After some reflection I've got to go with #tbone on this one. In my experience a history table should be a copy of the data in the "real" table with fields added to show when particular 'version' of the data shown by a row in the history table was in effect. So if the "real" table is something like
CREATE TABLE REAL_TABLE
(ID_REAL_TABLE NUMBER PRIMARY KEY,
COL2 NUMBER,
COL3 VARCHAR2(50));
then I'd create the history table as
CREATE TABLE HIST_TABLE
(ID_HIST_TABLE NUMBER PRIMARY KEY
ID_REAL_TABLE NUMBER,
COL2 NUMBER,
COL3 VARCHAR2(50),
EFFECTIVE_START_DT TIMESTAMP(9) NOT NULL,
EFFECTIVE_END_DT TIMESTAMP(9));
and I'd have the following triggers to get everything populated:
CREATE TRIGGER REAL_TABLE_BI
BEFORE INSERT ON REAL_TABLE
REFERENCING OLD AS OLD
NEW AS NEW
FOR EACH ROW
BEGIN
IF :NEW.ID_REAL_TABLE IS NULL THEN
:NEW.ID_REAL_TABLE := REAL_TABLE_SEQUENCE.NEXTVAL;
END IF;
END REAL_TABLE_BI;
CREATE TRIGGER HIST_TABLE_BI
BEFORE INSERT ON HIST_TABLE
FOR EACH ROW
BEGIN
IF :NEW.ID_HIST_TABLE IS NULL THEN
:NEW.ID_HIST_TABLE := HIST_TABLE_SEQUENCE.NEXTVAL;
END IF;
END HIST_TABLE_BI;
CREATE TRIGGER REAL_TABLE_AIUD
AFTER INSERT OR UPDATE OR DELETE ON REAL_TABLE
FOR EACH ROW
DECLARE
tsEffective_start_date TIMESTAMP(9) := SYSTIMESTAMP;
tsEffective_end_date TIMESTAMP(9) := dtEffective_start_date - INTERVAL '0.000000001' SECOND;
BEGIN
IF UPDATING OR DELETING THEN
UPDATE HIST_TABLE
SET EFFECTIVE_END_DATE := tsEffective_end_date
WHERE ID_REAL_TABLE = :NEW.ID_REAL_TABLE AND
EFFECTIVE_END_DATE IS NULL;
END IF;
IF INSERTING OR UPDATING THEN
INSERT INTO HIST_TABLE (ID_REAL_TABLE, COL2, COL3, EFFECTIVE_START_DATE)
VALUES (:NEW.ID_REAL_TABLE, :NEW.COL2, :NEW.COL3, tsEffective_start_date);
END IF;
END REAL_TABLE_AIUD;
Using this method the "history" table has all historical versions of the data in the "real" table PLUS a complete copy of the "current" data from the "real" table; this is done to simplify queries which need to report on all versions of the data in the table up to and including present values.
The advantage of using triggers to do all this is that the maintenance of the primary keys and the history table becomes automatic and can't be easily circumvented or forgotten.
Share and enjoy.
Sorry so slow to get back; its taken me a bit of fiddling, and I haven't had a lot of time to work on it.
Thanks to Bob Jarvis for pointing me at the compound triggers, which cleaned up the overall structure significantly. After that, I just had to sanitise the way I'm getting values back out of my table variable. On the odd chance that someone else stumbles over this looking for the answer to the same problem, I'll post my final solution here:
create or replace
trigger pg_machine_in_use_update
for insert or update or delete of in_use,lablocid
on labstats_adm.pg_machine
compound trigger
type arr is table of number index by binary_integer;
changedlabids arr;
idx binary_integer;
after each row is
newlabid labstats_adm.pg_labs.labid%TYPE;
oldlabid labstats_adm.pg_labs.labid%TYPE;
begin
-- store the labids of any changed locations
-- PL/SQL does not like us testing for the existence of something that isn't there, so just set it twice if necessary
if ( :new.lablocid is not null ) then
select labid into newlabid from labstats_adm.pg_lablocation where lablocid = :new.lablocid;
changedlabids( newlabid ) := 1;
end if;
if ( :old.lablocid is not null ) then
select labid into oldlabid from labstats_adm.pg_lablocation where lablocid = :old.lablocid;
changedlabids( oldlabid ) := 1;
end if;
end after each row;
after statement is
begin
idx := changedlabids.FIRST;
while idx is not null loop
insert into labstats_adm.pg_lab_history (labid, time, total_seats, used_seats)
select labid, systimestamp, total_seats, used_seats
from labstats_adm.lab_usage
where labid = idx;
idx := changedlabids.NEXT(idx);
end loop;
end after statement;
end pg_machine_in_use_update;