oracle - PL/SQL 游标的变量/文字替换?

标签 oracle plsql cursors

我经常需要在 Oracle PL/SQL 中调试游标。我的问题是我最终得到了几百行大光标,其中包含 50 多个变量和常量。我正在寻找一种方法来获取将常量和变量替换为其文字的语句版本。如果我想找出为什么光标没有显示记录/行,我应该最终替换这些变量/文字 30 分钟,然后才能运行选择并注释掉一些语句以找出问题所在。

所以如果我有类似的东西

CURSOR cFunnyCursor (
  v1 NUMBER,
  v2 NUMBER
) IS
SELECT * FROM TABLE
WHERE  col1  = v1
AND    col2 != v2
AND    col3  = CONSTANT;

我需要这样的选择:
SELECT * FROM TABLE
WHERE  col1  = 123
AND    col2 != 5324
AND    col3  = 'ValueXyz';

有没有办法以这种方式获取/记录 SELECT 以便我可以将其复制粘贴到新的 SQL 窗口中,这样我就不必花费 30 分钟来替换那些东西? (应该是我可以重用的东西,它没有绑定(bind)到那个特殊的游标,因为我经常在大量不同的游标上需要这些东西)。

最佳答案

下面的函数使用来自 GV$SQL_BIND_CAPTURE 的数据用最近的文字替换绑定(bind)变量。 Oracle 绑定(bind)元数据并非始终可用,因此以下函数可能不适用于所有查询。
创建函数:

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 只捕获绑定(bind)变量的第一个实例。在运行过程之前运行此语句以清除现有绑定(bind)数据。在生产环境中运行此语句时要小心,它可能会因为丢失缓存计划而暂时降低系统速度。
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') 

关于oracle - PL/SQL 游标的变量/文字替换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34084667/

相关文章:

cursors - TRS-80 POKE 光标表笑脸?

java - 从 JAVA 调用具有多个 IN 子句的查询

json - 我在 oracle 12.1.0.2 中运行 json_object,它说错误。为什么?

database - Oracle 数据泵 impdp 到远程服务器

oracle - 第 4 行错误 : PL/SQL: SQL Statement ignored

oracle - PL/SQL 日期维度填充数据 - 数据仓库

sql - 在 PL/SQL VARRAY 中使用 ROWTYPE 会产生编译错误

java - ORA-01000 - 超出最大打开游标数 - Spring JDBC 3.2.5

sql - 在 concat,oracle 期间格式化列标题