2016-07-19 4 views
2

私は約5つのスキーマですべてのテーブルのすべての列からデータのサンプルを取得しようとしています。以下は、このデータをちょうど1つのスキーマから取得する例です( "sde"をスキーマの実行に置き換えてください)。この作品は正常に動作:Oracle/SQL各テーブルの各列のデータのサンプル - あまりにも多いですか?

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 

私が直面してる問題は、私が実行したときにということです唯一の5000行で

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 

この特定のスキーマは、束の中で最小である:労働組合の完全なセットは、それが終了したことがない、私は使用して一度に数千行の数100を実行することができました復帰する。オンザフライクエリでは不可能な作業をしようとしていますか?または、これをより効率的にする方法や、これをループに分割して何らかの方法でチャンクをつくる方法はありますか?デベロッパーが関与してテーブルやETLなどを作成する必要がないようにしようとしています。私はSQLのエキスパートではありませんが、正しい方向を指していれば、それを解読できます。 :)

ありがとうございます。

+0

あなたが解決しようとしている実際の問題とは何ですか?おそらく、5つのスキーマのすべての表の各列のデータをサンプリングするだけでは、実際の問題に対する唯一の解決策ではありません(そしておそらく最善の解決策ではないかもしれません)。 – mathguy

+0

確かに。私は利用可能なテーブルと列をすばやく検索するためのカスタマー対応のTableauレポートを作成しました。これには、スキーマ、オブジェクトタイプ(テーブル、ビューなど)、テーブル名、列名、データタイプ、およびコメントなどの列が含まれます。私はまた、 "サンプルデータ"を各テーブルの各列からの単一の行のデータである列として含めることを望んでいました。私はそれを行う方法を知っていますが、ユニオンセットは、任意の通常のサイズのデータ​​セットでハングアップするようです...いくつかの最適化ポインタや代替メソッドを同じものを達成するために願っています。 – Drivium

答えて

1

はい、これはおそらく1つのクエリでは多すぎます。

解析のバグ、再帰的なSQL、または空のスペースのためにクエリが絶望的に​​遅くなる可能性があります。この作業には、困難ではあるが正確なストアドプロシージャ、または列統計を使用する迅速で不正確なバージョンが必要です。


は、なぜそれが遅い

  1. 解析時 Oracleのコンパイラは通常、高速であるかもしれないが、解析時間が指数関数的に増加するいくつかの奇妙な場合があります。多くの場合、UNION ALLsを使用することがそのケースの1つです。特に古いバージョンでは、10gのように、UNION ALLsが500以上のものが永遠に動作します。これらの問題は、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; 
    /
    

    enter image description here

  2. 再帰SQL時々、SQL文を調製するのに使用されるSQLは、問題になる可能性があります。通常、最悪の犯罪者は動的サンプリングです。動的サンプリングは、オンザフライ・テーブル統計を生成するために少量のデータを見つけるためのクエリを生成します。通常は高速ですが、クエリの実行計画が悪く、速度が遅くなることがあります。 /*+ dynamic_sampling(0) */のようなヒントで無効にすることができます。

    GV $ SQLを見ると、同様のヒントを使用する他の再帰的なSQLを見つけることができます。 /*+ no_parallel */のようなヒントの一部をコピーすることができます。

  3. 空き領域最初の行を取得することは、必ずしも簡単なことではありません。テーブルは以前はテラバイトのデータを持っていた可能性があり、誰かが99.9%を削除していて、セグメントの最後に1行しか置かれていない可能性があります。オラクルは、その1行を見つけるためにすべての空き領域を調べなければなりません。テーブルを再編成するなど、それを解決する方法がありますが、誰かがそれを忘れている可能性があります。

    おそらく10億行がありますが、そのうちの1つだけにその列の値があります。その列には索引がないため、表全体を読み取る必要があります。

可能な解決策

  1. 正確な、痛みを伴う道は最も正確な解決策は、一度にサブクエリの小さな塊を検索するストアドプロシージャが必要です。おそらく空のスペースの問題に対して並列処理を使用しています。おそらくテーブルに値を一時的に保存し、遅いクエリのトラブルシューティングを行う必要があります。小さなチャンクサイズは問題の解析を避け、少なくとも他の問題のあるサブクエリを見つけるのを容易にします。

  2. 不正確で高速な方法 Oracleは、デフォルトでデータベースの各列のオプティマイザ統計を収集します。デフォルトのアルゴリズムでは、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 
    
+0

非常に丁寧な対応をありがとうございます。これは素晴らしい情報です。シンプルなクエリで、私が思うことが達成できるとは思えません。私は機能を実行する権限を持っていないようだ...私はこの努力で敗北を認めなければならないかもしれない。 – Drivium

関連する問題