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.
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).
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
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>
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.