繁体   English   中英

动态 SQL 循环遍历多个模式中的同一个表

[英]Dynamic SQL to loop through same table in multiple schemas

我正在尝试创建一个 SQL 语句,其中包含来自 DBA_USERS 的每个模式的基本信息,以及来自每个模式中特定表的 select 作为同一语句的一部分。

我从 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;
    /

该语句运行但不会显示任何结果,因为我认为它应该使用批量收集器并使用 DBMS_OUTPUT.PUT_LINE 打印 output

output 应该是这样的:

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

首先,我应该如何将上面的语句重写为 output 到 session,其次,是否可以返回我期望的结果?

获得所需结果的一种选择是使用pipelined functions 他们以表格的形式提供结果。

顺便说一句,您的查询并不完全正确,因为您需要加入更多元素。 这就是为什么总是最好使用ANSI语法。 但是,我会保留您的语法以使您更容易解释。

让我给你举个例子。 我没有这个字段version ,所以我使用行计数器:

首先我们需要创建两种类型,一种是object ,另一种是table of 第一是行,第二是建表。

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.
 

现在,我们创建了一个与您的非常相似的pipelined function

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

您可以根据需要使 function 动态化,例如引入输入参数而不是对值进行硬编码。

更新

您的测试用例场景

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.

现在我们创建类型和 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

为什么在您的情况下不起作用? 您必须在具有正确权限的用户/模式中安装 function 和类型才能运行您正在执行的操作。

在我上面的示例中,作为测试,我确实在我的 sys 模式上安装了 function 和类型(你不应该这样做)。 所以,让我们删除 function 和类型,并为此创建一个额外的用户,我们将其称为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

请注意我授予schema3的权限,以便管道 function 工作。

您没有“看到”任何结果的原因是 PL/SQL完全在服务器内运行。 它与客户端没有连接,也无法访问客户端的 output 显示。 您需要使用 DBMS_OUTPUT.PUT_LINE 过程(在文档中查找)。 该过程写入一个缓冲区,然后在该过程完成时将其返回给客户端。 然后由客户端处理该缓冲区。 如果使用 sqlplus,您可以在调用任何过程之前通过“set serverout on”将其配置为显示 output 作为 session 设置。

此外,我将重写您的过程以消除显式 cursor 并使用 CURSOR FOR 循环:(我还将转换为用户 ANSI 标准 JOIN 语法,但我不打算在这里花时间分析查询以准确找出如何转换_)。 另外,我根本看不到程序是如何运行的,因为循环内的 SELECT 需要一个 INTO 子句来放置结果。

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;
/

问题是

execute immediate v_sql;

它没有 output。它需要一个into子句,以及一些东西来显示它:

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

顺便说一下,我建议在end; END; (请记住,这不是 COBOL)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM