Alter sequence from a trigger - sql

I have an auto increment trigger that goes something like as follows
CREATE OR REPLACE TRIGGER test_auto_inc
BEFORE INSERT ON TESTTABLE
FOR EACH ROW
BEGIN
IF :NEW.TestCol IS NULL THEN
SELECT Auto_Increment.nextval INTO :new.TestCol FROM dual;
END IF;
END;
and I want to add in an if statement that will somehow lead to a decrement in the sequence I am using which is as follows:
CREATE SEQUENCE Auto_Increment START WITH 1
INCREMENT BY 1
CACHE 100;
I need to decrement the count somehow like this
ALTER SEQUENCE Auto_Increment INCREMENT BY -1;
SELECT Auto_Increment.NEXTVAL FROM dual;
ALTER SEQUENCE Auto_Increment INCREMENT BY 1;
What is the best way to do this?
Thank you

That's most probably not a good idea. What real problem are you trying to solve?
Anyway, as you asked, here's one option which does that.
This is what you currently have:
SQL> create table testtable (testcol number, datum date);
Table created.
SQL> create sequence auto_increment start with 1 increment by 1 cache 100;
Sequence created.
Trigger:
SQL> create or replace trigger test_auto_inc
2 before insert on testtable
3 for each row
4 declare
5 l_seq number;
6 begin
7 l_seq := auto_increment.nextval;
8 if l_seq >= 3 then
9 execute immediate 'alter sequence auto_increment increment by -1';
10 l_seq := auto_increment.nextval;
11 execute immediate 'alter sequence auto_increment increment by 1';
12 end if;
13
14 :new.testcol := nvl(:new.testcol, l_seq);
15 end;
16 /
Trigger created.
Testing:
SQL> insert into testtable (datum) values (sysdate);
1 row created.
SQL> insert into testtable (datum) values (sysdate);
1 row created.
SQL> select * from testtable order by datum;
TESTCOL DATUM
---------- -------------------
1 20.03.2022 08:22:17
2 20.03.2022 08:22:26
SQL>
If you want to modify (alter, eh?) the sequence within a PL/SQL procedure (that's your trigger), you'll have to use dynamic SQL as this is the only way to run a DDL from PL/SQL. OK, let's add it (I also slightly modified your code; no need for select while fetching sequence value):
SQL> create or replace trigger test_auto_inc
2 before insert on testtable
3 for each row
4 declare
5 l_seq number;
6 begin
7 l_seq := auto_increment.nextval;
8 if l_seq >= 3 then
9 execute immediate 'alter sequence auto_increment increment by -1';
10 l_seq := auto_increment.nextval;
11 execute immediate 'alter sequence auto_increment increment by 1';
12 end if;
13
14 :new.testcol := nvl(:new.testcol, l_seq);
15 end;
16 /
Trigger created.
Nice, it compiled! Why wouldn't it, Oracle has no idea what's written between single quotes. Could've been the 1st statement from Moby Dick. Let's try it:
SQL> insert into testtable (datum) values (sysdate);
insert into testtable (datum) values (sysdate)
*
ERROR at line 1:
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at "SCOTT.TEST_AUTO_INC", line 6
ORA-04088: error during execution of trigger 'SCOTT.TEST_AUTO_INC'
SQL>
Whooops! Can't commit in a trigger. Bummer! Is there a way out of it? Certainly - declare the trigger to be an autonomous transaction:
SQL> create or replace trigger test_auto_inc
2 before insert on testtable
3 for each row
4 declare
5 pragma autonomous_transaction;
6 l_seq number;
7 begin
8 l_seq := auto_increment.nextval;
9 if l_seq >= 3 then
10 execute immediate 'alter sequence auto_increment increment by -1';
11 l_seq := auto_increment.nextval;
12 execute immediate 'alter sequence auto_increment increment by 1';
13 end if;
14
15 :new.testcol := nvl(:new.testcol, l_seq);
16 end;
17 /
Trigger created.
Does it work? Yes!
SQL> insert into testtable (datum) values (sysdate);
1 row created.
SQL> select * from testtable order by datum;
TESTCOL DATUM
---------- -------------------
1 20.03.2022 08:22:17
2 20.03.2022 08:22:26
3 20.03.2022 08:26:55
SQL>
Yet another insert:
SQL> insert into testtable (datum) values (sysdate);
1 row created.
SQL> select * from testtable order by datum;
TESTCOL DATUM
---------- -------------------
1 20.03.2022 08:22:17
2 20.03.2022 08:22:26
3 20.03.2022 08:26:55
3 20.03.2022 08:27:31 --> see? Yet another testcol = 3!
SQL>
So, yes ... it can be done. Note, though, that if you used autonomous transaction within a trigger for purposes different from logging, you're most probably doing it wrong.

Related

How to catch the null constraint exception in plsql when inserting a record?

I want to check the null constraint of a column when inserting a record into a table. There, I am trying to insert a null value through a procedure to a column which cannot be null. Here is my procedure.
CREATE OR REPLACE PROCEDURE RecordInsert(s_id IN NUMBER, pid IN NUMBER, pr_id IN NUMBER, quantity NUMBER, rv DATE, exercise_num NUMBER)
IS
null_constraint EXCEPTION;
PRAGMA EXCEPTION_INIT(null_constraint,-1451);
BEGIN
INSERT INTO Supplier_Part_Project_Tab
VALUES(s_id, pid, pr_id_, quantity, rv);
dbms_output.put_line('The row successfully inserted');
COMMIT;
EXCEPTION
WHEN null_constraint THEN
dbms_output.put_line('The column cannot be NULL');
WHEN OTHERS THEN
pkg_Error.prc_Exeception(exercise_num);
END;
This is how I executed the procedure.
BEGIN
RecordInsert( 1002, '', 2001, 80, (TO_DATE('2003/05/03 09:02:44', 'yyyy/mm/dd hh12:mi:ss AM')),8);
COMMIT;
END;
In the above procedure, I am trying to insert pid as null. There, I want to fire the 'null_constraint' exception. But it fires the 'OTHERS' exception. Hope the oracle exception -1451 is correct for my requirement. But why does it not fire?
Sample table:
SQL> CREATE TABLE test
2 (
3 id NUMBER NOT NULL,
4 name VARCHAR2 (20) NOT NULL
5 );
Table created.
Procedure without exception handling section (to see what will happen):
SQL> CREATE OR REPLACE PROCEDURE RecordInsert (par_id IN NUMBER,
2 par_name IN VARCHAR2)
3 IS
4 null_constraint EXCEPTION;
5 PRAGMA EXCEPTION_INIT (null_constraint, -1451);
6 BEGIN
7 INSERT INTO test (id, name)
8 VALUES (par_id, par_name);
9
10 DBMS_OUTPUT.put_line ('The row successfully inserted');
11 /*
12 EXCEPTION
13 WHEN null_constraint
14 THEN
15 DBMS_OUTPUT.put_line ('The column cannot be NULL');
16 WHEN OTHERS
17 THEN
18 DBMS_OUTPUT.put_line ('Others');
19 */
20 END;
21 /
Procedure created.
Testing:
SQL> exec recordinsert(1, 'Littlefoot');
The row successfully inserted
PL/SQL procedure successfully completed.
SQL> exec recordinsert(2, null);
BEGIN recordinsert(2, null); END;
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("SCOTT"."TEST"."NAME")
ORA-06512: at "SCOTT.RECORDINSERT", line 7
ORA-06512: at line 1
SQL>
Aha. ORA-01400 (not 1451). Let's modify the procedure, then:
SQL> CREATE OR REPLACE PROCEDURE RecordInsert (par_id IN NUMBER,
2 par_name IN VARCHAR2)
3 IS
4 null_constraint EXCEPTION;
5 PRAGMA EXCEPTION_INIT (null_constraint, -1400);
6 BEGIN
7 INSERT INTO test (id, name)
8 VALUES (par_id, par_name);
9
10 DBMS_OUTPUT.put_line ('The row successfully inserted');
11 EXCEPTION
12 WHEN null_constraint
13 THEN
14 DBMS_OUTPUT.put_line ('The column cannot be NULL');
15 WHEN OTHERS
16 THEN
17 DBMS_OUTPUT.put_line ('Others');
18 END;
19 /
Procedure created.
SQL> set serveroutput on
SQL>
SQL> exec recordinsert(2, null);
The column cannot be NULL --> here's your exception
PL/SQL procedure successfully completed.
SQL>
Error code you used was
ORA-01451: column to be modified to NULL cannot be modified to NULL
Cause: The column may already allow NULL values, the NOT NULL constraint is part of a primary key or check constraint, or an ALTER TABLE MODIFY statement attempted to change a column specification unnecessarily, from NULL to NULL.
Action: If a primary key or check constraint is enforcing the NOT NULL constraint, then drop that constraint.
It is related to ALTER TABLE statement, not to inserts which try to insert NULL values into NOT NULL columns, e.g.
SQL> create table test (id number not null, name varchar2(20));
Table created.
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NOT NULL NUMBER
NAME VARCHAR2(20)
SQL> alter table test modify name null;
alter table test modify name null
*
ERROR at line 1:
ORA-01451: column to be modified to NULL cannot be modified to NULL
SQL>

How to change the sequence order without dropping the sequence in oracle

my current sequence no is 203 i need to jump to 1203 without dropping the sequence.
First alter the sequence increment, given you stated 203 to 1203, I'm adding 1000, adjust as appropriate.
ALTER SEQUENCE yourSequence INCREMENT BY 1000;
Then request a value
SELECT yourSequence.NextVal FROM dual;
Then alter it back to incrementing by 1 (assuming it was 1 in the first place)
ALTER SEQUENCE yourSequence INCREMENT BY 1;
You really don't want to do this if the sequence is in active use - since it could jump up multiple thousands.
You may create such a procedure on your desired schema of DB as follows :
SQL>Create or Replace Procedure Pr_Set_Sequence( i_seq_name varchar2, i_val pls_integer ) is
v_val pls_integer;
begin
for c in (
Select u.sequence_name seq
From User_Sequences u
Where u.sequence_name = upper(i_seq_name)
)
loop
execute immediate 'select '||i_seq_name||'.nextval from dual' INTO v_val;
execute immediate 'alter sequence '||i_seq_name||' increment by ' ||
to_char(-v_val+i_val) || ' minvalue 0';
execute immediate 'select '||i_seq_name||'.nextval from dual' INTO v_val;
execute immediate 'alter sequence '||i_seq_name||' increment by 1 minvalue 0';
end loop;
end;
and call with the desired value ( in my case a create a new one called my_seq ) :
SQL> create sequence my_seq;
Sequence created
SQL> select my_seq.nextval from dual;
NEXTVAL
----------
1
SQL> begin
2 pr_set_sequence('my_seq',1203);
3 end;
4 /
PL/SQL procedure successfully completed
SQL> select my_seq.currval from dual;
NEXTVAL
----------
1203
Just do a WHILE loop, selecting from the sequence until it's up to the value you need. It's quite fast.
declare
v_sequence_value number;
begin
while v_sequence_value <= 1203 loop
select my_sequence.nextval into v_sequence_value from dual;
end loop;
end;
/

SP2-0552: Bind Variable "NEW" is not declared

I am trying to learn pl/sql triggers. I am trying to create a simple trigger by tracking tutorial http://www.tutorialspoint.com/plsql/plsql_triggers.htm but I got below error. I searched on the internet but could not find the solution. Could you help me on this issue?
CREATE OR replace TRIGGER display_salary_changes
BEFORE DELETE OR INSERT OR UPDATE ON ok.customers
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.salary - :OLD.salary;
END;
/
Trıgger DISPLAY_SALARY_CHANGES created.
SP2-0552: Bind Variable "NEW" is not declared.
PL/SQL procedure successfully completed.
Edit: I am using Sql Developer Version 4.1.1
Works for me (example from your link, but it's basically the same as your post):
SQL> create table demo (id integer, salary number);
Table created.
SQL> create or replace trigger display_salary_changes
2 before delete or insert or update on demo
3 for each row
4 when (new.id > 0)
5 declare
6 sal_diff number;
7 begin
8 sal_diff := :new.salary - :old.salary;
9 dbms_output.put_line('Old salary: ' || :old.salary);
10 dbms_output.put_line('New salary: ' || :new.salary);
11 dbms_output.put_line('Salary difference: ' || sal_diff);
12 end;
13 /
Trigger created.
SQL> show errors
No errors.
SQL> insert into demo (id, salary) values (1, 100);
Old salary:
New salary: 100
Salary difference:
1 row created.
SQL> update demo set salary = salary * 1.1 where id = 1;
Old salary: 100
New salary: 110
Salary difference: 10
1 row updated.
In your example it shows
Trıgger DISPLAY_SALARY_CHANGES created.
which doesn't look like SQL*Plus output. What tool did you use?
After that it gives a SQL*Plus SP2-0552 error about an undefined bind variable, followed by
PL/SQL procedure successfully completed.
What procedure was that? I suspect this is the output from a script with some other step that is failing after the trigger is created.
Is the trigger valid? You can normally right-click and check properties in desktop tools, or at the SQL*Plus prompt enter
show errors trigger display_salary_changes
Try this:
CREATE OR replace TRIGGER test_trg
BEFORE DELETE OR INSERT OR UPDATE ON test
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :new.d - :old.d;
END;
/
Can you please check you column name. I have tried your code below and I got output.
create table test
(
no number(10),
sal number(10)
);
CREATE OR replace TRIGGER test_tr
BEFORE DELETE OR INSERT OR UPDATE ON test
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.sal - :OLD.sal;
dbms_output.put_line(sal_diff);
END;
/
insert into test values(1,100);
update test set sal=200 where no=1;
Output :
1 rows inserted.
4 rows updated.
100
100
100
100
1 rows inserted.
I think it is missing the "REFERENCING NEW AS NEW OLD AS OLD" sentence:
CREATE TRIGGER [trigger_name]
BEFORE INSERT OR UPDATE ON [table_name]
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW

Error while updating a table with CLOB column:ORA-01461

I have a table in Oracle Database as follows,
create table test_clob(
id1 number,
clob_col clob);
If i try to insert a varchar2 variable with size more than 4000 into the CLOB column, it inserts without any problem.
insert into test_clob values (1,rpad('a',32760,'a'));
commit;
If i try to update the CLOB column as follows, it works perfectly fine.
update test_clob set clob_col = rpad('b',32760,'b') where id1 = 1;
commit;
However, if i try to run the update statement as follows, it is failing due to "ORA-01461: can bind a LONG value only for insert into a LONG column" error.
declare
large_string varchar2(32767) := rpad('c',32760,'c');
begin
update test_clob set clob_col = nvl(large_string,clob_col) where id1 = 1;
commit;
end;
I suspect it is the NVL function that is causing the problem. Any help on this is highly appreciated.
Note: I have used a simple table in the example, but actually the table has several columns and update statement has to update many colums at a time.
actually, rpad('a',32760,'a') when called from SQL would only return a 4k string which is why it works.
A Varchar type in SQL is limited to 4k, so when you try to bind a 32k varchar2 variable from pl/sql it will fail (as rpad when called from pl/sql will return the 32k).
eg:
SQL> select length(rpad('a',32760,'a')) from dual;
LENGTH(RPAD('A',32760,'A'))
---------------------------
4000
it silently limits the return to 4k for you. but pl/sql will not limit to 4k:
SQL> declare
2 large_string varchar2(32767) := rpad('c',32760,'c');
3 begin
4 dbms_output.put_line(length(large_string));
5 end;
6 /
32760
You should define your pl/sql variable as clob and NOT varchar2(32760):
SQL> create table test_clob(
2 id1 number,
3 clob_col clob);
Table created.
SQL> insert into test_clob values (1,rpad('a',32760,'a'));
1 row created.
SQL> select length(clob_col) from test_clob;
LENGTH(CLOB_COL)
----------------
4000
SQL> commit;
Commit complete.
SQL> declare
2 large_string clob := rpad('c',32760,'c');
3 begin
4 update test_clob set clob_col = nvl(large_string,clob_col) where id1 = 1;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL> select length(clob_col) from test_clob;
LENGTH(CLOB_COL)
----------------
32760
SQL>

Inserting into CLOB very very slow

I am experiencing a significant performance drop when inserting string of 'TinyString' (just an example string) into stored-in-row CLOB, as compared to VARCHAR2. My understanding is that when storing data of < 4000 bytes into a CLOB with STORAGE IN ROW enabled, the data is effectively stored in the same manner as VARCHAR2 (unless it 'overflows' 4000bytes) and there should be no significant performance drop. However, my benchmarking procedure* shows that inserting same data into CLOB is 15 times slower than inserting into VARCHAR2.
Have a look at the code below:
I have got a number of tables, each of which has a COMPOUND TRIGGER attached similar to the one below:
CREATE OR REPLACE TRIGGER mdhl_basic_trigger_compound
FOR INSERT OR UPDATE OR DELETE ON target_table
COMPOUND TRIGGER TYPE EVENTS_HIST IS TABLE OF log_table%ROWTYPE INDEX BY PLS_INTEGER;
coll_events_hist EVENTS_HIST;
ctr PLS_INTEGER := 0;
my_bgroup VARCHAR2(3);
BEFORE EACH ROW IS
BEGIN
IF INSERTING OR UPDATING THEN
my_bgroup := :NEW.BGROUP;
ELSE
my_bgroup := :OLD.BGROUP;
END IF;
ctr := ctr + 1;
coll_events_hist(ctr).BGROUP := my_bgroup;
coll_events_hist(ctr).TABLE_NAME := 'BASIC_MDHL';
coll_events_hist(ctr).EVENT_TS := current_timestamp;
coll_events_hist(ctr).EVENT_RAW := 'TinyString';
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FORALL counter IN 1 .. coll_events_hist.count()
INSERT INTO log_table VALUES coll_events_hist(counter);
END AFTER STATEMENT;
END mdhl_basic_trigger_compound;
Upon any operation on target_table, the above trigger stores data populated in coll_events_hist type into log_table, which is defined in a following way:
CREATE TABLE "USERNAME"."LOG_TABLE"
( "BGROUP" VARCHAR2(3) NOT NULL ENABLE,
"TABLE_NAME" VARCHAR2(255) NOT NULL ENABLE,
"EVENT_TS" TIMESTAMP (7) DEFAULT current_timestamp,
"EVENT_RAW" CLOB
)
SEGMENT CREATION IMMEDIATE
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)
TABLESPACE "USERS"
LOB ("EVENT_RAW") STORE AS BASICFILE "EV_RAW_SEG"(
TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 16384 PCTVERSION 5
CACHE
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT))
My setup is:
Windows 7 SP1,
Oracle 11g
*My benchamrking procedure iterates 10 times updating 21k rows on target_table in each iteration.
in your case is "tinystring" always <32767?
Your time is going to be wasted in the FORALL part looking up all the temporary lobs you've made.
you'd find better performance with inserts in the for each row part :
eg on my test system with your lob trigger:
SQL> insert into target_Table select 'ABC' from dual connect by level <= 10000;
10000 rows created.
Elapsed: 00:00:10.49
vs having the trigger as:
SQL> CREATE OR REPLACE TRIGGER mdhl_basic_trigger
2 before INSERT OR UPDATE OR DELETE ON target_table for each row
3 declare
4
5 my_bgroup VARCHAR2(3);
6
7 v_timer2 number := 0;
8 v_timer number;
9 BEGIN
10
11 IF INSERTING OR UPDATING THEN
12 my_bgroup := :NEW.BGROUP;
13 ELSE
14 my_bgroup := :OLD.BGROUP;
15 END IF;
16
17 INSERT INTO log_table VALUES(my_bgroup, 'BASIC_MDHL', current_timestamp, 'TinyString');
18
19 END mdhl_basic_trigger;
20 /
SQL> insert into target_Table select 'ABC' from dual connect by level <= 10000;
10000 rows created.
Elapsed: 00:00:01.18
if you KNOW your strings are always <32k you can keep the forall to get that speed boost if you create your trigger as:
SQL> CREATE OR REPLACE TRIGGER mdhl_basic_trigger_compound
2 FOR INSERT OR UPDATE OR DELETE ON target_table
3
4 COMPOUND TRIGGER
5
6 type events_rec is record (BGROUP VARCHAR2(3),
7 TABLE_NAME VARCHAR2(255) ,
8 EVENT_TS TIMESTAMP (7),
9 EVENT_RAW varchar2(32767));
10 TYPE EVENTS_HIST IS TABLE OF events_rec INDEX BY PLS_INTEGER;
11 coll_events_hist EVENTS_HIST;
12 ctr PLS_INTEGER := 0;
13 my_bgroup VARCHAR2(3);
14
15 v_timer2 number := 0;
16 v_timer number;
17 BEFORE EACH ROW IS
18 BEGIN
19
20 IF INSERTING OR UPDATING THEN
21 my_bgroup := :NEW.BGROUP;
22 ELSE
23 my_bgroup := :OLD.BGROUP;
24 END IF;
25
26 ctr := ctr + 1;
27 coll_events_hist(ctr).BGROUP := my_bgroup;
28 coll_events_hist(ctr).TABLE_NAME := 'BASIC_MDHL';
29 coll_events_hist(ctr).EVENT_TS := current_timestamp;
30 coll_events_hist(ctr).EVENT_RAW := 'TinyString';
31
32 END BEFORE EACH ROW;
33
34 AFTER STATEMENT IS
35 BEGIN
36 v_timer := dbms_utility.get_time;
37 FORALL counter IN 1 .. coll_events_hist.count()
38 INSERT INTO log_table VALUES coll_events_hist(counter);
39 v_timer2 := v_timer2 + (dbms_utility.get_time - v_timer);
40 dbms_output.put_line(v_timer2/100);
41 END AFTER STATEMENT;
42 END mdhl_basic_trigger_compound;
43 /
SQL> insert into target_Table select 'ABC' from dual connect by level <= 10000;
10000 rows created.
Elapsed: 00:00:00.39
i.e. defer the lob operation until the insert.
Even when a CLOB is stored in-line, there is some overhead compared to a standard VARCHAR2, as described in appendix C of the LOB performance guideline.
When the length of a LOB is less than 3964 bytes then it is stored inline with a 36 bytes header. A VARCHAR2 of length X will be stored as X bytes of data with an extra one or two bytes of overhead.
I think this overhead will carry into memory, which means PLSQL CLOB objects will be less efficient than VARCHAR2 of comparable size.
The 34-35 extra bytes will add up as demonstrated with the following script:
SQL> create table test_var(a varchar2(4000));
Table created
SQL> create table test_clob(a clob);
Table created
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_time TIMESTAMP := systimestamp;
3 BEGIN
4 FOR i IN 1..100000 LOOP
5 INSERT INTO test_var VALUES (rpad('x', 1000, 'x'));
6 END LOOP;
7 dbms_output.put_line(systimestamp - l_time);
8 END;
9 /
+000000000 00:00:16.180299000
SQL> DECLARE
2 l_time TIMESTAMP := systimestamp;
3 BEGIN
4 FOR i IN 1..100000 LOOP
5 INSERT INTO test_clob VALUES (rpad('x', 1000, 'x'));
6 END LOOP;
7 dbms_output.put_line(systimestamp - l_time);
8 END;
9 /
+000000000 00:00:27.180716000
It takes more time to insert CLOBs, which can be explained by the extra space consumed:
SQL> EXEC dbms_stats.gather_table_stats(USER, 'TEST_VAR');
PL/SQL procedure successfully completed.
SQL> EXEC dbms_stats.gather_table_stats(USER, 'TEST_CLOB');
PL/SQL procedure successfully completed.
SQL> select blocks, table_name from user_tables where table_name like 'TEST_%';
BLOCKS TABLE_NAME
---------- ------------------------------
33335 TEST_CLOB
28572 TEST_VAR
The problem is aggravated when we insert smaller strings:
-- after TRUNCATE tables
SQL> DECLARE
2 l_time TIMESTAMP := systimestamp;
3 BEGIN
4 FOR i IN 1..1000000 LOOP
5 INSERT INTO test_var VALUES (rpad('x', 10, 'x'));
6 END LOOP;
7 dbms_output.put_line(systimestamp - l_time);
8 END;
9 /
+000000000 00:00:51.916675000
SQL> DECLARE
2 l_time TIMESTAMP := systimestamp;
3 BEGIN
4 FOR i IN 1..1000000 LOOP
5 INSERT INTO test_clob VALUES (rpad('x', 10, 'x'));
6 END LOOP;
7 dbms_output.put_line(systimestamp - l_time);
8 END;
9 /
+000000000 00:01:57.377676000
-- Gather statistics
SQL> select blocks, table_name from user_tables where table_name like 'TEST_%';
BLOCKS TABLE_NAME
---------- ------------------------------
7198 TEST_CLOB
2206 TEST_VAR