动态 SQL 循环遍历多个模式中的相同 table

Dynamic SQL to loop through same table in multiple schemas

我正在尝试创建一个 SQL 语句,其中包含每个模式的 DBA_USERS 的基本信息以及每个模式中特定 table 的 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

打印输出

输出应该是这样的:

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

首先,我应该如何重写上面的语句以输出到会话,其次,是否可以return我期望的结果?

获得所需结果的一个选项是使用 pipelined functions。他们以 table.

的形式提供结果

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

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

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

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

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

更新

您的测试用例场景

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.

现在我们创建类型和函数。

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

为什么你的情况不起作用?您必须在 user/schema 中安装函数和类型,并具有 运行 您正在执行的操作的正确权限。

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

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

此外,我将重写您的过程以消除显式游标并使用 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;

没有输出。它需要一个 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)。