Dynamic SQL to loop through same table in multiple schemas - sql

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

Related

Code to execute select on list of multiple Oracle databases

Need your or guidance on how I can execute a select on multiple databases provided in the list. the goal behind this code is to query multiple remote databases and insert the output in current database.
Need to db_link to be fetched from a list or table
insert into xxxx.DB_tracker value(SELECT d.name FROM v$database#**opXXX_du**);
Dynamic SQL.
Suppose that database links are stored in the link table:
SQL> select * From links;
LINK
---------
dbl_ora10
dbl_ora11
dbl_orcl
You'd then use a loop, create an insert statement and execute it. As I don't have those database links, I'm just displaying statements to the screen. You'd uncomment the execute immediate line.
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200);
3 begin
4 for cur_r in (select link from links) loop
5 l_str := 'insert into db_tracker ' ||
6 'select name from v$database#' || cur_r.link;
7 dbms_output.put_line(l_str);
8
9 -- execute immediate l_str;
10 end loop;
11 end;
12 /
insert into db_tracker select name from v$database#dbl_ora10
insert into db_tracker select name from v$database#dbl_ora11
insert into db_tracker select name from v$database#dbl_orcl
PL/SQL procedure successfully completed.
SQL>
If you want to actually select name and display it on the screen, then you need the into clause. Something like this:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_name varchar2(30);
3 begin
4 for cur_r in (select link from links) loop
5 execute immediate 'select name from v$database#' || cur_r.link
6 into l_name;
7 dbms_output.put_line(l_name);
8 end loop;
9 end;
10 /
XE
PL/SQL procedure successfully completed.
SQL>

How to run repeated statement with different parameter in SQL

