Oracle/SQL 每个 table 中每列的数据样本 - 太多了?

Oracle/SQL Sample of data from each column from each table - too much?

我试图从大约 5 个模式中每个 table 的每一列中获取数据样本。下面是我仅从 1 个模式获取此数据的示例(将 "sde" 替换为 运行 的任何模式)。这篇运行还不错:

select CASE when 
lead(ROWNUM) over(order by ROWNUM) is null then
'select '||''''||T.TABLE_NAME||''''||' as TABLE_NAME,'||''''||T.COLUMN_NAME||''''||' as COLUMN_NAME, cast('|| T.COLUMN_NAME ||' as 
VarChar2(50)) as SAMPLE_DATA from sde.'||T.TABLE_NAME ||' where '||T.COLUMN_NAME||' is not null and ROWNUM=1;'  
else
'select '||''''||T.TABLE_NAME||''''||' as TABLE_NAME,'||''''||T.COLUMN_NAME||''''||' as COLUMN_NAME, cast('|| T.COLUMN_NAME ||' as 
VarChar2(50)) as SAMPLE_DATA from sde.'||T.TABLE_NAME ||' where '||T.COLUMN_NAME||' is not null and ROWNUM=1 union all' end as qry_txt
from all_tab_columns t where T.OWNER='SDE' and T.DATA_TYPE != 'BLOB' and T.DATA_TYPE != 'LONG'
ORDER BY ROWNUM asc, qry_txt asc

当上面的结果集是 运行 时,这里是 1 行输出的示例:

select 'HUD_TYPE' as TABLE_NAME,'HUD_TYPE_ID' as COLUMN_NAME, cast(HUD_TYPE_ID as VarChar2(50)) as SAMPLE_DATA from sde.HUD_TYPE where HUD_TYPE_ID is not null and ROWNUM=1 union all

我面临的问题是,当我 运行 完整的联合集时,它永远不会完成,我只能 运行 几 100 到几 1000 行一次使用:

select CASE when 
lead(ROWNUM) over(order by ROWNUM) is null then
'select '||''''||T.TABLE_NAME||''''||' as TABLE_NAME,'||''''||T.COLUMN_NAME||''''||' as COLUMN_NAME, cast('|| T.COLUMN_NAME ||' as 
VarChar2(50)) as SAMPLE_DATA from sde.'||T.TABLE_NAME ||' where '||T.COLUMN_NAME||' is not null and ROWNUM=1;'  
else
'select '||''''||T.TABLE_NAME||''''||' as TABLE_NAME,'||''''||T.COLUMN_NAME||''''||' as COLUMN_NAME, cast('|| T.COLUMN_NAME ||' as 
VarChar2(50)) as SAMPLE_DATA from sde.'||T.TABLE_NAME ||' where '||T.COLUMN_NAME||' is not null and ROWNUM=1 union all' end as qry_txt
from all_tab_columns t where T.OWNER='SDE' and T.DATA_TYPE != 'BLOB' and T.DATA_TYPE != 'LONG'
ORDER BY ROWNUM asc, qry_txt asc

OFFSET 4800 ROWS FETCH NEXT 400 ROWS ONLY; --Using this method so I grab the last few hundred lines so my case statement remains valid for demo

这个特定的架构是其中最小的架构,只有 5,000 行到 return。我正在尝试为即时查询完成一项不可能完成的任务吗?或者有没有一种方法可以提高效率或将其分解成循环以某种方式获取块?尽量避免让我们的开发人员参与并创建 tables、ETL 等。我不是 SQL 专家,但如果我指向正确的方向,我可以破解它. :)

提前致谢。

是的,一次查询可能做的太多了。

由于解析错误、递归 SQL 或空 space,查询可能会非常慢使用列统计信息的版本。


