Create trigger with a cursor inside for multiple inserts - oracle

I want to create a trigger on every insert to the USERS table that will insert several entries to the BALANCES table, one entry for every coin id that exists in the SQL_COINS_VIEW view (hope i'm clear enough). The idea is that every time an account is created, it gets a balance 0 for each available coin in the list.
I tried this -
CREATE or REPLACE TRIGGER update_balances
AFTER INSERT
ON USERS
FOR EACH ROW
DECLARE
v_userid number(8);
cursor coinlist_cur is
select ID from SQL_COINS_VIEW;
BEGIN
select ID into v_userid from USERS;
For coinid in coinlist_cur
loop
insert into balances
(BALANCES_ID_SEQ.NEXTVAL,v_userid,coinid,0);
end loop;
END;
But i get an error -
Error(10,1): PL/SQL: SQL Statement ignored
Error(11,42): PL/SQL: ORA-01747: invalid user.table.column, table.column, or column specification
The tables are structures this way -
SQL> desc SQL_COINS_VIEW;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER(10)
NAME NOT NULL VARCHAR2(50)
VALUE NOT NULL NUMBER(18,6)
UPDATETIME NVARCHAR2(10)
SQL>
SQL> desc USERS;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER(8)
LOGINNAME NOT NULL VARCHAR2(12)
PASSWORD NOT NULL VARCHAR2(12)
EMAIL NOT NULL VARCHAR2(50)
PHONENUMBER VARCHAR2(25)
SQL>
SQL> desc BALANCES;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER(12)
USERID NOT NULL NUMBER(8)
COINID NOT NULL NUMBER(10)
AMOUNT NOT NULL NUMBER(30)
How can i properly create this trigger?
Thanks in advance.

Your insert is missing the values keyword; the error is from it trying to interpret the values as column names. And coinid is a record, so you need to refer to a field within that, which is id in the cursor declaration:
insert into balances
values (BALANCES_ID_SEQ.NEXTVAL,v_userid,coinid.id,0);
It's good practice to list the columns too though.

Related

ORA-00904: invalid identifier from PL/SQL code while the SQL part within works

When I defined a PL/SQL function in SQL developer and tried to run it, it returned "ORA-00904: "SYS"."FUNC1": invalid identifier;00904. 00000 - "%s: invalid identifier"", which isn't very helpful. I don't know what has gone wrong. The SQL part alone can run though.
the PL/SQL that return error
CREATE OR REPLACE FUNCTION func1 (
emp_id IN NUMBER
) RETURN NUMBER AS
emp_fname VARCHAR2(50);
BEGIN
SELECT
firstname
INTO emp_fname
FROM
employees
WHERE
employeeid = emp_id;
RETURN emp_fname;
END func1;
/
select sys.func1(9) from dual;
the SQL that run
SELECT
firstname
FROM
employees
WHERE
employeeid = 9;
Definition of Table Employees
Name Null? Type
--------------- -------- -------------
EMPLOYEEID NOT NULL NUMBER
LASTNAME NOT NULL VARCHAR2(20)
FIRSTNAME NOT NULL VARCHAR2(10)
TITLE VARCHAR2(30)
TITLEOFCOURTESY VARCHAR2(25)
BIRTHDATE DATE
HIREDATE DATE
ADDRESS VARCHAR2(60)
CITY VARCHAR2(15)
REGION VARCHAR2(15)
POSTALCODE VARCHAR2(10)
COUNTRY VARCHAR2(15)
HOMEPHONE VARCHAR2(24)
EXTENSION VARCHAR2(4)
PHOTO LONG RAW
NOTES VARCHAR2(600)
REPORTSTO NUMBER
PHOTOPATH VARCHAR2(255)
Please help!
Test environment:
OS: Oracle Linux 7.9
Oracle DB 21c Express Edition for Linux
SQL Developer for Linux 21.2.1.204 build 204.1703
Don't use SYS as a work area. If you mess with any of the system tables then you may make your database unusable.
Use the SYS user to create a new user and then work in that user's schema.
Your function compiles successfully; however, it will fail at runtime (except for those rare people who are known by numbers and not names) as the signature is RETURN NUMBER but it returns the firstname which is a string and you would get the exception:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "FIDDLE_RAHCBFZAHWTUCSZNOWGB.FUNC1", line 14
You can fix it by making the return type the same as the firstname column and the simplest method is to use %TYPE. You also ought to handle a NO_DATA_FOUND exception:
CREATE OR REPLACE FUNCTION func1 (
emp_id IN EMPLOYEES.EMPLOYEEID%TYPE
) RETURN EMPLOYEES.FIRSTNAME%TYPE
AS
emp_fname VARCHAR2(50);
BEGIN
SELECT firstname
INTO emp_fname
FROM employees
WHERE employeeid = emp_id;
RETURN emp_fname;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END func1;
/
If you have the sample data:
INSERT INTO employees (employeeid, lastname, firstname)
VALUES (9, 'Abbots', 'Alice');
Then:
select func1(9) from dual;
Outputs:
FUNC1(9)
Alice
db<>fiddle here

How to get information about all types of data declared in a given package

Using the information from the representations of the data dictionary, get information about all types of data declared in a given package.
NAME | TYPE | PACKAGE
----------------------------------------------------
T_ASSOCIATIVE | ASSOCIATIVE ARRAY | MY_TYPES
T_TABLE_TYPE | NESTED TABLE | MY_TYPES
T_CURSOR_TYPE | EREFCURSOR |
...
The program should be issued in the form of an anonymous block.
1) desc package_name;
no variants to get certain rows, just parse: bad practive for such task
2)
select * from user_types;
select * from user_type_attrs;
select * from user_type_methods;
select * from user_procedures;
select * from user_source;
Doesn't get any result, only package type, but not types declared within this package
You can see PL/SQL types in the all_plsql_types view
SQL> > desc all_plsql_types
Name Null? Type
----------------------------------------------------------------- -------- --------------------------------------------
OWNER NOT NULL VARCHAR2(128)
TYPE_NAME VARCHAR2(136)
PACKAGE_NAME NOT NULL VARCHAR2(128)
TYPE_OID NOT NULL RAW(16)
TYPECODE VARCHAR2(58)
ATTRIBUTES NUMBER
CONTAINS_PLSQL VARCHAR2(3)
... or the dba_ or user_ versions if you prefer.
That view is only available from 12c. In 11gR2, if you PL/Scope enabled, you can extract that information from the all_identifiers view:
SQL> desc all_identifiers;
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER NOT NULL VARCHAR2(30)
NAME VARCHAR2(30)
SIGNATURE VARCHAR2(32)
TYPE VARCHAR2(18)
OBJECT_NAME NOT NULL VARCHAR2(30)
OBJECT_TYPE VARCHAR2(13)
USAGE VARCHAR2(11)
USAGE_ID NUMBER
LINE NUMBER
COL NUMBER
USAGE_CONTEXT_ID NUMBER
... or the dba_ or user_ versions if you prefer.
Quick demo:
alter session set PLSCOPE_SETTINGS='IDENTIFIERS:ALL';
create package my_types as
type T_ASSOCIATIVE is table of number index by pls_integer;
type T_TABLE_TYPE is table of number;
type T_CURSOR_TYPE is ref cursor;
end my_types;
/
select name, type
from user_identifiers
where object_name = 'MY_TYPES'
and usage = 'DECLARATION'
and type != 'PACKAGE'
order by name;
NAME TYPE
------------------------------ ------------------
T_ASSOCIATIVE INDEX TABLE
T_CURSOR_TYPE REFCURSOR
T_TABLE_TYPE NESTED TABLE
You may need to recompile existing objects; either by recreating them or less intrusively with alter package:
alter session set PLSCOPE_SETTINGS='IDENTIFIERS:ALL';
alter package my_types compile;

