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