Variable/Literal replacement for PL/SQL Cursors?

旧巷老猫 提交于 2020-06-24 07:53:08

问题


I often have to debug cursors in Oracle PL/SQL. My problem is that I end up with a few hundered lines big cursors with like 50+ variables and constants. I'm searching for a way to get a version of the statement where constants and variables are replaced with their literals. If I want to find out why the cursor isn't showing the record/line it should I end up replacing those variables/literals for 30 minutes before I can run the select and comment out some of the statements to find out what's wrong.

So if I have something like

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

I need the SELECT like this:

SELECT * FROM TABLE
WHERE  col1  = 123
AND    col2 != 5324
AND    col3  = 'ValueXyz';

is there any way to get/log the SELECT in that way so I could just copy paste it in a new SQL window so I don't have to spend 30 minutes to replace that stuff? (should be something I can reuse that's not bind to that special cursor because I need that stuff quite often on a ton of different cursors).


回答1:


The below function replaces bind variables with recent literals, using data from GV$SQL_BIND_CAPTURE. Oracle bind metadata is not always available, so the below function may not work with all queries.

Create the function:

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;

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

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

Run the function:

Oracle only captures the first instance of bind variables. Run this statement before running the procedure to clear existing bind data. Be careful running this statement in production, it may temporarily slow down the system because it lost cached plans.

alter system flush shared_pool;

Now find the SQL_ID. This can be tricky, depending on how generic or unique the SQL is.

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

Finally, plug the SQL into the procedure and it should return the code with literals. Unfortunately the SQL lost all formatting. There's no easy way around this. If it's a huge deal you could potentially build something using PL/Scope to replace the variables in the procedure instead but I have a feeling that would be ridiculously complicated. Hopefully your IDE has a code beautifier.

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

Full example with a procedure:

I modified your source code and added unique identifiers so the queries can be easily found. I used a hint because parsed queries do not include regular comments. I also changed the data types to include strings and dates to make the example more realistic.

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

Results:

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') 



回答2:


The way I do this is to copy and paste the sql into an editor window, prepend all the variables with : and then run the query. As I use Toad, I get a window prompting me for values for all the bind variables in the query, so I fill those out and the query runs. Values are saved, so the query can be rerun without much hassle, or if you need to tweak a value, you can do.

e.g.:

SELECT * FROM TABLE
WHERE  col1  = v1
AND    col2 != v2
AND    col3  = CONSTANT;

becomes

SELECT * FROM TABLE
WHERE  col1  = :v1
AND    col2 != :v2
AND    col3  = :CONSTANT;



回答3:


I think you have to use Dynamic SQL functionality to get those variable values. By using ref cursor variable you can even see the output.
Please take a look at the below query.

DECLARE
vString  VARCHAR2 (32000);
vResult  sys_refcursor;
BEGIN
vString := 
     'SELECT * FROM table   
       WHERE col1 = '|| v1|| ' 
         AND col2 != '|| v2|| ' 
         AND col3 = '|| v;

OPEN vResult FOR vString;

DBMS_OUTPUT.put_line (vString);
END;

If you have a larger Cursor query it is not a efficient way. Because you may need to replace whole Cursor query into Dynamic SQL.




回答4:


A possible approach would be assiging the cursor to a SYS_REFCURSOR variable, and then assign the SYS_REFCURSOR to a bind variable.

If you run this snippet in Toad, you'll be asked to define the :out variable in the pop-up window: just select Direction: OUT / Type: CURSOR and the dataset will be shown in the "Data Grid" tab.

declare
  l_refcur   sys_refcursor;
  v1         varchar2(4) := 'v1'; 
  v2         varchar2(4) := 'v2'; 
  c_constant varchar2(4) := 'X';
begin
  open l_refcur for
    SELECT * FROM dual
     WHERE dummy  = c_CONSTANT;
  :out := l_refcur;
end;

Other SQL IDEs should support this feature as well.



来源:https://stackoverflow.com/questions/34084667/variable-literal-replacement-for-pl-sql-cursors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!