Cast SYS_REFCURSOR to a PL/SQL Type

I have a stored procedure that calls a function that returns a SYS_REFCURSOR. The function returns a SYS_REFCURSOR because this function is also called from Java application and Java does not understand rowtype.
Here is my function.
function f_get_building(
p_building_id in T_BUILDING.ID%type
) return sys_refcursor
AS
v_cursor sys_refcursor;
BEGIN
open v_cursor for
select
BUILDING_ID,
CAMPUS_ID,
DELETE_FLAG,
max(EFFECTIVE_DATE),
END_DATE,
IMAGE_URL,
INSTITUTION_ID,
LOCAL_ID,
LOCATION_ID,
NAME
from V_BUILDING
where BUILDING_ID = p_building_id
group by
BUILDING_ID,
CAMPUS_ID,
DELETE_FLAG,
END_DATE,
IMAGE_URL,
INSTITUTION_ID,
LOCAL_ID,
LOCATION_ID,
NAME;
return v_cursor;
END f_get_building;
In another stored procedure I am also calling this function but having issues using it. Here is the stored procedure.
procedure sp_delete_building(
p_building_id in T_BUILDING.ID%type,
p_permanent_delete in boolean default false
)
AS
v_building_cur sys_refcursor;
v_building_rec V_BUILDING%rowtype;
BEGIN
-- if permanment delete
if p_permanent_delete = true
then
delete from T_BUILDING where ID = p_building_id;
-- otherwise perform soft delete
else
-- lookup
v_building_cur := f_get_building(p_building_id);
-- if cursor is empty there is nothing to do
if v_building_cur%notfound then
return;
end if;
fetch v_building_cur into v_building_rec; -- this line is where the error happens
-- if its already deleted nothing to do
if v_building_rec.DELETE_FLAG = 'Y'
then
return;
else
insert into T_BUILDING_ATTRIBUTE(BUILDING_ID,EFFECTIVE_DATE,DELETE_FLAG,
IMAGE_URL,LOCATION_ID,NAME)
values (v_building_rec.BUILDING_ID,current_timestamp,'Y',v_building_rec.IMAGE_URL
,v_building_rec.LOCATION_ID,v_building_rec."NAME");
end if;
end if;
END sp_delete_building;
I am getting the following PL/SQL stacktrace.
ORA-01722: invalid number
ORA-06512: at "OBR.PKG_BUILDING", line 114
ORA-06512: at line 8
Forgive my ignorance, this is my first project using PL/SQL, I would classify myself as a Java developer, not a database developer. Since I am selecting everything from V_BUILDING I expected I would just be able to case it as a rowtype inside the stored procedure. How can I use my function inside my stored procedure?
Update:
Here is the create statement for V_BUILDING
CREATE OR REPLACE FORCE VIEW "OBR"."V_BUILDING" ("BUILDING_ID", "LOCAL_ID", "INSTITUTION_ID", "EFFECTIVE_DATE", "END_DATE", "DELETE_FLAG", "CAMPUS_ID", "LOCATION_ID", "IMAGE_URL", "NAME") AS
SELECT
ba.BUILDING_ID,
b.LOCAL_ID,
b.INSTITUTION_ID,
ba.EFFECTIVE_DATE,
NVL(MIN(ba2.EFFECTIVE_DATE - INTERVAL '0.000001' SECOND),TO_DATE('31-DEC-9999', 'DD-MON-YYYY')) AS END_DATE,
ba.DELETE_FLAG,
ba.CAMPUS_ID,
ba.LOCATION_ID,
ba.IMAGE_URL,
ba.NAME
FROM
T_BUILDING b
INNER JOIN T_BUILDING_ATTRIBUTE ba
ON b.ID = ba.BUILDING_ID
LEFT JOIN T_BUILDING_ATTRIBUTE ba2
ON ba.BUILDING_ID = ba2.BUILDING_ID
AND ba2.EFFECTIVE_DATE > ba.EFFECTIVE_DATE
GROUP BY
ba.BUILDING_ID,
b.LOCAL_ID,
b.INSTITUTION_ID,
ba.EFFECTIVE_DATE,
ba.DELETE_FLAG,
ba.CAMPUS_ID,
ba.LOCATION_ID,
ba.IMAGE_URL,
ba.NAME
ORDER BY ba.BUILDING_ID, ba.EFFECTIVE_DATE DESC;
Update 2:
Here is a screenshot of the types in the view
CAMPUS_ID - NUMBER(10)
LOCATION_ID - NUMBER(10)
IMAGE_URL - VARCHAR(500)
NAME - VARCHAR(255)
BUILDING_ID - NUMBER(10)
LOCAL_ID - VARCHAR(30)
INSTITUTION_ID - NUMBER(10)
EFFECTIVE_DATE - TIMESTAMP(6)
END_DATE - TIMESTAMP (6)
DELETE_FLAG - CHAR(1)
Here are the list of columns and their datatypes as returned by the view and the ref cursor:
LIST OF COLS FROM VIEW DATATYPE FROM VIEW LIST OF COLS FROM CURSOR DATATYPE FROM CURSOR
---------------------- ------------------ ------------------------ --------------------
BUILDING_ID NUMBER(10) BUILDING_ID NUMBER(10)
LOCAL_ID VARCHAR(30) CAMPUS_ID NUMBER(10)
INSTITUTION_ID NUMBER(10) DELETE_FLAG CHAR(1)
EFFECTIVE_DATE TIMESTAMP(6) max(EFFECTIVE_DATE) TIMESTAMP(6)
END_DATE TIMESTAMP(6) END_DATE TIMESTAMP(6)
DELETE_FLAG CHAR(1) IMAGE_URL VARCHAR(500)
CAMPUS_ID NUMBER(10) INSTITUTION_ID NUMBER(10)
LOCATION_ID NUMBER(10) LOCAL_ID VARCHAR(30)
IMAGE_URL VARCHAR(500) LOCATION_ID NUMBER(10)
NAME VARCHAR(255) NAME VARCHAR(255)
They are not the same, yet by using the V_BUILDING%ROWTYPE in your sp_delete_building procedure, you're treating the ref cursor results as if the column order is the same as that of the view.
You can see that there are several mismatches between the datatypes of the view and the cursor select lists - it's probably the "LOCATION_ID/LOCAL_ID" mismatch that's causing the invalid number error that you're seeing.
You either need to change the order of your ref cursor so that the list of columns is returned in the same order as that of the view, or to explicitly list the columns of the cursor in the v_building_rec record type.
As an aside, you should give your max(EFFECTIVE_DATE) column in the refcursor an alias.

