Oracle
 sql >> Baza danych >  >> RDS >> Oracle

Zmienna/dosłowna zamiana kursorów PL/SQL?

Poniższa funkcja zastępuje zmienne bind najnowszymi literałami, używając danych z GV$SQL_BIND_CAPTURE. Metadane powiązania Oracle nie zawsze są dostępne, więc poniższa funkcja może nie działać ze wszystkimi zapytaniami.

Utwórz funkcję:

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;
/

Uruchom funkcję:

Oracle przechwytuje tylko pierwsze wystąpienie zmiennych wiązania. Uruchom tę instrukcję przed uruchomieniem procedury, aby wyczyścić istniejące dane wiązania. Zachowaj ostrożność, uruchamiając to polecenie w środowisku produkcyjnym, może to tymczasowo spowolnić system, ponieważ utracił buforowane plany.

alter system flush shared_pool;

Teraz znajdź SQL_ID. Może to być trudne, w zależności od tego, jak ogólny lub unikalny jest kod SQL.

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

Na koniec podłącz SQL do procedury, która powinna zwrócić kod z literałami. Niestety SQL stracił całe formatowanie. Nie ma łatwego sposobu na obejście tego. Jeśli to wielka sprawa, możesz potencjalnie zbudować coś przy użyciu PL/Scope, aby zastąpić zmienne w procedurze, ale mam wrażenie, że byłoby to śmiesznie skomplikowane. Mam nadzieję, że Twoje IDE ma upiększacz kodu.

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

Pełny przykład z procedurą:

Zmodyfikowałem Twój kod źródłowy i dodałem unikalne identyfikatory, aby zapytania można było łatwo znaleźć. Użyłem podpowiedzi, ponieważ analizowane zapytania nie zawierają zwykłych komentarzy. Zmieniłem również typy danych, aby zawierały ciągi i daty, aby przykład był bardziej realistyczny.

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%';

Wyniki:

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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Czy dobrze jest zawsze używać klauzuli join w Oracle?

  2. Proste przypisanie SQL zmiennej Oracle

  3. Maksymalna suma zapytania

  4. Mapowanie Split Entity dające nieoczekiwane wyniki z bazą danych Oracle

  5. Jak mogę łatwo przeanalizować wykonanie pakietu Oracle pod kątem problemów z wydajnością?