sql >> データベース >  >> RDS >> Oracle

PL / SQLカーソルの変数/リテラル​​置換?

    以下の関数は、GV $ SQL_BIND_CAPTUREのデータを使用して、バインド変数を最近のリテラルに置き換えます。 Oracleバインドメタデータは常に利用できるとは限らないため、以下の関数がすべてのクエリで機能するとは限りません。

    関数を作成します:

    create or replace function get_sql_with_literals(p_sql_id varchar2) return clob authid current_user is
    /*
        Purpose: Generate a SQL statement with literals, based on values in GV$SQL_BIND_CAPTURE.
            This can be helpful for queries with hundreds of bind variables (or cursor sharing),
            and you don't want to spend minutes manually typing each variable.
    */
        v_sql_text clob;
        v_names sys.odcivarchar2list;
        v_values sys.odcivarchar2list;
    begin
        --Get the SQL_ID and text.
        --(Use dynamic SQL to simplify privileges.  Your user must have access to GV$ views,
        -- but you don't need to have them directly granted to your user, role access is fine.)
        execute immediate
        q'[
            select sql_fulltext
            from gv$sql
            --There may be multiple rows, for clusters or child cursors.
            --Can't use distinct with CLOB SQL_FULLTEXT, but since the values will be the same
            --we can pick any one of the rows.
            where sql_id = :p_sql_id
                and rownum = 1
        ]'
        into v_sql_text
        using p_sql_id;
    
        --Try to find the binds from GV$SQL_MONITOR.  If the values exist, this is the most accurate source.
        execute immediate
        q'[
            --Get the binds for the latest run.
            select
                case
                    when name like ':SYS_%' then ':"' || substr(name, 2) || '"'
                    else name
                end name,
                case
                    when dtystr like 'NUMBER%' then nvl(the_value, 'NULL')
                    when dtystr like 'VARCHAR2%' then '''' || the_value || ''''
                    when dtystr like 'DATE%' then 'to_date('''||the_value||''', ''MM/DD/YYYY HH24:MI:SS'')'
                    --From: https://ardentperf.com/2013/11/19/convert-rawhex-to-timestamp/
                    when dtystr like 'TIMESTAMP%' then
                        'to_timestamp('''||
                            to_char( to_number( substr( the_value, 1, 2 ), 'xx' ) - 100, 'fm00' ) ||
                            to_char( to_number( substr( the_value, 3, 2 ), 'xx' ) - 100, 'fm00' ) ||
                            to_char( to_number( substr( the_value, 5, 2 ), 'xx' ), 'fm00' ) ||
                            to_char( to_number( substr( the_value, 7, 2 ), 'xx' ), 'fm00' ) ||
                            to_char( to_number( substr( the_value, 9, 2 ), 'xx' )-1, 'fm00' ) ||
                            to_char( to_number( substr( the_value,11, 2 ), 'xx' )-1, 'fm00' ) ||
                            to_char( to_number( substr( the_value,13, 2 ), 'xx' )-1, 'fm00' ) ||
                            ''', ''yyyymmddhh24miss'')'
                    else 'Unknown type: '||dtystr
                end the_value
            from
            (
                select xmltype.createXML(binds_xml) binds_xml
                from
                (
                    select binds_xml, last_refresh_time, max(last_refresh_time) over () max_last_refresh_time
                    from gv$sql_monitor
                    where sql_id = :p_sql_id
                        and binds_xml is not null
                )
                where last_refresh_time = max_last_refresh_time
                    and rownum = 1
            ) binds
            cross join
            xmltable('/binds/bind' passing binds.binds_xml
                columns
                    name varchar2(128) path '@name',
                    dtystr varchar2(128) path '@dtystr',
                    the_value varchar2(4000) path '/'
            )
            --Match longest names first to avoid matching substrings.
            --For example, we don't want ":b1" to be matched to ":b10".
            order by length(name) desc, the_value
        ]'
        bulk collect into v_names, v_values
        using p_sql_id;
    
    
        --Use gv$sql_bind_capture if there was nothing from SQL Monitor.
        if v_names is null or v_names.count = 0 then
            --Get bind data.
            execute immediate
            q'[
                select
                    name,
                    --Convert to literals that can  be plugged in.
                    case
                        when datatype_string like 'NUMBER%' then nvl(value_string, 'NULL')
                        when datatype_string like 'VARCHAR%' then '''' || value_string || ''''
                        when datatype_string like 'DATE%' then 'to_date('''||value_string||''', ''MM/DD/YYYY HH24:MI:SS'')'
                        --TODO: Add more types here
                    end value
                from
                (
                    select
                        datatype_string,
                        --If CURSOR_SHARING=FORCE, literals are replaced with bind variables and use a different format.
                        --The name is stored as :SYS_B_01, but the actual string will be :"SYS_B_01".
                        case
                            when name like ':SYS_%' then ':"' || substr(name, 2) || '"'
                            else name
                        end name,
                        position,
                        value_string,
                        --If there are multiple bind values captured, only get the latest set.
                        row_number() over (partition by name order by last_captured desc nulls last, address) last_when_1
                    from gv$sql_bind_capture
                    where sql_id = :p_sql_id
                )
                where last_when_1 = 1
                --Match longest names first to avoid matching substrings.
                --For example, we don't want ":b1" to be matched to ":b10".
                order by length(name) desc, position
            ]'
            bulk collect into v_names, v_values
            using p_sql_id;
        end if;
    
        --Loop through the binds and replace them.
        for i in 1 .. v_names.count loop
            v_sql_text := replace(v_sql_text, v_names(i), v_values(i));
        end loop;
    
        --Return the SQL.
        return v_sql_text;
    end;
    /
    

    関数を実行します:

    Oracleは、バインド変数の最初のインスタンスのみをキャプチャします。プロシージャを実行する前にこのステートメントを実行して、既存のバインドデータをクリアします。このステートメントを本番環境で実行する場合は注意が必要です。キャッシュされたプランが失われるため、システムの速度が一時的に低下する可能性があります。

    alter system flush shared_pool;
    

    次に、SQL_IDを見つけます。これは、SQLの汎用性または一意性によっては、注意が必要な場合があります。

    select *
    from gv$sql
    where lower(sql_fulltext) like lower('%unique_string%')
        and sql_fulltext not like '%quine%';
    

    最後に、SQLをプロシージャにプラグインすると、リテラルを含むコードが返されます。残念ながら、SQLはすべてのフォーマットを失いました。これを回避する簡単な方法はありません。それが大したことなら、代わりにPL / Scopeを使って何かを構築し、代わりにプロシージャ内の変数を置き換えることができるかもしれませんが、途方もなく複雑になると思います。うまくいけば、IDEにコードビューティファイアがあります。

    select get_sql_with_literals(p_sql_id => '65xzbdjubzdqz') sql
    from dual;
    

    手順を含む完全な例:

    ソースコードを変更し、クエリを簡単に見つけられるように一意の識別子を追加しました。解析されたクエリには通常のコメントが含まれていないため、ヒントを使用しました。また、例をより現実的にするために、文字列と日付を含むようにデータ型を変更しました。

    drop table test1 purge;
    create table test1(col1 number, col2 varchar2(100), col3 date);
    
    create or replace procedure test_procedure is
        C_Constant constant date := date '2000-01-01';
        v_output1 number;
        v_output2 varchar2(100);
        v_output3 date;
    
        CURSOR cFunnyCursor (
          v1 NUMBER,
          v2 VARCHAR2
        ) IS
        SELECT /*+ unique_string_1 */ * FROM TEST1
        WHERE  col1  = v1
        AND    col2 != v2
        AND    col3  = C_CONSTANT;
    begin
        open cFunnyCursor(3, 'asdf');
        fetch cFunnyCursor into v_output1, v_output2, v_output3;
        close cFunnyCursor;
    end;
    /
    
    begin
        test_procedure;
    end;
    /
    
    select *
    from gv$sql
    where lower(sql_fulltext) like lower('%unique_string%')
        and sql_fulltext not like '%quine%';
    

    結果:

    select get_sql_with_literals(p_sql_id => '65xzbdjubzdqz') sql
    from dual;
    
    SQL
    ---
    SELECT /*+ unique_string_1 */ * FROM TEST1 WHERE COL1 = 3 AND COL2 != 'asdf' AND COL3 = to_date('01/01/2000 00:00:00', 'MM/DD/YYYY HH24:MI:SS') 
    


    1. オープンクエリ関数を使用して、SQLサーバーからOracleストアドプロシージャを実行しました

    2. Oracle DBの複数のテーブルを更新する方法は?

    3. Webページでユーザーステータス(アクティブ、アイドル)を確認する

    4. MySQLログを開始して確認するにはどうすればよいですか?