PL/SQL Trigger in Oracle errors

i don't know how to start !!
i have a work in oracle database,and it is all about triggers and constraints ...
the work is to create triggers and constraints on some tables of database of league of hokey ...
and since i'm new , and not familiar with triggers i have a lot of errs!!!
let's take these two tables as exemple :
1/ "equipe" (means team) table :
Name Null? Type
----------------------------------------- -------- ----------------------------
ID_EQ NOT NULL NUMBER(6)
NOM VARCHAR2(50)
ENREGISTRMENT VARCHAR2(50)
ID_LIG NUMBER(6)
ID_CAPITAINE NUMBER(6)
ID_ENT NUMBER(6)
2/ "joueur" (means player) :
Name Null? Type
----------------------------------------- -------- ----------------------------
ID_JOU NOT NULL NUMBER(6)
NUMERO NUMBER(4)
POSITION VARCHAR2(50)
ID_EQ NUMBER(6)
where :
"id_eq" and "id_jou" are primary keys.
"joueur.id_eq" is referenced to "equipe.id_eq".
"equipe.id_capitaine" is referenced to "joueur.id_jou".
i want to create a trigger that write a msg of err if the user insert in or update the table "equipe" where the "capitaine" is not a player in the team ("equipe") , i try a lot , buttt ... always the msg:
Warning: Trigger created with compilation errors.
This is one of the triggers , if someone can find the err and fix it , or suggest a better one :
CREATE OR REPLACE TRIGGER capitaine_in_equipe
before UPDATE OR INSERT ON equipe
FOR EACH ROW
DECLARE
id_p joueur.id_eq%TYPE;
BEGIN
if (:new.iq_capitaine is not null ) then
SELECT id_eq INTO id_p
FROM joueur
WHERE id_jou = :new.iq_capitaine;
IF ( id_p != :new.id_eq ) THEN
raise_application_error(-20100,' the captain is not a player of the team');
END IF;
END IF;
END;
and if you know some good references of triggers, pl/sql Oracle for biggeners put it, please!
thank you ;)
Surely, there are other way to implement your logic, but if you want to use your trigger, the following works for me
Create equipe:
CREATE TABLE equipe
( ID_EQ number(6) not null,
NOM varchar2(50),
ENREGIS number(6),
ID_CAPITAINE number(6),
ID_ENT number(6),
CONSTRAINT equipe_pk PRIMARY KEY (ID_EQ)
);
Create joueur:
create table joueur
(ID_JOU number(6) not null,
NUMERO number(4),
POSITION varchar2(50),
ID_EQ number(6),
CONSTRAINT joueur_pk PRIMARY KEY (id_jou)
);
Alter both with the foreign key:
alter table equipe add(CONSTRAINT fk_equipe
FOREIGN KEY (ID_CAPITAINE)
REFERENCES joueur(ID_JOU));
alter table joueur add(CONSTRAINT fk_joueur
FOREIGN KEY (id_eq)
REFERENCES equipe(ID_EQ));
Create your trigger:
CREATE OR REPLACE TRIGGER capitaine_in_equipe
before UPDATE OR INSERT ON equipe
FOR EACH ROW
DECLARE
id_p joueur.id_eq%TYPE;
BEGIN
if (:new.id_capitaine is not null ) then
SELECT id_eq INTO id_p
FROM joueur
WHERE id_jou = :new.id_capitaine;
IF ( id_p != :new.id_eq ) THEN
raise_application_error(-20100,' the captain is not a player of the team');
END IF;
END IF;
END;
Notice in your tables definition, you mentioned a column id_capitaine. In your trigger you used the name iq_capitaine. I'm not sure if that reflects your real code or just a typo here.

PL/SQL Error on trigger for auto_increment

I'm trying to auto_increment the cust_id field in the following table:
CREATE TABLE A113222813_CUSTOMERS (
CUST_ID NUMBER(10) PRIMARY KEY,
CUST_FNAME VARCHAR2(20),
CUST_SNAME VARCHAR2(20),
CUST_UNAME VARCHAR2(30) NOT NULL,
CUST_PASS VARCHAR2(40) NOT NULL
)
I create the following sequence and trigger to handle this:
CREATE SEQUENCE CUST_SEQ START WITH 1 INCREMENT BY 1 NOCYCLE;
CREATE OR REPLACE TRIGGER CUST_TRG
BEFORE INSERT ON A113222813_CUSTOMERS
FOR EACH ROW
BEGIN
:NEW.CUST_ID := CUST_SEQ.NEXTVAL;
END;
But it keeps throwing the following error:
Error(2,30): PLS-00357: Table,View Or Sequence reference 'CUST_SEQ.NEXTVAL' not allowed
in this context
Any idea what I am doing wrong?
This is not possible before 11g. You can use sequence_name.NEXTVAL in regular assignments from 11g not before that, and that by the following:
select CUST_SEQ.NEXTVAL into :NEW.CUST_ID from dual;

Resources