I have a very basict PL/SQL question.
How do I loop through values of a certain table column and update them?
Do i do this in a loop?
There is usually no need to loop through a table. All you do is to execute an update statement of the kind
UPDATE mytable
SET myfield = 'new value'
WHERE somecondition;
But you could also define a cursor for update. Google for "oracle cursor loop update current". One example is this http://www.techonthenet.com/oracle/cursors/current_of.php
Here is my own example:
create table my_example(id number, somefield varchar2(80));
insert into my_example values (1, 'test 1');
insert into my_example values (2, 'test 2');
insert into my_example values (3, 'test 3');
set linesize 100
select * from my_example;
set serveroutput on
declare
l_somefield varchar2(80);
cursor c is select somefield from my_example for update of somefield;
begin
open c;
loop
fetch c into l_somefield;
exit when c%notfound;
update my_example
set somefield = l_somefield || ' some more text'
where current of c;
end loop;
close c;
end;
select * from my_example;
drop table my_example;
Output
Table created.
1 row created.
1 row created.
1 row created.
ID SOMEFIELD
---------- --------------------------------------------------------------------------------
1 test 1
2 test 2
3 test 3
3 rows selected.
PL/SQL procedure successfully completed.
ID SOMEFIELD
---------- --------------------------------------------------------------------------------
1 test 1 some more text
2 test 2 some more text
3 test 3 some more text
3 rows selected.
Table dropped.
Related
CREATE OR REPLACE TRIGGER "DUPLICATE_FOO" AFTER INSERT ON "FOO" FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
insert into remote_foo values(:new); -- can I do this?
EXCEPTION
-- TODO log somewhere
END;
Is there an elegant way to create a trigger that basically duplicates a record from one table to another?
I would like to avoid having to specify the fields of the table since it will mean that the trigger would have to be updated in case there are changes in the schema (the remote scheme would be updated of course). I have like a dozen of tables more.
All the examples I have found always specify the fields in the insert (:new.fieldX).
The keyword here is NOT to specify column names, right? In my opinion, you should because that's the only way you can control it.
the remote scheme would be updated of course
is kind of dangerous. WHAT IF it doesn't happen? "Of course" works until it does not work.
Sample tables (both are empty):
SQL> create table foo (id number, name varchar2(20));
Table created.
SQL> create table remote_foo as select * From foo where 1 = 2;
Table created.
If you use a trigger which is an autonomous transaction, then it won't see :new pseudorecord (as this is an autonomous transaction; right?); to this trigger, select * from foo where id = :new.id; won't return anything and remote_foo remains empty:
SQL> create or replace trigger trg_ai_foo
2 after insert on foo
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 insert into remote_foo select * from foo where id = :new.id;
8 commit;
9 end;
10 /
Trigger created.
SQL> insert into foo (id, name) values (1, 'Littlefoot');
1 row created.
SQL> select * from foo;
ID NAME
---------- --------------------
1 Littlefoot
SQL> select * from remote_foo; --> it remained empty
no rows selected
SQL>
Note that - if you specified columns - it would work (but that's not what you wanted):
SQL> create or replace trigger trg_ai_foo
2 after insert on foo
3 for each row
4 declare
5 pragma autonomous_transaction;
6 begin
7 insert into remote_foo (id, name) values (:new.id, :new.name);
8 commit;
9 end;
10 /
Trigger created.
SQL> insert into foo (id, name) values (2, 'Bigfoot');
1 row created.
SQL> select * from foo;
ID NAME
---------- --------------------
2 Bigfoot
SQL> select * from remote_foo;
ID NAME
---------- --------------------
2 Bigfoot
SQL>
So, what to do? Switch to a statement-level trigger (instead of a row-level): it doesn't have to be autonomous, but has to have something that will prevent duplicates to be inserted - for example, a NOT EXISTS clause:
SQL> create or replace trigger trg_ai_foo
2 after insert on foo
3 begin
4 insert into remote_foo
5 select * from foo a
6 where not exists (select null from remote_foo b
7 where b.id = a.id);
8 end;
9 /
Trigger created.
SQL> insert into foo (id, name) values (1, 'Littlefoot');
1 row created.
SQL> insert into foo (id, name)
2 select 2, 'Bigfoot' from dual union all
3 select 3, 'anat0lius' from dual;
2 rows created.
Result:
SQL> select * from foo;
ID NAME
---------- --------------------
2 Bigfoot
3 anat0lius
1 Littlefoot
SQL> select * from remote_foo;
ID NAME
---------- --------------------
1 Littlefoot
3 anat0lius
2 Bigfoot
SQL>
I am using the following TRIGGER to Insert in my TEST Table:
create or replace
TRIGGER TRG_CYCLE
BEFORE INSERT ON TEST_CYCLE
FOR EACH ROW
BEGIN
IF :NEW.LOGID IS NULL
THEN SELECT SEQ_CYCLE.nextval INTO :NEW.LOGID from dual;
END IF;
END;
SEQ_CYCLE is the Sequence. Now I have to use the CYCLE option in order to start with 1 after the MAX_VALUE is reached. Since I don't want duplicate LOGIDs I want to do an UPDATE if the LOGID exists, if not an INSERT. Can I do this inside the Trigger?
If I understand well, you may need an INSTEAD OF trigger; to build such a trigger you need to create a VIEW over your table and build the trigger on this view.
For example, say you have:
create sequence SEQ_CYCLE maxValue 3 cycle nocache;
create table TEST_CYCLE ( logId number, someColumn varchar2(20));
You can create a view and a trigger over the view:
create or replace view v_test_cycle as select * from test_cycle;
create or replace trigger trgCycle
instead of insert on v_test_cycle
for each row
declare
vCheck number;
vSeqVal number := SEQ_CYCLE.nextVal;
begin
select count(*)
into vCheck
from v_test_cycle
where logId = vSeqVal;
--
if vCheck = 0 then
insert into test_cycle(logId, someColumn) values ( vSeqVal, :NEW.someColumn);
else
update test_cycle
set someColumn = :NEW.someColumn
where logId = vSeqVal;
end if;
end;
If you do the INSERTs on the view, this is what you get:
SQL> select * from test_cycle;
no rows selected
SQL> insert into v_test_cycle(someColumn) values('some value 1');
1 row created.
SQL> insert into v_test_cycle(someColumn) values('some value 2');
1 row created.
SQL> insert into v_test_cycle(someColumn) values('some value 3');
1 row created.
SQL> insert into v_test_cycle(someColumn) values('some value 4');
1 row created.
SQL> insert into v_test_cycle(someColumn) values('some value 5');
1 row created.
SQL> select * from test_cycle;
LOGID SOMECOLUMN
---------- --------------------
1 some value 4
2 some value 5
3 some value 3
SQL>
I need to execute some statements within the IF clause only if a table exists.
But the issue I am facing is, even when the condition is false, the statements are getting executed.
DECLARE
count_matching_row NUMBER := 0;
count_matching_tbl NUMBER := 0;
BEGIN
SELECT COUNT(*)
INTO count_matching_tbl
FROM user_tables
WHERE LOWER(table_name) = 'tab1';
IF(count_matching_tbl = 1)
THEN
SELECT COUNT (*)
INTO count_matching_row
FROM test1
WHERE ID IN (SELECT ID FROM tab1);
IF(count_matching_row = 0)
THEN
INSERT INTO review_case
SELECT
DISTINCT ID, d,e
FROM tab1
WHERE ID IS NOT NULL;
INSERT INTO review_case_payer
SELECT
a,b,c
FROM tab1
WHERE a IS NOT NULL;
COMMIT;
END IF;
END IF;
END;
/
Whenever I execute these statements, if the table 'tab1' exists it works fine.
If the table tab1 does not exist I get the error
"ORA-06550: line 13, column 14:
PL/SQL: ORA-00942: table or view does not exist"
I get similar errors for each line where I try to access table "tab1"
I tried with ref cursor but still the same, I cannot use it for insert statements.
Your error is due to the fact that you're using a table that may not exist; this error is thrown because the script has compile problems, not data problems, so the way you try to use the IF is not enough to handle your situation.
You need to use some dynamic SQL to handle an object that could not exist; for example, see the following.
If the table does not exist, nothing will be done:
SQL> select * from tab1;
select * from tab1
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> declare
2 vCountTab number;
3 begin
4 select count(1)
5 into vCountTab
6 from user_tables
7 where table_name = 'TAB1';
8
9 if vCountTab = 1 then
10 execute immediate 'insert into TAB1 values (1, 2)';
11 end if;
12 end;
13 /
PL/SQL procedure successfully completed.
If the table exists, the insert will be done:
SQL> create table tab1(a number, b number);
Table created.
SQL> declare
2 vCountTab number;
3 begin
4 select count(1)
5 into vCountTab
6 from user_tables
7 where table_name = 'TAB1';
8
9 if vCountTab = 1 then
10 execute immediate 'insert into TAB1 values (1, 2)';
11 end if;
12 end;
13 /
PL/SQL procedure successfully completed.
SQL> select * from tab1;
A B
---------- ----------
1 2
SQL>
Is it possible to check the count of a table before any changes happen and the count after the insert and match them inside the same trigger?
for ex: old.count and new.count (before and after insert) ?
old.count and new.count (before and after insert)
Nothing stops you from using SELECT COUNT(*) in a before insert trigger. Of course, you won't do it in a after insert trigger, since a select count(*) on the same table on which an after trigger is defined would throw mutating table error. One way is autonomous transaction. But, in your case, it isn't that complex.
You could define a BEFORE INSERT TRIGGER and take the table count. The after insert count could be taken manually after the actual insert is done.
For example, I have a table t1 with one row. I have defined a before insert trigger on it, which would give me the table count before the insert happens.
SQL> DROP TABLE t1 PURGE;
Table dropped.
SQL>
SQL> CREATE TABLE t1 (A NUMBER);
Table created.
SQL>
SQL> INSERT INTO t1 VALUES (1);
1 row created.
SQL>
SQL> SELECT * FROM t1;
A
----------
1
SQL>
SQL> CREATE OR REPLACE TRIGGER trg
2 BEFORE INSERT
3 ON t1
4 FOR EACH ROW
5
6 DECLARE
7 val number;
8 BEGIN
9 SELECT COUNT(*)
10 INTO val
11 FROM t1;
12
13 DBMS_OUTPUT.PUT_LINE('TABLE COUNT BEFORE INSERT = '||val);
14
15 END;
16 /
Trigger created.
SQL>
SQL> set serveroutput on
SQL> INSERT INTO t1 VALUES (1);
TABLE COUNT BEFORE INSERT = 1
1 row created.
SQL>
SQL> SELECT COUNT(*) FROM t1;
COUNT(*)
----------
2
SQL>
So, you see TABLE COUNT BEFORE INSERT = 1 and then after insert the count is 2.
I am running the following in the Scott schema:
SET serveroutput ON;
BEGIN
FOR c_Emp IN (SELECT * FROM emp)
LOOP
dbms_output.put_line('The record processed by the cursor ' || c_Emp%rowcount);
END LOOP;
end;
This gives the error:
cursor attribute may not be applied to non-cursor 'C_EMP'
However if this is done using an explicit cursor it works fine:
set serveroutput on ;
DECLARE
emp_record emp%ROWTYPE;
count_variable NUMBER;
CURSOR c IS
SELECT * FROM emp;
BEGIN
OPEN c;
loop
fetch c INTO emp_record;
exit WHEN c%notfound;
dbms_output.put_line ('The record count is ' || c%rowcount);
END loop;
close c;
end;
Just want to understand : whether while using the CURSOR FOR LOOP, is the index variable not a cursor attribute, if so why? could someone plz expalin this....
c_Emp is not the cursor, its a record with felds for each column in the SELECT statment
c_Emp is similar to the emp_record from your second example.
Even while using a FOR loop the cursor has to be explicitly defined.
A sample use of FOR loop with a cursor would look like below:
declare
cursor c1 is select a from table;
begin
FOR b in c1
loop
<required logic>
end loop;
end;
To get the index in a for loop, you can add the rownum pseudocolumn in the select clause of implicit cursor.
SET serveroutput ON;
BEGIN
FOR c_Emp IN (SELECT e.*, rownum FROM emp e)
LOOP
dbms_output.put_line('The record processed by the cursor ' || c_Emp.rownum);
END LOOP;
end;
Try this:
SET serveroutput ON;
DECLARE
x NUMBER :=0 ;
BEGIN
FOR c_Emp IN (SELECT * FROM emp)
LOOP
x := x+1;
dbms_output.put_line('The record processed by the cursor ' || x);
END LOOP;
-----
IF x>0 THEN
dbms_output.put_line('Cursr was opened');
ELSE
dbms_output.put_line('Cursr was not opened');
END IF;
end;
SQL> create table product(
2 product_id number(4) not null,
3 product_description varchar2(20) not null
4 );
Table created.
SQL>
SQL> insert into product values (1,'Java');
1 row created.
SQL> insert into product values (2,'Oracle');
1 row created.
SQL> insert into product values (3,'C#');
1 row created.
SQL> insert into product values (4,'Javascript');
1 row created.
SQL> insert into product values (5,'Python');
1 row created.
SQL> create table company(
2 product_id number(4) not null,
3 company_id NUMBER(8) not null,
4 company_short_name varchar2(30) not null,
5 company_long_name varchar2(60)
6 );
Table created.
SQL> insert into company values(1,1001,'A Inc.','Long Name A Inc.');
1 row created.
SQL> insert into company values(1,1002,'B Inc.','Long Name B Inc.');
1 row created.
SQL> insert into company values(1,1003,'C Inc.','Long Name C Inc.');
1 row created.
SQL> insert into company values(2,1004,'D Inc.','Long Name D Inc.');
1 row created.
SQL> insert into company values(2,1005,'E Inc.','Long Name E Inc.');
1 row created.
SQL> insert into company values(2,1006,'F Inc.','Long Name F Inc.');
1 row created.
SQL> DECLARE
2 CURSOR cursorValue IS
3 SELECT h.product_description,o.company_short_name FROM company o,product h
4 WHERE o.product_id =h.product_id
5 ORDER by 2;
6 num_total_rows NUMBER;
7 BEGIN
8
9 FOR idx IN cursorValue LOOP
10 dbms_output.put_line(rpad(idx.product_description,20,' ')||' '||
11 rpad(idx.company_short_name,30,' '));
12
13 num_total_rows :=cursorValue%ROWCOUNT;
14 END LOOP;
15 IF num_total_rows >0 THEN
16 dbms_output.new_line;
17 dbms_output.put_line('Total Organizations = '||to_char(num_total_rows));
18 END IF;
19 END;
20 /
Java A Inc.
Java B Inc.
Java C Inc.
Oracle D Inc.
Oracle E Inc.
Oracle F Inc.
Total Organizations = 6
PL/SQL procedure successfully completed.
SQL> drop table product;
Table dropped.
SQL> drop table company;
Table dropped.
enter code here