【问题标题】:Dynamic SQL to loop through same table in multiple schemas动态 SQL 循环遍历多个模式中的同一个表
【发布时间】:2021-11-17 01:35:51
【问题描述】:

我正在尝试创建一个 SQL 语句,其中包含来自 DBA_USERS 的每个模式的基本信息,并且还从每个模式中的特定表中进行选择,作为同一语句的一部分。

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

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

【问题讨论】:

  • 如果您至少使用Oracle 9i,那么建议使用ANSI join syntax
  • 你在 MASTER 模式的每个表上都有一个字段 VERSION,对吧?
  • @RobertoHernandez - 不完全是,在每个模式中,都有一个表“MASTER”(在每个模式中总是命名相同),其中包含一个列“VERSION”,它是一个具有特定应用程序版本号的 varchar2 数据类型.这可能因模式(支持特定应用程序类型)而异,因此查询只需对数据库中所有自定义/非系统模式的“版本号”以及模式(而非表)大小和创建日期等

标签: sql oracle plsql


【解决方案1】:

获得所需结果的一个选项是使用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

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

更新

您的测试用例场景

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

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

在上面的示例中,作为测试,我确实在我的 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 的权限以使流水线功能正常工作。

【讨论】:

  • 你能用一个我可以测试的可重现的例子来更新这个问题吗?
  • @Huskie69,我当前的网络没有访问聊天服务的权限。
  • 我可以使用的
  • @Huskie69,函数有AND o.object_name='ODSPOSTING' 这一行,应该是AND o.object_name='MASTER'
  • @Huskie69,我会用你的测试用例更新我的答案
【解决方案2】:

您没有“看到”任何结果的原因是 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;
/

【讨论】:

  • 我当然希望您访问的表中没有ownerobject_name 的恶意值。 xkcd.com/327
  • @SteveKass - 你的评论让我立刻想到了 Little Bobby Tables,在我注意到你已经包含它之前,我正在查找要发布它的链接。 :-)
【解决方案3】:

问题是

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

【讨论】:

  • “(记住这不是 COBOL)” 作为一个老的、顽固的 COBOL 程序员——他不记得他上一次真正使用它是什么时候了——我类似于那句话! ;-)(也许​​将我的 cmets 全部大写会更合适,但在当前的网络礼仪中,这会像大喊大叫一样):-) 实际上我不认为所有的大写都是 COBOL问题,因为它是早期硬件的限制。我知道我用小写写了很多 COBOL。
  • @EdStevens,我很抱歉对 COBOL 的随意诽谤。我应该说 COBOL-74 或任何需要大写锁定的最后一个版本,或者可能是 Fortran。我几年前读过Queen of COBOL blog post "UPPERCASE, lowercase",所以我应该知道得更多。
  • @Willam Robertson - 无需道歉。我确实添加了几个笑脸,试图表明一种轻松的态度。
猜你喜欢
  • 2016-02-24
  • 1970-01-01
  • 1970-01-01
  • 2014-06-10
  • 2021-06-23
  • 2012-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多