Inserting into CLOB very very slow - sql

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

Related

Alter sequence from a trigger

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.

Dynamic SQL to loop through same table in multiple schemas

I'm trying to create a SQL statement which contains basic information from DBA_USERS for each schema and also select from a specific table in each schema as part of the same statement.
I have part of a statement cobbled together from other answers to similar questions on StackExchange:
DECLARE
v_sql varchar2(4000);
cursor c1 is
select o.owner
, o.object_name
, u.created
, TO_CHAR(round(sum(ds.bytes)/1024/1024/1024,'0000'))||' GB'
from dba_users u
, dba_objects o
, dba_segments ds
WHERE u.account_status = 'OPEN'
and u.DEFAULT_TABLESPACE not in ('SYSAUX','SYSTEM')
and u.username=o.owner
and o.object_name='MASTER'
and o.object_type='TABLE'
and ds.owner =o.owner;
BEGIN
for REC in c1 loop
v_sql := 'select VERSION from '||REC.owner||'.'||REC.object_name;
EXECUTE IMMEDIATE v_sql;
end loop;
END;
/
This statement runs but won't show me any results as I believe it should be using a bulk collector and printing the output using DBMS_OUTPUT.PUT_LINE
The output should be something like this:
USERNAME CREATED SIZE VERSION
SchemaA 2021-01-01 20GB 1.1
SchemaB 2021-01-02 22GB 1.2.2
SchemaC 2021-01-03 18GB 1.5.8
Firstly, how should I rewrite the statement above to output to the session, and secondly, is it possible to return the results I'm expecting?
One option to get a result as you want would be to use pipelined functions. They deliver results in the form of a table.
By the way, your query is not completely right, as you need to join more elements. That is why is always best to use ANSI syntax. However, I would keep your syntax to make easier for you the explanation.
Let me show you an example. I don't have this field version, so I am using the counter of rows:
First we need to create the two types, one as an object and the other as table of. The first is the row, the second is the table construction.
SQL> CREATE OR REPLACE TYPE t_tf_row AS OBJECT ( username varchar2(40), created_date date, size_mb varchar2(10), counter number );
/
Type created.
SQL> CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
/
Type created.
Now, we create a pipelined function very similar to yours.
SQL> CREATE OR REPLACE FUNCTION get_schema_details RETURN t_tf_tab PIPELINED
AS
v_sql varchar2(4000);
v_counter pls_integer;
BEGIN
for h in
(
select o.owner
, o.object_name
, u.created
, round(ds.bytes/1024/1024/1024) as table_size
from dba_users u
, dba_objects o
, dba_segments ds
WHERE u.account_status = 'OPEN'
and u.DEFAULT_TABLESPACE not in ('SYSAUX','SYSTEM')
and u.username=o.owner
and u.username=ds.owner
and o.object_name = ds.segment_name
and o.object_type = ds.segment_type
and o.object_name='ODSPOSTING'
and o.object_type='TABLE'
)
loop
v_sql := 'select count(*) from '||h.owner||'.'||h.object_name;
EXECUTE IMMEDIATE v_sql into v_counter;
PIPE ROW(t_tf_row(h.owner,h.created,h.table_size,v_counter));
end loop;
END;
/
Function created.
SQL> select * from table(get_schema_details());
USERNAME CREATED_D SIZE_MB COUNTER
---------------------------------------- --------- ---------- ----------
ODSVIEWS 24-MAR-20 14 71853408
ALFAODS 20-DEC-19 14 71853408
You can make the function as dynamic as you want, for example introducing input parameters instead of hardcoding the values.
UPDATE
Your test case scenario
SQL> CREATE USER SCHEMA1 IDENTIFIED BY Oracle_1234
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP_GROUP; 2 3
User created.
SQL> GRANT CREATE TABLE TO SCHEMA1;
Grant succeeded.
SQL> GRANT UNLIMITED TABLESPACE TO SCHEMA1;
Grant succeeded.
SQL> CREATE USER SCHEMA2 IDENTIFIED BY Oracle_1234
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP_GROUP;
User created.
SQL> GRANT CREATE TABLE TO SCHEMA2;
Grant succeeded.
SQL> GRANT UNLIMITED TABLESPACE TO SCHEMA2;
Grant succeeded.
SQL> CREATE TABLE SCHEMA1.MASTER(VERSION VARCHAR2(6 BYTE));
Table created.
SQL> CREATE TABLE SCHEMA2.MASTER(VERSION VARCHAR2(6 BYTE));
Table created.
SQL> INSERT INTO "SCHEMA1"."MASTER" (VERSION) VALUES ('1.1.0');
1 row created.
SQL> COMMIT;
Commit complete.
SQL> INSERT INTO "SCHEMA2"."MASTER" (VERSION) VALUES ('2.2.0');
1 row created.
SQL> COMMIT;
Commit complete.
Now we create the types and function.
SQL> CREATE OR REPLACE TYPE t_tf_row AS OBJECT ( username varchar2(40), created_date DATE, size_mb varchar2(10), counter NUMBER );
2 /
Type created.
SQL> CREATE OR REPLACE TYPE t_tf_tab IS TABLE OF t_tf_row;
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION get_master_version_details RETURN t_tf_tab PIPELINED
AS
v_sql varchar2(4000);
2 3 4 v_counter pls_integer;
BEGIN
5 6 FOR h IN
(
SELECT o.owner
7 8 9 , o.object_name
, u.created
, round(ds.bytes/1024/1024/1024) AS table_size
10 11 12 FROM dba_users u
, dba_objects o
, dba_segments ds
13 14 15 WHERE u.account_status = 'OPEN'
AND u.DEFAULT_TABLESPACE NOT IN ('SYSAUX','SYSTEM')
AND u.username=o.owner
16 17 18 AND u.username=ds.owner
AND o.object_name = ds.segment_name
AND o.object_type = ds.segment_type
19 20 21 AND o.object_name='MASTER'
AND o.object_type='TABLE'
)
22 23 24 loop
v_sql := 'select count(*) from '||h.owner||'.'||h.object_name;
EXECUTE IMMEDIATE v_sql INTO v_counter;
25 26 27 PIPE ROW(t_tf_row(h.owner,h.created,h.table_size,v_counter));
END loop;
END;
28 29 30 /
Function created.
SQL> SELECT COUNT(*) FROM all_objects WHERE object_name='MASTER' AND object_type='TABLE';
COUNT(*)
----------
2
SQL> SELECT * FROM TABLE(get_master_version_details());
USERNAME CREATED_D SIZE_MB COUNTER
---------------------------------------- --------- ---------- ----------
SCHEMA1 28-SEP-21 0 1
SCHEMA2 28-SEP-21 0 1
Why in your case is not working ? You have to install the function and types within a user/schema with the right privileges to run the operations you are doing.
In my example above, as a test, I did install the function and the type on my sys schema ( something you should not do it ). So, let's drop the function and types, and create an additional user for that, we will call it schema3
SQL> DROP TYPE t_tf_tab;
Type dropped.
SQL> DROP TYPE t_tf_row;
Type dropped.
SQL> DROP FUNCTION get_master_version_details;
Function dropped.
SQL> create user schema3 identified by Oracle_1234 default tablespace users temporary tablespace temp_group ;
User created.
SQL> grant select any table, create procedure, create table, select any dictionary to schema3 ;
Grant succeeded.
SQL> CREATE OR REPLACE TYPE schema3.t_tf_row AS OBJECT ( username varchar2(40), created_date DATE, size_mb varchar2(10), counter NUMBER );
2 /
Type created.
SQL> CREATE OR REPLACE TYPE schema3.t_tf_tab IS TABLE OF t_tf_row;
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION schema3.get_master_version_details RETURN t_tf_tab PIPELINED
AS
v_sql varchar2(4000);
v_counter pls_integer;
BEGIN
2 3 4 5 6 FOR h IN
7 (
SELECT o.owner
, o.object_name
8 9 10 , u.created
, round(ds.bytes/1024/1024/1024) AS table_size
FROM dba_users u
, dba_objects o
, dba_segments ds
WHERE u.account_status = 'OPEN'
11 12 13 14 15 16 AND u.DEFAULT_TABLESPACE NOT IN ('SYSAUX','SYSTEM')
AND u.username=o.owner
AND u.username=ds.owner
17 18 19 AND o.object_name = ds.segment_name
AND o.object_type = ds.segment_type
AND o.object_name='MASTER'
20 21 22 AND o.object_type='TABLE'
23 )
loop
24 25 v_sql := 'select count(*) from '||h.owner||'.'||h.object_name;
EXECUTE IMMEDIATE v_sql INTO v_counter;
PIPE ROW(t_tf_row(h.owner,h.created,h.table_size,v_counter));
26 27 28 END loop;
END;
/ 29 30
Function created.
SQL> SELECT * FROM TABLE(schema3.get_master_version_details());
USERNAME CREATED_D SIZE_MB COUNTER
---------------------------------------- --------- ---------- ----------
SCHEMA1 28-SEP-21 0 1
SCHEMA2 28-SEP-21 0 1
Be aware of the privileges I granted to schema3 in order for the pipelined function to work.
The reason you are not 'seeing' any result is that PL/SQL operates entirely within the server. It has no connection to the client, and no means of accessing the client's output display. You need to use the DBMS_OUTPUT.PUT_LINE procedure (look it up in the docs). That procedure writes to a buffer that is then returned to the client when the procedure completes. It is then up to the client to deal with that buffer. If using sqlplus, you configure it to display that output by 'set serverout on' as a session setting before invoking any procedures.
Also, I'd rewrite your procedure to eliminate the explicit cursor and use a CURSOR FOR loop: (I'd also convert to user ANSI standard JOIN syntax, but I'm not going to spend time here analyzing the query to figure out exactly how to convert that_). Also, I don't see how the procedure runs at all, given that your SELECT inside the loop needs an INTO clause to have a place to put the result.
DECLARE
v_sql varchar2(4000);
v_version varchar2(80);
BEGIN
for REC in (select o.owner
,o.object_name
,u.created
,TO_CHAR(round(sum(ds.bytes)/1024/1024/1024,'0000'))||' GB'
from dba_users u
,dba_objects o
,dba_segments ds
WHERE u.account_status = 'OPEN'
and u.DEFAULT_TABLESPACE not in ('SYSAUX','SYSTEM')
and u.username=o.owner
and o.object_name='MASTER'
and o.object_type='TABLE'
and ds.owner =o.owner;)
loop
v_sql := 'select VERSION from '||REC.owner||'.'||REC.object_name into v_version;
EXECUTE IMMEDIATE v_sql;
dbms_output.put_line('Version is '||v_version);
end loop;
END;
/
The problem is
execute immediate v_sql;
which has no output. It needs an into clause, and something to display it:
declare
demo_text varchar2(50);
begin
execute immediate 'select 2 + 2 as demo from dual'
into demo_text;
dbms_output.put_line(demo_text);
end;
4
By the way, I recommend deciding between end; and END; (bearing in mind this isn't COBOL).

How to update a clob column with data over 2 million characters

I am trying to update a clob value with length > 2 million characters in PL/SQL. I am getting the error
String literal too long
Is there any way I can get around this error?
This is the PL/SQL code snippet I am trying to update the clob value with:
DECLARE
value clob;
clob_field clob;
fromindex integer;
offset integer;
chunks integer;
eclob clob;
sql_stmt clob;
BEGIN
fromindex := 1;
offset := 2;
clob_field := '<clob_value_with_length_2Million>';
chunks := 1+(dbms_lob.Getlength(clob_field) / 2);
value :='';
FOR chunk IN 1 .. chunks LOOP
IF ( chunk != 1) THEN
value := value || ' || ';
END IF;
value := value || 'to_clob('''||dbms_lob.Substr(clob_field, offset, fromindex)||''')';
fromindex := fromindex + 2;
END LOOP;
dbms_output.put_line(value);
sql_stmt := 'update mytable
set sources = ' || value ||' where scenario_id = 1 and entry_index = 1';
EXECUTE IMMEDIATE sql_stmt;
END;
I am getting the error at clob_field initialization and it is obvious as PL/SQl wont allow more than 32k characters. So, I am reaching out here to see if I can have any solution to my problem.
You can reduce the amount of code needed and improve performance of your code if you used bind variables. If you are attempting to build a different update statement each time, the database will need to come up with a execution plan for each different query. Using bind variables also removes the need of having to do any SQL sanitization to protect against SQL injection.
Example
SQL> CREATE TABLE mytable
2 AS
3 SELECT 1 AS scenario_id, 1 AS entry_index, EMPTY_CLOB () || 'clob1' AS sources FROM DUAL
4 UNION ALL
5 SELECT 2 AS scenario_id, 2 AS entry_index, EMPTY_CLOB () || 'clob2' AS sources FROM DUAL;
Table created.
SQL> SELECT * FROM mytable;
SCENARIO_ID ENTRY_INDEX SOURCES
----------- ----------- --------------------------------------------------------------------------------
1 1 clob1
2 2 clob2
SQL> DECLARE
2 clob_field CLOB;
3 l_scenario_id NUMBER;
4 l_entry_index NUMBER;
5 sql_stmt CLOB;
6 BEGIN
7 clob_field := '<clob_value_with_length_2Million>';
8 l_scenario_id := 1;
9 l_entry_index := 1;
10
11 sql_stmt :=
12 'update mytable set sources = :bind_clob where scenario_id = :scenario and entry_index = :entry';
13
14 EXECUTE IMMEDIATE sql_stmt
15 USING clob_field, l_scenario_id, l_entry_index;
16 END;
17 /
PL/SQL procedure successfully completed.
SQL> SELECT * FROM mytable;
SCENARIO_ID ENTRY_INDEX SOURCES
----------- ----------- --------------------------------------------------------------------------------
1 1 <clob_value_with_length_2Million>
2 2 clob2

How to add an anonymous block with this code

A junior SQL developer was trying to write an anonymous block to but is running into issues. The code should count how many items a person can afford based on their budget. The are sure that the SQL works fine, and their logic for counting the number of products is OK, but they don’t remember the right syntax for creating an anonymous block. Help them by finding and fixing the three errors in the following PL/SQL:
BEGIN
DECLARE
firstName VARCHAR(50) := 'Rob';
budget NUMBER = 600;
counter NUMBER;
CURSOR all_products AS
SELECT product_name, list_price FROM oe.PRODUCT_information;
counter := 0;
FOR items IN all_products LOOP
IF (items.LIST_PRICE <= budget) THEN
counter := counter + 1;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(firstName || ', you can afford ' || TO_CHAR(counter) || ' items.');
END;
Two minor mistakes:
DECLARE should go first, BEGIN-END next
you missed colon sign for the BUDGET variable
Test case:
SQL> set serveroutput on
SQL> create table product_information (product_name varchar2(20), list_price number);
Table created.
SQL> insert into product_Information values('Some product', 100);
1 row created.
Your code, fixed:
SQL> DECLARE
2 firstname VARCHAR(50):= 'Rob';
3 budget NUMBER := 600; -- missing colon
4 counter NUMBER;
5 cursor all_products is
6 SELECT product_name,
7 list_price
8 FROM product_information; -- I removed OE. (as I don't have that schema)
9
10 BEGIN
11 counter := 0;
12 FOR items IN all_products LOOP
13 IF(items.list_price <= budget)THEN
14 counter := counter + 1;
15 END IF;
16 END LOOP;
17
18 dbms_output.put_line(firstname
19 || ', you can afford '
20 || TO_CHAR(counter)
21 || ' items.');
22 END;
23 /
Rob, you can afford 1 items.
PL/SQL procedure successfully completed.
SQL>

How do I run a count of rows over a database link?

This code, works. It runs a row count the way you'd expect, I want to tweek it, mostly to do a count over a db_link for tables dictated as I see fit.
declare
n number;
begin
for i in (select table_name from user_tables) loop
execute immediate' select count(*) from '||i.table_name into n;
dbms_output.put_line('Table Name: '||i.table_name||' Count of Row''s: '||n);
end loop;
end;
/
So, this is the adapted code... it includes a variable with the name of the link. (The link works fine) But how to reference it is probably where I'm coming unstuck.
declare
l_dblink varchar2(100) := 'DB1';
n number;
begin
for i in (select table_name from my_tables) loop
execute immediate' select count(*) from '||i.table_name#||l_dblink into n;
dbms_output.put_line('Table Name: '||i.table_name||' Count of Row''s: '||n);
end loop;
end;
/
Can someone please have a look and tell me where I'm going wrong? I just want the SQL to pick up the table names from a local table, and then use the names to count the rows in those tables, which reside in the remote database.
Monkey is on the wrong tree and can't eat a banana.
SQL> create table my_tables (table_name varchar2(20));
Table created.
SQL> insert into my_tables values ('dual');
1 row created.
SQL> set serveroutput on
SQL> declare
2 l_dblink varchar2(100) := 'db1';
3 n number;
4 begin
5 for i in (select table_name from my_tables) -- has to be like this
6 loop -- vvv
7 execute immediate' select count(*) from '||i.table_name || '#' || l_dblink into n;
8 dbms_output.put_line('Table Name: '||i.table_name||' Count of Row''s: '||n);
9 end loop;
10 end;
11 /
Table Name: dual Count of Row's: 1
PL/SQL procedure successfully completed.