【问题标题】:Oracle/SQL Sample of data from each column from each table - too much?Oracle/SQL 每个表中每一列的数据样本——太多了?
【发布时间】:2016-07-19 18:10:41
【问题描述】:

我正在尝试从大约 5 个模式中的每个表的每一列中获取数据样本。下面是我仅从 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 行要返回。我正在尝试为动态查询做一项不可能完成的任务吗?或者有没有一种方法可以提高效率或将其分解成循环以某种方式抓取块?尽量避免让我们的开发人员参与并创建表、ETL 等。我不是 SQL 专家,但如果我指向正确的方向,我可以破解它。 :)

提前致谢。

【问题讨论】:

  • 您要解决的实际问题是什么?也许从五个模式中每个表的每一列中采样数据并不是解决实际问题的唯一解决方案(甚至可能不是最好的解决方案)。
  • 当然。我创建了一个面向客户的 Tableau 报告,它提供了一种快速搜索可用表和列的方法。它包括架构、对象类型(表、视图等)、表名、列名、数据类型和注释等列。我还想将“示例数据”作为一列包含在内,它是每个表中每一列的单行数据。我知道该怎么做,但是联合集似乎与任何正常大小的数据集都挂起......希望有一些优化指针或替代方法来实现同样的效果。

标签: sql oracle toad


【解决方案1】:

是的,在一个查询中这可能太多了。

由于解析错误、递归 SQL 或空白空间,查询可能会非常缓慢 此任务可能需要一些开发 - 要么是困难但准确的存储过程,要么是使用列统计信息的快速且不准确的版本。


为什么会慢

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

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

    --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 可能会出现问题。通常最严重的违规者是动态采样。动态采样生成查询以查找少量数据以生成动态表统计信息。通常它很快,但有时这些查询可能有糟糕的执行计划并且很慢。您可以通过 /*+ dynamic_sampling(0) */ 之类的提示禁用它。

    如果您查看 GV$SQL,您会发现其他一些使用类似提示的递归 SQL。您可能需要复制其中的一些提示,例如 /*+ no_parallel */

  3. 空白空间 检索第一行不一定是一件小事。有可能一张表以前有 1TB 的数据,有人删除了 99.9% 的数据,而该段的末尾只有一行。 Oracle 必须查看所有空白空间才能找到那一行。有一些方法可以解决这个问题,比如重新组织表格,但可能有人忘记了这样做。

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

可能的解决方案

  1. 准确而痛苦的方法 最准确的解决方案需要一个存储过程来一次搜索较小的子查询块。可能对任何空白空间问题使用并行性。您可能需要将值临时存储在表中,并在此过程中对一些缓慢的查询进行故障排除。较小的块大小将避免解析问题,并且至少可以更容易地找到存在其他问题的子查询。

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

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

    以下代码来自 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;
    / 
    

    快速返回结果的查询:

    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
    

【讨论】:

  • 感谢您非常彻底的回复。这是很好的信息。听起来我所追求的不太可能通过一个简单的查询来实现。看来我没有运行函数的权限......我可能不得不承认这项努力的失败。
猜你喜欢
  • 1970-01-01
  • 2021-09-26
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多