为什么它可能很慢

  1. 解析时间 Oracle 的编译器通常很快,但在某些奇怪的情况下解析时间呈指数增长。使用大量 UNION ALLs 就是其中一种情况。特别是对于旧版本,如 10g,超过 500 UNION ALLs 将永远 运行。这些问题会影响类似的方法,例如我在 this answer 中展示的多 table 插入。所以可能没有简单的解决方法。

    此代码显示了查询时间如何随 UNION ALL 呈指数增长。这是 最好的 可能情况,仅使用 DUAL table.

    --Find time to execute simple statements.
    declare
        v_sql clob := 'select 1 a from dual';
        v_count number;
        v_time_before number;
        v_time_after number;
    begin
        for i in 1 .. 5000 loop
            v_sql := v_sql || ' union all select 1 a from dual';
            --Only execute every 100th iteration.
            if mod(i, 100) = 0 then
                v_time_before := dbms_utility.get_time;
                execute immediate 'select count(*) from ('||v_sql||')' into v_count;
                v_time_after := dbms_utility.get_time;
                dbms_output.put_line(i||':'||to_char(v_time_after-v_time_before));
            end if;
        end loop;
    end;
    /
    

  2. 递归 SQL 有时用于准备 SQL 语句的 SQL 可能是个问题。通常最严重的罪犯是动态采样。动态采样生成查询以查找少量数据以生成动态 table 统计信息。通常它很快,但有时这些查询可能有糟糕的执行计划并且很慢。您可以使用 /*+ dynamic_sampling(0) */.

    之类的提示禁用它

    如果您查看 GV$SQL,您可以找到其他一些使用类似提示的递归 SQL。您可能想要复制其中一些提示,例如 /*+ no_parallel */.

  3. Empty Space 检索第一行不一定是一件小事。可能 table 之前有 1 TB 的数据,有人删除了其中的 99.9%,并且只有一行位于段的末尾。 Oracle 必须查看所有空的 space 才能找到那一行。有一些方法可以解决这个问题,比如重新组织 table,但可能有人忘记了。

    或者可能有十亿行,但只有其中一行具有该列的值。该列没有索引,因此必须读取整个 table。

可能的解决方案

  1. 准确、痛苦的方式最准确的解决方案需要一个存储过程一次搜索较小的子查询块。可能对任何空 space 问题使用并行性。您可能需要暂时将这些值存储在 table 中,并在此过程中解决一些缓慢的查询问题。较小的块大小将避免解析问题,并且至少可以更容易地找到有其他问题的子查询。

  2. 不准确、快速的方式 Oracle 默认收集数据库中每一列的优化器统计信息。 (您需要与 DBA 检查以确保他们没有禁用默认值,不幸的是许多 DBA 都这样做。)使用默认算法 Oracle 扫描每个 table 中的每一行并记录高值和低值对于每一列。

    几乎可以立即从数据字典中读取那些高值和低值。问题是转换为原始值并不完全准确。

    以下代码来自 Jonathan Lewis 的 this article,具体来自匿名评论。

    create or replace function raw_to_num(i_raw raw)
    return number
    as
        m_n number;
    begin
        dbms_stats.convert_raw_value(i_raw,m_n);
        return m_n;
    end;
    /  
    
    create or replace function raw_to_date(i_raw raw)
    return date
    as
        m_n date;
    begin
        dbms_stats.convert_raw_value(i_raw,m_n);
        return m_n;
    end;
    /  
    
    create or replace function raw_to_varchar2(i_raw raw)
    return varchar2
    as
        m_n varchar2(32767);
    begin
        dbms_stats.convert_raw_value(i_raw,m_n);
        return m_n;
    end;
    / 
    

    查询 returns 结果很快:

    select
            table_name,
            column_name,
            decode(data_type,
                    'VARCHAR2',to_char(raw_to_varchar2(low_value)),
                    'DATE',to_char(raw_to_date(low_value)),
                    'NUMBER',to_char(raw_to_num(low_value))
            ) low_value,
            decode(data_type,
                    'VARCHAR2',to_char(raw_to_varchar2(high_value)),
                    'DATE',to_char(raw_to_date(high_value)),
                    'NUMBER',to_char(raw_to_num(high_value))
            ) high_value
    from all_tab_columns t where T.OWNER='SDE' and T.DATA_TYPE != 'BLOB' and T.DATA_TYPE != 'LONG'
    order by 1,2