Using SQL (or PL/SQL) I'd like to do something like:
GRANT SELECT, INSERT, TRIGGER, UPDATE, DELETE, REFERENCES, RULE ON {mytable} to {userid}
but do this for n number of tables. In SAS I could create a macro and pass in the table name (and/or the userid) as a parameter. Can the same be done in SQL using a procedure?
If you have list of tables stored in some other table (or, if it is about all tables in a schema), then you could create a procedure which would accept username as a parameter and grant those privileges on all those tables to that user.
For example (Oracle, which uses PL/SQL; as you didn't mention database you really use):
SQL> create or replace procedure p_grant (par_username in varchar2) is
2 begin
3 for cur_r in (select table_name
4 from user_tables
5 where table_name in ('EMP', 'DEPT', 'BONUS')
6 )
7 loop
8 dbms_output.put_Line('Grant on table: ' || cur_r.table_name);
9 execute immediate 'grant select, insert, update, delete on ' || cur_r.table_name || ' to ' || par_username;
10 end loop;
11 end;
12 /
Procedure created.
SQL> set serveroutput on
SQL> begin
2 p_grant('mike');
3 end;
4 /
Grant on table: BONUS
Grant on table: DEPT
Grant on table: EMP
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.

PL/SQL procedure to accept an item_num and report its description,price,and on_hand quantity is not working

This is a homework assignment in my database management class. We were given the option of using Oracle SQL Developer on campus computers, or using SSH Secure Shell (I am using the later). I am to write a procedure which accepts an item_num and reports its description, price, and on_hand quantity. (Btw: all of these come from a table named "item".) My professor has said that a few commands (listed above the procedure) must proceed the procedure. My code:
serveroutput on;
accept item_num prompt 'Enter a item_num: ';
exec reports_item_info();
create or replace procedure reports_item_info (i_item_num in
item.item_num%type) as
i_description item.descripton%type;
i_std_price item.std_price%type;
i_on_hand item.on_hand%type;
begin
select description,std_price,on_hand
into i_description,i_std_price,i_on_hand
from item
where item_num = i_item_num;
dbms_output.put_line(rtrim(i_description))||' '||
(rtrim(i_std_price))||' '||(rtrim(i_on_hand))
end;
/
I know that something isn't right, because it doesn't work. How could I alter it, so that it will work?
If you pay attention to what you type, it works; you didn't provide CREATE TABLE so I created it myself.
SQL> create table item (item_num number,
2 description varchar2(20),
3 std_price number,
4 on_hand number);
Table created.
SQL>
SQL> create or replace procedure reports_item_info
2 (i_item_num in item.item_num%type)
3 as
4 i_description item.description%type;
5 i_std_price item.std_price%type;
6 i_on_hand item.on_hand%type;
7 begin
8 select description, std_price, on_hand
9 into i_description, i_std_price, i_on_hand
10 from item
11 where item_num = i_item_num;
12
13 dbms_output.put_line(rtrim(i_description) ||' '||
14 rtrim(i_std_price) ||' '||
15 rtrim(i_on_hand)
16 );
17 end;
18 /
Procedure created.
SQL>
Testing:
SQL> insert into item values (1, 'test', 100, 10);
1 row created.
SQL> exec reports_item_info(1);
test 100 10
PL/SQL procedure successfully completed.
SQL>
Your mistakes?
it is not a DESCRIPTON but DESCRIPTION
missing semi-colon at the end of DBMS_OUTPUT.PUT_LINE call
superfluous parenthesis
[EDIT]
Try with this:
SQL> declare
2 item_num number := &enter_item_num_value;
3 begin
4 reports_item_info(item_num);
5 end;
6 /
Enter value for enter_item_num_value: 1
old 2: item_num number := &enter_item_num_value;
new 2: item_num number := 1;
test 100 10
PL/SQL procedure successfully completed.
SQL>
or
SQL> accept itnum prompt 'Enter value : ';
Enter value : 1
SQL> exec reports_item_info(&itnum);
test 100 10
PL/SQL procedure successfully completed.
SQL>
The problem is due to the usage of dbms_output.put_line make it as
dbms_output.put_line(rtrim(i_description)||' '||rtrim(i_std_price)||' '||rtrim(i_on_hand));
by removing redundant parentheses.
Put a semi-colon at the end of this statement of course.
And there's a problem in spelling of the column description.
Then, use the following :
SQL> set serveroutput on;
SQL> CREATE TABLE item(
item_num varchar2(50),
description varchar2(100),
std_price number(10,2),
on_hand varchar2(100)
);
SQL> INSERT INTO item VALUES ('AH74, BR23, CD33','abc',15.75,'defgh');
SQL> COMMIT;
SQL> create or replace procedure reports_item_info(i_item_num in item.item_num%type) as
i_description item.description%type;
i_std_price item.std_price%type;
i_on_hand item.on_hand%type;
begin
select description, std_price, on_hand
into i_description, i_std_price, i_on_hand
from item
where item_num = i_item_num;
dbms_output.put_line(rtrim(i_description) || ' '|| rtrim(i_std_price)||' '||rtrim(i_on_hand));
end;
/
SQL> accept item_num prompt 'Enter a item_num: '; -- enter AH74, BR23, CD33 when prompts
SQL> exec reports_item_info('&item_num'); -- use parameter with quotes
abc 15.75 defgh
PL/SQL procedure successfully completed

Array in IN() clause oracle PLSQL

I am passing String array(plcListchar) to Stored Procedure, i would like to use this String array in IN() clause.
i can not use plcListchar directly in IN() clause.
Let me show how i am crating plcListchar string array in JAVA.
String array[] = {"o", "l"};
ArrayDescriptor des = ArrayDescriptor.createDescriptor("CHAR_ARRAY", con);
ARRAY array_to_pass = new ARRAY(des,con,array);
callStmtProductSearch.setArray(4, array_to_pass);
for crating CHAR_ARRAY,
create or replace type CHAR_ARRAY as table of varchar2;
i want use plcListchar in IN clause. the following is my Stored Procedure.
CREATE OR REPLACE PROCEDURE product_search(
status IN varchar2,
plcList IN varchar2,
i_culture_id IN number,
plcListchar IN CHAR_ARRAY,
status_name OUT varchar2,
culture_code OUT varchar2)
AS
CURSOR search_cursor IS
SELECT p.status_name, p.culture_code
FROM PRISM_ITEM_cultures#prism p
WHERE p.publishable_flag=1
AND p.isroll =0
AND status = '-1'
AND p.plc_status IN (   );
BEGIN
OPEN search_cursor;
FETCH search_cursor INTO status_name, culture_code ;
CLOSE search_cursor;
END;
Could you please suggest me how to use, if you like to suggest any other logic, it is great.
Assuming that your collection is defined in SQL, not just in PL/SQL, you can use the TABLE operator (the definition you posted isn't syntactically valid-- you'd need to specify a length for the VARCHAR2)
AND p.plc_status IN (SELECT column_value
FROM TABLE( plcListchar ))
Since I don't have your tables, an example using the SCOTT schema
SQL> create type ename_tbl is table of varchar2(30);
2 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_enames ename_tbl := ename_tbl( 'KING', 'SMITH' );
3 begin
4 for i in (select *
5 from emp
6 where ename in (select column_value
7 from table( l_enames )))
8 loop
9 dbms_output.put_line( 'ENAME = ' || i.ename );
10 end loop;
11* end;
SQL> /
ENAME = KING
ENAME = SMITH
PL/SQL procedure successfully completed.