Here I have a question that I need to write a PL/SQL. The structure of the database is also linked. The question requires to use a sequence inside a procedure. I'm new to this and don't know if this works properly and my exec command doesn't seem working please help me out. Also is this how to look up the max shareholder_id that the sequence should start with, or can I a select inside create sequence?
Write a PL/SQL procedure called INSERT_DIRECT_HOLDER which will be used to insert new direct holders. Create a sequence to automatically generate shareholder_ids. Use this sequence in your procedure.
-Input parameters: first_name, last_name
DROP SEQUENCE shareholder_id_seq;
SELECT
MAX(shareholder_id)
FROM shareholder;
CREATE SEQUENCE shareholder_id_seq
INCREMENT BY 1
START WITH 25;
CREATE OR REPLACE PROCEDURE insert_direct_holder(
p_first_name in direct_holder.first_name%type,
p_last_name in direct_holder.last_name%type)
IS
v_shareholder_id NUMBER(6);
BEGIN
INSERT INTO DIRECT_HOLDER(direct_holder_id,first_name,last_name) values(shareholder_id_seq.nextval, p_first_name, p_last_name);
INSERT INTO shareholder (shareholder_id, type) VALUES (shareholder_id_seq.nextval,'Direct_Holder');
COMMIT;
END;
/
/* test command*/
exec insert_direct_holder( p_first_name, p_last_name );
You need to insert the record with same ID in both the tables.
also, you need to insert record into your parent table (shareholder) first and then child table(direct_holder).
CREATE OR REPLACE PROCEDURE insert_direct_holder(
p_first_name in direct_holder.first_name%type,
p_last_name in direct_holder.last_name%type)
IS
v_shareholder_id NUMBER(6);
BEGIN
v_shareholder_id := shareholder_id_seq.nextval;
INSERT INTO shareholder (shareholder_id, type) VALUES (v_shareholder_id,'Direct_Holder');
INSERT INTO DIRECT_HOLDER(direct_holder_id,first_name,last_name) values(v_shareholder_id, p_first_name, p_last_name);
COMMIT;
END;
/
/* test command*/
exec insert_direct_holder( p_first_name, p_last_name );
Sequence is created once to implement auto incrementing feature for any numeric column.
For the current use case it has to be created just once and left forever hopefully.Sequence can be modified in future if required.
If shareholder table has records in it already, then create the sequence with start value as SELECT MAX(shareholder_id) + 1 FROM shareholder; to avoid primary key constraint violation.
A slight modification is required for the stored procedure to use the same SHAREHOLDER.SHAREHOLDER_ID as the column has foreign key relationship with DIRECT_HOLDER.
Use INSERT ALL to insert to both the tables for the same sequence.nextval.
CREATE OR REPLACE PROCEDURE insert_direct_holder(
p_first_name in direct_holder.first_name%type,
p_last_name in direct_holder.last_name%type)
IS
BEGIN
INSERT ALL
INTO SHAREHOLDER
(shareholder_id, type) values(shareholder_id_seq.nextval,'Direct_Holder')
INTO DIRECT_HOLDER
(direct_holder_id,first_name,last_name) values
(shareholder_id_seq.nextval,p_first_name,p_last_name)
SELECT 'DUMMY' FROM dual;
COMMIT;
END;
/
dbfiddle demo with working code : https://dbfiddle.uk/?rdbms=oracle_18&fiddle=5d80488fb69d78d4b5087f06a5becf96
Related
I am trying to use v('APP_USER') as default value for a column. I get null when I use it in select like
select v('APP_USER') from dual;
But when I use it as default in column, like below, I am getting error.
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')));
Error
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')))
Error report -
ORA-04044: procedure, function, package, or type is not allowed here
04044. 00000 - "procedure, function, package, or type is not allowed here"
*Cause: A procedure, function, or package was specified in an
inappropriate place in a statement.
*Action: Make sure the name is correct or remove it.
Can anyone explain this or have a turnaround for this ??
There are other options than V('APP_USER'). Since Apex 5, the APP_USER is stored in the sys_context and that is a lot more performant than the V() function. It is available as SYS_CONTEXT('APEX$SESSION','APP_USER').
It also works as a default value for tables:
create table test_table
(col_1 VARCHAR2(100) DEFAULT SYS_CONTEXT('APEX$SESSION','APP_USER'));
Table TEST_TABLE created.
That being said, the best practice for audit columns is a trigger that populates the the 4 audit columns (as #Littlefoot suggested). Have a look at quicksql (under SQL Workshop > Utilities or on livesql.oracle.com). You can have it generate the triggers for you if you set "include Audit columns" and "Apex Enabled". An example of such a generated trigger is:
create or replace trigger employees_biu
before insert or update
on employees
for each row
begin
if inserting then
:new.created := sysdate;
:new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end employees_biu;
/
One option is to use a database trigger, e.g.
CREATE OR REPLACE TRIGGER trg_biu_test
BEFORE INSERT OR UPDATE ON test
FOR EACH ROW
BEGIN
:new.col1 := v ('APP_USER');
END;
/
I have an oracle table which has
An auto increment primary key which uses a sequence.
Unique key
Non unique field/s
create table FOO (
ai_id number primary key,
name varchar(20),
bar varchar(20)
CONSTRAINT foo_uk_name UNIQUE (name)
);
create sequence FOO_seq;
create or replace trigger FOO_trg
before insert on FOO
for each row
begin
select FOO_seq.nextval into :new.ai_id from dual;
end;
I have separate stored procedure which upserts the table
create PROCEDURE UPSERT_FOO(
name_input IN VARCHAR2,
bar_input IN VARCHAR2
begin
begin
insert into FOO ( name, bar )
values ( name_input, bar_input )
exception
when dup_val_on_index then
update FOO
set bar = bar_input
where name = name_input
end;
end;
This works perfectly fine but the only issue is, sequence "FOO_seq" always increases regardless of whether it is an update or insert(As FOO_seq increments in "FOO_trg" before it inserts).
Is there a way to increment the sequence, only when there is an insert, without hurting the performance?
Oracle has a built-in merge statement to do an 'upsert':
create PROCEDURE UPSERT_FOO(
name_input IN VARCHAR2,
bar_input IN VARCHAR2
) as
begin
merge into foo
using (
select name_input as name, bar_input as bar from dual
) src
on (foo.name = src.name)
when matched then
update set foo.bar = src.bar
when not matched then
insert (name, bar)
values (src.name, src.bar);
end;
/
The insert only happens (and thus the trigger only fires, incrementing the sequence) if there is no match.
That doesn't have to be done through a procedure now, of course; you could just issue a merge directly, plugging in the name/bar values you would currently have to pass to the procedure.
Incidentally, your trigger could be simplified slightly to do an assignment:
create or replace trigger FOO_trg
before insert on FOO
for each row
begin
:new.ai_id := FOO_seq.nextval;
end;
/
db<>fiddles using your original code and using the code above. Notice the ID for 'b' in the final query; 5 in the first one, but only 2 in the second one.
Gaps in sequences shouldn't matter, of course; they are guaranteed to increment and be unique (if they don't cycle), not to be gapless. Or to necessarily be issued in strict order if you have a cache and are using RAC. Still, your approach would potentially waste a lot of values for no reason, and it doesn't need to be that complicated.
I've been working around this trigger and when I run the script it tells me the previous error message. I can't seem to figure out why it won't compile correctly, every pl/sql trigger tutorial seems to have the structure my trigger has. Code is the following:
create
or replace trigger new_artist before insert
on
Artist referencing new as nvartist declare counter number;
begin select
count( * ) into
counter
from
Performer
where
Stage_name = nvartist.Stage_name;
if counter > 0 then signal sqlstate '45000';
else insert
into
Artist
values(
nvartist.Stage_name,
nvartist.Name
);
insert
into
Performer
values(nvartist.Stage_name);
end if;
end;
It checks if the new artist already exists in its supertype (Performer), if it does exist it gives an error if it doesn't it inserts both into artist(Stage_name varchar2, Name varchar2) and Performer(Stage_name). Another subtype of Performer (and sibling to Artist) is Band(Stage_name), which in turn has a relationship with Artist. Why does the compiler yell at me for this trigger?
Thanks in advance
You may want to try this variant (I slightly modified names of your tables).
Creating tables with sample data:
CREATE table test_artist(
stage_name varchar2(100)
, name varchar2(100)
);
create table test_performer(
stage_name varchar2(100)
);
/*inserting test performer on which trigger will rise an error*/
insert into test_performer
select 'performer_1' from dual;
Creating trigger:
create or replace trigger new_artist
before insert
on TEST_ARTIST
referencing new as nvartist
for each row
declare
counter number;
begin
select count(*)
into counter
from test_performer
where Stage_name = :nvartist.Stage_name;
if counter > 0 then
--signal sqlstate '45000' ;
raise_application_error( -20001, 'No insertion with existing Performer');
else
/*you cant update the same table, in other case you'll get
ora-04091 table mutating error.
But nevertheless this values will be inserted by sql triggered this trigger.*/
--insert into test_artist values(:nvartist.Stage_name, :nvartist.Name);
insert into test_performer values(:nvartist.Stage_name);
end if;
end new_artist;
After that this insert will work, cause the is no 'performer_2' in 'test_performer' table:
insert into test_artist
select 'performer_2', 'name_2' from dual;
And this will fail:
insert into test_artist
select 'performer_1', 'name_1' from dual;
I have a customer table.
I have created stored procedure I can use to insert new data into the table. But what if I wanted to use the same procedure to update OR delete data from that table. Could I do this easily or do I have to use a separate function/procedure for each function?
create or replace procedure add_customer(custid in table.id%type,
name table.name%type)
is
begin
insert into table(id, name)
values(id, name);
commit;
end;
/
You can add parameter like action in example below and use it in code:
create or replace procedure modify_customer(
action in varchar2, custid in table.id%type, custname table.name%type)
is
begin
if action = 'insert' then
insert into table(id, name) values(custid, name);
commit;
elsif action = 'delete' then
delete from table where id = custid and name = custname;
commit;
end if;
end;
You can add a discriminator parameter to your add_customer procedure which says whether the action is INSERT, UPDATE or DELETE. Based on this parameter you can create the required insert, update or delete statement. This way you will be able to use a common procedure for all the actions.
As far as using one procedure or multiple procedures, if the table is a simple one with limited number of columns, one procedure should do fine. But once the number of columns increases in the table, one procedure might become more complicated than required.
How can i make multiple, composite query in oracle?
for example this several queries in one step?
1
CREATE TABLE test (id NUMBER PRIMARY KEY, name VARCHAR2(30));
2
CREATE SEQUENCE test_sequence START WITH 1 INCREMENT BY 1;
3
CREATE OR REPLACE TRIGGER test_trigger
BEFORE INSERT
ON test
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT test_sequence.nextval INTO :NEW.ID FROM dual;
END;
4
INSERT INTO test (name) VALUES ('Jon');
5
INSERT INTO test (name) VALUES ('Meloun');
We solved it by wrapping each statement in an EXECUTE IMMEDIATE command inside a PL/SQL script:
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE test (id NUMBER PRIMARY KEY, name VARCHAR2(30))';
EXECUTE IMMEDIATE 'CREATE SEQUENCE test_sequence START WITH 1 INCREMENT BY 1';
-- etc
END;
By and large DDL statements have to executed one at a time. It is true that Oracle supports the CREATE SCHEMA syntax, but this only permits the creation of multiple tables and/or views plus grants on them. It doesn't include ancilliary objects such as sequences or triggers.
You could turn the two inserts into a single statement like this:
INSERT INTO test (name)
select 'Jon' from dual
union all
select 'Meloun' from dual
/
This might be more trouble than its worth, but I suppose it does give you a simple transactionality: it inserts either all the rows or none of them.