如何在 Oracle 中动态分析给定模式名和表名的元数据?

Posted

技术标签:

【中文标题】如何在 Oracle 中动态分析给定模式名和表名的元数据?【英文标题】:How to analyze metadata of given schema name and table name dynamically in Oracle? 【发布时间】:2021-09-01 07:43:55 【问题描述】:

我正在尝试编写一个动态 oracle plsql 脚本,该脚本将 schema_name 和 table_name 作为参数并返回 Primaty Key 列名、索引标志(如果表包含索引返回 1 否则 0)、db 中表大小的数据量、行所选架构和表的计数和列数。这是我的代码

DECLARE
  p_table_name      VARCHAR2(1000);
  p_owner_name      VARCHAR2(1000);
  v_pk_columns_name VARCHAR2(1000);
  v_ind_exists_flg  VARCHAR2(1000);
  v_data_volume     VARCHAR2(1000);
  v_row_cnt         VARCHAR2(1000);
  v_column_cnt      VARCHAR2(1000);
  v_flag_one        NUMBER;
  v_flag_zero       NUMBER;
  v_constraint_type VARCHAR2(100);
BEGIN
  v_flag_one        := 1;
  v_flag_zero       := 0;
  v_constraint_type := 'P';
  p_owner_name      := '0DS03';
  p_table_name      := 'ODS_SALES';
  v_data_volume     := 'SELECT SUM(bytes)/1024/1024 INTO v_db_size 
                          FROM dba_segments 
                         WHERE owner = ' || p_owner_name ||
                          'AND segment_name = ' || p_table_name;

  EXECUTE IMMEDIATE v_data_volume;

  v_row_cnt := 'SELECT COUNT(*) FROM ' || p_owner_name;
  EXECUTE IMMEDIATE v_row_cnt;

  v_ind_exists_flg := 'SELECT CASE WHEN (index_name) <> null THEN ' || v_flag_one || '
                                   ELSE ' || v_flag_zero || '
                                   END AS flag
                         FROM dba_ind_columns
                        WHERE table_owner =' || p_owner_name ||
                         'AND table_name =' || p_table_name;
  EXECUTE IMMEDIATE v_ind_exists_flg;

  v_column_cnt := 'SELECT COUNT(column_name) 
                     FROM all_tab_columns
                    WHERE table_name =' || p_table_name || 
                     'AND owner = ' ||p_owner_name;
  EXECUTE IMMEDIATE v_column_cnt;

  v_pk_columns_name := 'SELECT cols.column_name
                          FROM all_constraints cons, all_cons_columns cols
                         WHERE cons.constraint_type = ' || v_constraint_type ||
                          'AND cols.owner = ' || p_owner_name ||
                          'AND cons.table_name = ' || p_table_name ||
                          'AND cons.constraint_name = cols.constraint_name
                           AND cons.owner = cols.owner';
  EXECUTE IMMEDIATE v_pk_columns_name;

END;

它给了我这些错误:

ORA-00933: SQL command not properly ended
ORA-06512: at line 23
00933. 00000 -  "SQL command not properly ended"

如何解决这些错误?我的语法有什么问题?感谢您的帮助。

【问题讨论】:

';'不见了。 第 18 行是p_table_name : = 'ODS_SALES'。第 14 列是 ':` 和 = 之间的空格。删除它。您似乎是一次编写了整个过程而不是构建它(即编写和测试代码以做一件事然后扩展它),因为当您更正第一个语法错误时,您会遇到许多额外的语法错误.然后,如果不是所有动态组装的 SQL 语句,大多数情况下都会出现问题。在执行语句之前打印/记录语句,以便调试调用 execute immediate 时遇到的错误。 如果您将EXECUTE IMMEDIATE 与返回值的语句一起使用,请使用EXECUTE IMMEDIATE &lt;your statement&gt; INTO &lt;a variable&gt; 它似乎比它需要的复杂得多,因为它使用动态 SQL。把它变成普通的静态 SQL(除了计算表中的行数),它会更容易使用。 一周前的问题***.com/questions/68884984/… 似乎向您展示了如何做到这一点 【参考方案1】:

工作版本:

declare
    p_owner_name   all_tables.owner%type := 'ODS03';
    p_table_name   all_tables.table_name%type := 'ODS_SALES';

    v_size_mb      number(8,1);
    v_pk_columns   varchar2(1000);
    v_index_cnt    number;
    v_row_cnt_sql  varchar2(1000) := 'select count(*) from ' || p_owner_name||'.'||p_table_name;
    v_row_cnt      integer;
    v_column_cnt   number;
