I'm having trouble in getting started with PL/SQL
Here is my code:
SET SERVEROUTPUT ON;
DECLARE
v_cname customers.customer_name%type := '&customer_name';
v_cardno customers.card_number%type := '&card_number';
v_lastcid customers.customer_id%type;
BEGIN
SELECT customer_id INTO v_lastcid from customers
where customer_id = (select max(customer_id) from customers);
dbms_output.put_line(v_lastcid);
INSERT INTO customers(customer_id, customer_name, card_number)
VALUES(v_lastcid.NEXTVAL, v_cname, v_cardno);
COMMIT;
END;
This returns an error:
ORA-06550: line 12, column 20:
PLS-00487: Invalid reference to variable 'V_LASTCID'
ORA-06550: line 12, column 20:
PL/SQL: ORA-02289: sequence does not exist
ORA-06550: line 11, column 13:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
Any help would me much appreciated!
You probably meant to say MAX + 1 (see lines 7 and 12), i.e.
SQL> create table customers
2 (customer_id number,
3 customer_name varchar2(20),
4 card_number varchar2(20));
Table created.
SQL> DECLARE
2 v_cname customers.customer_name%type := '&customer_name';
3 v_cardno customers.card_number%type := '&card_number';
4 v_lastcid customers.customer_id%type;
5
6 BEGIN
7 SELECT nvl(max(customer_id), 0) INTO v_lastcid from customers
8 where customer_id = (select max(customer_id) from customers);
9 dbms_output.put_line(v_lastcid);
10
11 INSERT INTO customers(customer_id, customer_name, card_number)
12 VALUES(v_lastcid + 1, v_cname, v_cardno);
13 COMMIT;
14 END;
15 /
Enter value for customer_name: Little
Enter value for card_number: Foot
0
PL/SQL procedure successfully completed.
SQL> select * from customers;
CUSTOMER_ID CUSTOMER_NAME CARD_NUMBER
----------- -------------------- --------------------
1 Little Foot
SQL>
Although it works, it is doomed to fail in a multi-user environment if two (or more) users fetch the same MAX value; insert would fail with a DUP-VAL-ON-INDEX error (if the ID is supposed to be unique).
Therefore, use a sequence (which is what NEXTVAL in your code suggests):
SQL> create sequence seq_cust;
Sequence created.
SQL> DECLARE
2 v_cname customers.customer_name%type := '&customer_name';
3 v_cardno customers.card_number%type := '&card_number';
4 v_lastcid customers.customer_id%type;
5 BEGIN
6 INSERT INTO customers(customer_id, customer_name, card_number)
7 VALUES(seq_cust.nextval, v_cname, v_cardno);
8 COMMIT;
9 END;
10 /
Enter value for customer_name: Big
Enter value for card_number: Foot
PL/SQL procedure successfully completed.
nextval is used to get the next value from a sequence. It's usually used to create a pseudo primary key. You can think of it like a special function that can only be called on sequences.
v_lastcid is a variable that has the same type as the customer_id column in the customers table. So if you had a table like this...
CREATE TABLE CUSTOMERS ( CUSTOMER_ID INTEGER );
...then v_lastcid is an integer;
If you're trying to make a dummy customer with the next highest number maybe you mean something like...
INSERT INTO customers(customer_id, customer_name, card_number)
VALUES(v_lastcid + 1, v_cname, v_cardno);
Hope this helps.
Related
i need help from you guys.
I am using oracle apex for university, and my task is to create triggers. I'm struggling a lot creating them, I don't even know what is wrong with this trigger. Can someone help me?
I get error
Encountered the symbol "SELECT" when expecting one of the following: ( - + case mod new not null <an identifier> <a double-quoted delimited-identifier> <a bind variable> continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date <a string literal with character set specification> <a number> <a single-quoted SQL string> pipe <an alternatively-quoted string literal with character set specification> <an alternat
Error at line 3: PLS-00049: bad bind variable 'NEW'
Error at line 3: PLS-00103: Encountered the symbol "" when expecting one of the following: . ( * # % & = - + ; < / > at for in is mod remainder not rem <an exponent (**)> <> or != or ~= >= <= <> and or like like2 like4 likec between group having intersect minus order start union where connect || indicator multiset member submultiset
Error at line 3: PLS-00049: bad bind variable 'CONTRACT' Error at line 3: PLS-00049: bad bind variable 'CONTRACT' -
CREATE OR REPLACE TRIGGER restrict_orders
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
DECLARE countOrders number;
set countOrders := SELECT count(contract) FROM orders WHERE :new:contract := old:contract AND (:old.status := 'PREPARING' OR :old.status := 'IN PROGRESS');
IF (countOrders :> 3)
THEN
RAISE_APPLICATION_ERROR (-20202, 'You have too much active orders.');
END IF;
END;
Quite a few errors; see if this helps.
Sample table:
SQL> select * From orders;
CONTRACT STATUS
---------- ---------
1 PREPARING
Trigger:
SQL> CREATE OR REPLACE TRIGGER restrict_orders
2 BEFORE INSERT ON orders
3 FOR EACH ROW
4 DECLARE
5 countOrders number;
6 BEGIN
7 SELECT count(contract)
8 INTO countOrders
9 FROM orders
10 WHERE contract = :new.contract
11 AND status IN ('PREPARING', 'IN PROGRESS');
12
13 IF countOrders > 3 THEN
14 RAISE_APPLICATION_ERROR (-20202, 'You have too many active orders.');
15 END IF;
16 END;
17 /
Trigger created.
Testing:
SQL> insert into orders (contract, status) values (1, 'PREPARING');
1 row created.
SQL> insert into orders (contract, status) values (1, 'PREPARING');
1 row created.
SQL> insert into orders (contract, status) values (1, 'PREPARING');
1 row created.
SQL> insert into orders (contract, status) values (1, 'PREPARING');
insert into orders (contract, status) values (1, 'PREPARING')
*
ERROR at line 1:
ORA-20202: You have too many active orders.
ORA-06512: at "SCOTT.RESTRICT_ORDERS", line 11
ORA-04088: error during execution of trigger 'SCOTT.RESTRICT_ORDERS'
Kind of works. However, note that it'll fail if you attempt to insert more than a single row at a time because table will be mutating (so you'd have to take another, more complex approach), but - if you'll insert just one row at a time, you're good:
SQL> insert into orders (contract, status)
2 select 1, 'PREPARING' from dual union all
3 select 2, 'IN PROGRESS' from dual;
insert into orders (contract, status)
*
ERROR at line 1:
ORA-04091: table SCOTT.ORDERS is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.RESTRICT_ORDERS", line 4
ORA-04088: error during execution of trigger 'SCOTT.RESTRICT_ORDERS'
SQL>
You need to correct your syntax which should be -
CREATE OR REPLACE TRIGGER restrict_orders
BEFORE INSERT ON orders
FOR EACH ROW
DECLARE countOrders number;
BEGIN
SELECT count(contract)
INTO countOrders
FROM orders
WHERE :new.contract = :old.contract
AND :old.status IN ('PREPARING', 'IN PROGRESS');
IF (countOrders >= 3) THEN
RAISE_APPLICATION_ERROR (-20202, 'You have too much active orders.');
END IF;
END;
As a disclaimer, this question is only for my curiosity and for practicing triggers and compound triggers particularly.
I've been trying to replace the UNIQUE constraint with a trigger in order to understand triggers more, but I haven't been successful so far, mainly because of the global variables that I'm not so comfortable with inside the compound triggers.
what I'm trying to do with a trigger :
ALTER TABLE Employee
ADD CONSTRAINT emp_UQ
UNIQUE (id_emp, id_office);
here's what I tried so far (t for type, g for global):
CREATE OR REPLACE TRIGGER BIUUniqueConstraint
FOR INSERT OR UPDATE ON Employee
COMPOUND TRIGGER
TYPE tIdEmpOffice IS TABLE OF Employee.id_emp%TYPE
INDEX BY VARCHAR2(80);
gIdEmpOffice tIdEmpOffice;
TYPE tId_emp IS TABLE OF Employee.id_emp%TYPE;
gId_emp tId_emp;
TYPE tId_office IS TABLE OF Employee.id_office%TYPE;
gId_office tId_office;
BEFORE STATEMENT IS
BEGIN
SELECT e.id_emp, e.id_office
BULK COLLECT INTO gId_emp, gId_office
FROM Employee e
ORDER BY e.id_emp;
FOR j IN 1..gId_emp.COUNT() LOOP
gIdEmpOffice(gId_emp(j)) := gId_office(j);
END LOOP;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(gIdEmpOffice(:NEW.id_emp);
END IF;
END BEFORE EACH ROW;
END BIUCompteParti;
/
I have no clue how to move forward with this trigger and would like suggestions and explanations if possible about how to use globale variables to store data and how to use them on a row level.
Here's one option.
Sample table:
SQL> create table employee (id_emp number, id_office number);
Table created.
Trigger:
SQL> create or replace trigger trg_emp_unique
2 for insert or update on employee
3 compound trigger
4 type t_row is record (id_emp number, id_office number);
5 type t_tab is table of t_row;
6 l_tab t_tab := t_tab();
7 l_cnt number;
8
9 before each row is
10 begin
11 l_tab.extend;
12 l_tab(l_tab.last).id_emp := :new.id_emp;
13 l_tab(l_tab.last).id_office := :new.id_office;
14 end before each row;
15
16 after statement is
17 begin
18 for i in l_tab.first .. l_tab.last loop
19 select count(*) into l_cnt
20 from employee
21 where id_emp = l_tab(i).id_emp
22 and id_office = l_tab(i).id_office;
23 if l_cnt > 1 then
24 raise_application_error(-20000, 'Unique constraint violated');
25 end if;
26 end loop;
27 l_tab.delete;
28 end after statement;
29 end trg_emp_unique;
30 /
Trigger created.
SQL>
As you can see, it doesn't contain before statement nor after each row parts; if they aren't used, you don't have to put them into the trigger.
Let's try it:
SQL> insert into employee(id_emp, id_office) values (1, 1);
1 row created.
SQL> insert into employee(id_emp, id_office) values (1, 2);
1 row created.
Re-insert the first combination (1, 1):
SQL> insert into employee(id_emp, id_office) values (1, 1);
insert into employee(id_emp, id_office) values (1, 1)
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
That failed (as it should). Let's update existing rows:
SQL> select * from employee;
ID_EMP ID_OFFICE
---------- ----------
1 1
1 2
SQL> update employee set id_office = 5;
update employee set id_office = 5
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
Kind of works.
You can count whether a paired value for those columns already exists with a statement level trigger such as
CREATE OR REPLACE TRIGGER trg_chk_unique_emp_dept_id
AFTER INSERT ON employee
DECLARE
val INT;
BEGIN
SELECT NVL(MAX(COUNT(*)),0)
INTO val
FROM employee
GROUP BY id_emp, id_office;
IF val > 1 THEN
RAISE_APPLICATION_ERROR(-20304,
'Each employee may be assigned to a department once at most !');
END IF;
END;
/
which will check if an attempt made to insert the same value pairs more than once, and if so, it will hurl with an error message.
Demo
Error screenshot
I'm trying to create trigger :
Create or Replace trigger poin_member
After Insert on dtl_poin
For Each Row
Begin
Update into tb_member set total_poin = total_poin + NEW.tambahan poin
Where id_member=NEW.id_member;
End;
To add up every data insert on tambahan_poin that have the same id_member and insert the sum results into total_poin but error invalid table name. even though the table name entered is correct.
what's the solution?
Oracle points you to error position. It says:
LINE/COL ERROR
-------- -------------------------------------
2/1 PL/SQL: SQL Statement ignored
2/8 PL/SQL: ORA-00903: invalid table name
Line #2:
Update into tb_member
--------
12345678
^
|
error is here. Obviously, invalid syntax. You can't use INTO here
So, yes - you should pay attention to what Oracle says and do something about it.
Quite a few errors. I'm creating sample tables, just to make trigger compile. Read comments within code.
SQL> CREATE TABLE dtl_poin
2 (
3 id_member NUMBER,
4 tambahan_poin NUMBER --> in your trigger, you used "NEW.tambahan poin" which is invalid
5 -- as there can't be any space in column name (unless enclosed
6 -- into double quotes)
7 );
Table created.
SQL> CREATE TABLE tb_member
2 (
3 id_member NUMBER,
4 total_poin NUMBER
5 );
Table created.
SQL>
Trigger:
SQL> CREATE OR REPLACE TRIGGER poin_member
2 AFTER INSERT
3 ON dtl_poin
4 FOR EACH ROW
5 BEGIN
6 -- UPDATE INTO tb_member --> wrong syntax; INTO can't be used here
7 UPDATE tb_member
8 SET total_poin = total_poin + :NEW.tambahan_poin
9 WHERE id_member = :NEW.id_member;
10 END;
11 /
Trigger created.
Testing:
SQL> INSERT INTO tb_member (id_member, total_poin)
2 VALUES (1, 25);
1 row created.
SQL> INSERT INTO dtl_poin (id_member, tambahan_poin)
2 VALUES (1, 100);
1 row created.
SQL> SELECT * FROM dtl_poin;
ID_MEMBER TAMBAHAN_POIN
---------- -------------
1 100
SQL> SELECT * FROM tb_member;
ID_MEMBER TOTAL_POIN
---------- ----------
1 125 --> good; 100 was added to 25 and the result is now 125
SQL>
CREATE TABLE INVENTORY(
INVENTORY_ID NUMBER(6) PRIMARY KEY,
ITEM_NAME VARCHAR2(255),
QUANTITY NUMBER,
PRICE NUMBER,
ITEM_SIZE VARCHAR(255),
INVENTORY_VALUE NUMBER);
===
CREATE TABLE INVENTORY_AUDIT(
DATE_CHANGED DATE,
USER_NAME VARCHAR2(30),
INV_ID NUMBER(6),
OLD_QUANTITY NUMBER,
NEW_QUANTITY NUMBER,
CONSTRAINT fk_INV_ID
FOREIGN KEY (INV_ID)
REFERENCES INVENTORY (INVENTORY_ID));
===
CREATE OR REPLACE TRIGGER INV_CHG
BEFORE UPDATE OF QUANTITY
ON INVENTORY
FOR EACH ROW
BEGIN
IF :NEW.QUANTITY <> :OLD.QUANTITY THEN
INSERT INTO INVENTORY_AUDIT
VALUES (GETDATE(), v('APP_USER'), :NEW.INVENTORY_ID, :OLD.QUANTITY, :NEW.QUANTITY)
END IF;
END;
===
ORA-24344: success with compilation error
ORA-06512: at "SYS.WWV_DBMS_SQL_APEX_190200", line 592
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1658
ORA-06512: at "SYS.WWV_DBMS_SQL_APEX_190200", line 578
ORA-06512: at "APEX_190200.WWV_FLOW_DYNAMIC_EXEC", line 2057
3. ON INVENTORY
4. FOR EACH ROW
5. BEGIN
6. IF :NEW.QUANTITY <> :OLD.QUANTITY THEN
7. INSERT INTO INVENTORY_AUDIT
===
I tried to create trigger that triggered by inventory table's quantity change.
But it throws an error and syntax looks fine for me.
I tried without IF whole statement but still had same problem.
That means my create trigger statement has problem right?
But I cannot find it.
Two things:
missing semi-colon at the end of the INSERT statement
use sysdate instead of getdate
Here's how:
SQL> CREATE OR REPLACE TRIGGER INV_CHG
2 BEFORE UPDATE OF QUANTITY
3 ON INVENTORY
4 FOR EACH ROW
5 BEGIN
6 IF :NEW.QUANTITY <> :OLD.QUANTITY THEN
7 INSERT INTO INVENTORY_AUDIT
8 VALUES (sysdate, --> sysdate instead of getdate()
9 v('APP_USER'),
10 :NEW.INVENTORY_ID,
11 :OLD.QUANTITY,
12 :NEW.QUANTITY
13 ); --> missing semi-colon here
14 END IF;
15 END;
16 /
Trigger created.
SQL>
Table before solve:
ID NAME DATA
1 zhang 9
1 zhang 12
2 wang 1
2 wang 2
/this is the table before solved/
Table after solve:
ID NAME DATA
1 DIY 13
2 DIY 3
/this is what I want to get result/
There is the procedure:
update A a
set a.date=(select max(f_get(f.id,f.date,g.date))
from A f,A g
where f.date!=g.date
and f.id=a.id);
--function f_get()
create or replace function f_get
(id in varchar2,date in varchar,date2 in varchar )
return varchar is
Result varchar
date3 varchar(4);
begin
select nvl(date,date2) into date3
from dual;
Result:=date3;
delete from A a
where a.ID=id
and a.date=date2;--there is error
return(Result);
end f_get;
Your question does its best to hide itself, but this is the point:
"--there is error "
The error you get is (presumably) ORA-14551: cannot perform a DML operation inside a query, which you are getting because you are calling a FUNCTION which includes a DELETE command from a SELECT statement.
Oracle's transactional model doesn't allow queries to change the state of the database. Instead of a FUNCTION you need to write a procedure.
Although, if you want to remove duplicate rows, a straight SQL solution will suffice. Something like
delete from a
where (id, date) not in
( select id, max(date) from a
group by id)
/
You really should pay attention how to write questions. It would help us to help you. This is my guess what you are looking for. Unfortunately I don't have 9i available, but hope this helps !
create table so7t (
id number,
name varchar2(10),
data number -- date is a reserved word and can't be used as identifier
);
-- 1001
insert into so7t values (1, 'zhang', 9);
-- 1100
insert into so7t values (1, 'zhang', 12);
-- 0001
insert into so7t values (2, 'wang', 1);
-- 0010
insert into so7t values (2, 'wang', 2);
select * from so7t;
/* from http://www.dbsnaps.com/oracle/bitwise-operators-in-oracle/ */
create or replace function bitor (x number, y number)
return number
is
begin
return (x+y)-bitand(x,y);
end;
/
show errors
create or replace procedure solve (
p_id in number
) as
type ids_t is table of number;
v_ids ids_t;
v_result number := 0;
begin
select data bulk collect into v_ids from so7t where id = p_id;
for i in v_ids.first .. v_ids.last loop
v_result := bitor(v_result, v_ids(i));
end loop;
delete from so7t where id = p_id;
insert into so7t values (p_id, 'DIY', v_result);
end;
/
begin
solve(1);
commit;
solve(2);
commit;
end;
/
Table before solve:
ID NAME DATA
---------- ---------- ----------
1 zhang 9
1 zhang 12
2 wang 1
2 wang 2
Table after solve:
ID NAME DATA
---------- ---------- ----------
1 DIY 13
2 DIY 3