begin
    select sum(bytes)/power(1024,2) into v_size_mb
    from   dba_segments
    where  owner = p_owner_name and segment_name = p_table_name;

    execute immediate v_row_cnt_sql into v_row_cnt;

    select count(*) into v_column_cnt
    from   all_tab_columns
    where  table_name = p_table_name and owner = p_owner_name;

    select count(*) into v_index_cnt
    from   all_indexes i
    where  i.owner = p_owner_name
    and    i.table_name = p_table_name;

    select listagg(cols.column_name, ', ') within group (order by cols.position)
    into   v_pk_columns
    from   all_constraints cons
           join all_cons_columns cols
                on  cols.owner = cons.owner
                and cols.table_name = cons.table_name
                and cols.constraint_name = cons.constraint_name
    where  cons.owner = cols.owner
    and    cons.table_name = p_table_name
    and    cons.constraint_type = 'P';

    dbms_output.put_line('Table: '||p_owner_name||'.'||p_table_name||':');
    dbms_output.put_line('Size (MB): '||round(v_size_mb,1));
    dbms_output.put_line('Rows: '||v_row_cnt);
    dbms_output.put_line('Columns: '||v_column_cnt);
    dbms_output.put_line('Indexes: '||v_index_cnt);
    dbms_output.put_line('PK columns: '||v_pk_columns);
end;

唯一需要动态的部分是计算行数。

在许多系统中,表会非常大,以至于计算行数可能需要很多小时,因此您可能需要考虑对此的要求。可以接受近似计数吗?统计信息 (all_tables.num_rows) 是否足够接近?如果确实需要精确计数,可以考虑并行查询(需要企业版)。

【讨论】:

【参考方案2】:

您的方法不是最优的。您不能使用静态 SQL 从参数化表中进行选择,但可以使用静态 SQL 从数据库元数据中选择任何表。

一种可能的方法是定义一个选择字典数据的视图(这不是必需的,但会使您的过程更简单)。

create or replace  view my_tab_meta as
with pk as (
SELECT COLS.OWNER,CONS.TABLE_NAME, listagg(cols.column_name,'.') within group (order by cols.POSITION) as pk_cols
FROM ALL_CONSTRAINTS CONS, ALL_CONS_COLUMNS COLS
WHERE CONS.CONSTRAINT_TYPE = 'P' 
AND CONS.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME
AND CONS.OWNER = COLS.OWNER
group by COLS.OWNER,CONS.TABLE_NAME),
idx as (
select distinct TABLE_OWNER, TABLE_NAME from all_indexes) 
select tab.OWNER, tab.TABLE_NAME, 
tab.NUM_ROWS, 
tab.BLOCKS * (select VALUE from v$parameter where name = 'db_block_size') /1024/1024 db_size,
case when idx.TABLE_OWNER is not null then 1 else 0 end as idx_flag,
pk.pk_cols
from all_tables tab
left outer join idx
on tab.owner = idx.TABLE_OWNER and tab.TABLE_NAME = idx.TABLE_NAME
left outer join pk
on tab.owner = pk.OWNER and tab.TABLE_NAME = pk.TABLE_NAME;

一些讨论

您应该决定使用 DBA 视图或 ALL 视图 - 而不是组合它们。

您需要访问字典视图的权限才能创建视图

我也从字典中获取大小和行数。这更有效,但需要最新的对象统计信息。

下面是 PL/SQL 块的框架

declare
p_table_name varchar2(128) := 'TAB1';
p_owner_name  varchar2(128) := 'REPORTER';
v_PK_COLUMNS_NAME varchar2(4000);
v_IND_EXISTS_FLG varchar2(1);
v_DATA_VOLUME number;
v_ROW_CNT number;
v_COLUMN_CNT number; 
begin
  select 
    NUM_ROWS, DB_SIZE, IDX_FLAG, PK_COLS 
  into v_ROW_CNT, v_DATA_VOLUME, v_IND_EXISTS_FLG, v_PK_COLUMNS_NAME
  from MY_TAB_META 
  where owner = p_owner_name and table_name = p_table_name;
end;
/

还要注意正确数据类型的使用,例如行数为number

【讨论】:

以上是关于如何在 Oracle 中动态分析给定模式名和表名的元数据?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle18获取数据库当前用户下所有表名和表名的注释

oracle中,改变表名和字段名的大小写

MYSQL如何设置大小写敏感

需要用表名和表的模式列出 SQL Server 数据库中的所有触发器

oracle sql用的mysql软件,查关联的表名的时候会有表名和后面的括号,如图

SQL注入的简单认识