{"id":1885,"date":"2023-12-08T01:38:48","date_gmt":"2023-12-07T23:38:48","guid":{"rendered":"https:\/\/blog.sqlora.com\/en\/?p=1885"},"modified":"2023-12-08T01:38:51","modified_gmt":"2023-12-07T23:38:51","slug":"how-to-get-the-final-sql-after-macro-expansion","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/how-to-get-the-final-sql-after-macro-expansion\/","title":{"rendered":"How to get the final SQL after macro expansion"},"content":{"rendered":"\n<p>In my <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.sqlora.com\/en\/oracle-20c-sql-macros\/\" target=\"_blank\">very first post<\/a> about SQL macros, I mentioned that for table macros, there is a simple way to see the SQL statement after macro expansion using <code><strong>dbms_utility.expand_sql_text<\/strong><\/code>. However, for scalar SQL macros, there is no such straightforward method. We can activate a CBO trace (also known as event 10053) and find the final statement in the trace file. This approach works for both scalar and table SQL macros. In this post, we will explore how to do this, and we will use&#8230; a SQL macro for that! Well, at least we will give it a try&#8230;<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>If you are a DBA, generating and accessing a CBO trace may not be an issue at all for you. If you are a developer then it may be a bit trickier in terms of the workflow and the required privileges. It would probably make sense to develop this process as a privileged user and encapsulate it in a definer rights PL\/SQL unit and then grant the developers the execute privilege on it. <\/p>\n\n\n\n<p>But do we have to use SQL macro for it? &#8211; Definitely <strong>not!<\/strong> We could develop a procedure or function returning CLOB or a pipelined function returning a content of a trace file row by row. But because I&#8217;m just trying to get to know the SQL macros more deeply &#8211; what limitations, edge cases, do&#8217;s and don&#8217;ts there are &#8211; I&#8217;m reaching for every opportunity to use them. That&#8217;s my reason for trying to solve the task as a SQL macro. And indeed, it paid off immediately. Because it was just the use case that made me realize that the behavior of the macros with regard to definer\/invoker rights <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-and-invoker-rights\/\" target=\"_blank\">does not correspond to the documentation, as described in the previous post<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Approach<\/h2>\n\n\n\n<p>How do I expect the solution to work? We&#8217;ll develop a table SQL macro, which we can select from, passing the original statement with SQL macro(s) as parameter and getting the final query back. <\/p>\n\n\n\n<p>We will use the following SQL macro for the test (let&#8217;s name it test macro for clarity): <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; quick-code: false; notranslate\" title=\"\">\ncreate or replace function duration_years \n                    (p_date_from in date\n                   , p_date_to in date) \nreturn varchar2 sql_macro(scalar) as\nbegin\n   return q&#039;&#x5B; floor(months_between(p_date_to, p_date_from)\/12) ]&#039;;\nend;\n\/\n<\/pre><\/div>\n\n\n<p><em><strong>Listing 1<\/strong><\/em><\/p>\n\n\n\n<p>And here is the SQL statement using this macro that we want to inspect more closely:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nselect ename\n,      duration_years(hiredate, sysdate) as years\n,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\nfrom   emp e;\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 2<\/em><\/strong><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Note 1: How to pass a query as a parameter?<\/h4>\n\n\n\n<p>It is not clearly documented, but the values for the text parameters are just not visible (nulled) inside the macro body. If we want to pass the query as a parameter, we can do this by wrapping it in a collection, as <a rel=\"noreferrer noopener\" href=\"https:\/\/stewashton.wordpress.com\/2022\/01\/03\/sql-table-macros-9-just-pivot\/\" target=\"_blank\">Stew Ashton proposes<\/a>. For example, using a pre-defined Oracle type <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-black-color\"><strong>sys.odcivarchar2list<\/strong>.<\/mark> If you want to learn more about SQL macros, I would highly recommend reading his blog (there are currently 13 posts about SQL macros). And have a look at his very useful package <a rel=\"noreferrer noopener\" href=\"https:\/\/stewashton.wordpress.com\/2021\/12\/10\/sql-table-macros-6-sqm_util-package\/\" target=\"_blank\"><strong>SQM_UTIL<\/strong>.<\/a> <\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-an-oracle-programmer wp-block-embed-an-oracle-programmer\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"T8SgH2AsNs\"><a href=\"https:\/\/stewashton.wordpress.com\/2022\/01\/03\/sql-table-macros-9-just-pivot\/\">SQL table macros 9: just&nbsp;pivot!<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; clip: rect(1px, 1px, 1px, 1px);\" title=\"&#8220;SQL table macros 9: just&nbsp;pivot!&#8221; &#8212; An Oracle Programmer\" src=\"https:\/\/stewashton.wordpress.com\/2022\/01\/03\/sql-table-macros-9-just-pivot\/embed\/#?secret=JVW1JYzXSd#?secret=T8SgH2AsNs\" data-secret=\"T8SgH2AsNs\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Note 2: How to force the CBO trace creation<\/h4>\n\n\n\n<p>First of all, what is the goal of it? We don&#8217;t want to examine the execution plan and why a particular execution plan was generated an selected. We just want to see the query after macro expansion. Hence, we don&#8217;t have to pay attention to exact environment such as NLS-settings and so on, compared to how the query will run in real life.  Maybe I am wrong, but I don&#8217;t yet see how they can influence the macro expansion.<\/p>\n\n\n\n<p>On the other hand, we want the trace file to be created each time we call the SQL macro. The point is, the trace file will only be created while hard parsing. If the SQL has already been hard parsed, no trace file will be created. To overcome this behavior we can use the procedure <code><strong>DBMS_SQLDIAG.DUMP_TRACE<\/strong><\/code>.  Per default, it also behaves the above-mentioned way, but according to MOS Note 2909982.1 we can force the trace creation by using the value <strong>&#8216;Compiler&#8217;<\/strong> instead of <strong>&#8216;Optimizer&#8217; <\/strong>for the parameter <strong><em>p_component:<\/em><\/strong><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nbegin\n    DBMS_SQLDIAG.DUMP_TRACE( p_sql_id=&gt;&#039;f8hxn7zk6jvyu&#039;\n                    , p_child_number=&gt;1\n                    , p_component=&gt;&#039;Compiler&#039;\n                    , p_file_id=&gt;&#039;SQM_2023&#039;);\nend;\n\/\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 3 <\/em><\/strong><\/p>\n\n\n\n<p>As you can see, we pass the SQL_ID and the child number. That is, the SQL_ID have to be in the shared pool already or you&#8217;ll get an error.  First, I thought just to do an <strong>OPEN &lt;cursor&gt; FOR &lt;query&gt;<\/strong>. We don&#8217;t need to fetch, actually. However, this approach will not work for DML. After all,  I decided to treat the presence of as SQL_ID in a shared pool as a requirement.  <\/p>\n\n\n\n<p>Now we can start coding and implement the first version.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\ncreate or replace  function resolve_sqlmacro (P_query in sys.odcivarchar2list) \n  return varchar2 sql_macro is\n    v_result varchar2(4000);\n    v_tracefile varchar2(100) ;\n    v_dump_trace_cmd varchar2(500);\n    v_sql_id varchar2(20);\n    v_cur sys_refcursor;\n    v_child number;\nbegin\n  v_sql_id := dbms_sql_translator.sql_id(p_query(1));\n -- will not work for DML \n -- open v_cur for p_query (1);\n  select max(child_number) into v_child from v$sql where sql_id = v_sql_id;\n  \n  if v_child is not null then  \n    v_dump_trace_cmd := \n            replace(\n                replace(\n                     replace( q&#039;&#x5B;\n                        begin \n                            DBMS_SQLDIAG.DUMP_TRACE(   p_sql_id=&gt;&#039;#sql_id#&#039;\n                                                    ,  p_child_number=&gt; &#039;#child#&#039;\n                                                    ,  p_component=&gt;&#039;Compiler&#039;\n                                                    ,  p_file_id=&gt;&#039;#v_trace_identifier#&#039;);\n                        end;]&#039;,&#039;#sql_id#&#039;,v_sql_id)\n                    , &#039;#v_trace_identifier#&#039;, &#039;SQM_&#039;||to_char(sysdate, &#039;HH24MISS&#039;))\n                , &#039;#child#&#039;, v_child) ;\n    -- create a trace file            \n    execute immediate v_dump_trace_cmd;\n    -- what is the name of the trace file?\n    select substr(regexp_substr(value, &#039;\/&#x5B;^\/]*$&#039;),2)\n    into   v_tracefile \n    from   sys.V_$diag_info\n    where name = &#039;Default Trace File&#039;;\n    -- return the query to find the relevant line \n    v_result := q&#039;&#x5B; select x.trcline from v$diag_trace_file_contents \n                match_recognize (\n                    partition by trace_filename\n                    order by line_number\n                    measures payload as trcline\n                    pattern (a b)\n                    define a as (payload like &#039;Final query after%&#039;)\n                    ) x \n                  where trace_filename = &#039;#TRACEFILENAME#&#039; ]&#039;;\n    return replace (v_result,&#039;#TRACEFILENAME#&#039;, v_tracefile);\n  else\n    v_result := replace(q&#039;&#x5B;select &#039;The SQL_ID #sql_id# was not found, please execute the statement first.&#039;]&#039;, &#039;#sql_id#&#039;, v_sql_id );\n    return v_result;    \n  end if;\nend;\n\/\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 4<\/em><\/strong><\/p>\n\n\n\n<p>As clarified in the <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-and-invoker-rights\/\" target=\"_blank\">previous post<\/a>, SQL macros are executed with definer rights. This is also how we want it to work in this case. The macro can be created by a user, who has all required privileges. In this case we need direct grants for V_$SQL and for V_$SQL_BIND_CAPTURE (used in DBMS_SQLDIAG, but this package is in turn invoker rights, so that we need a direct grant after all)<\/p>\n\n\n\n<p><strong><em>Line 10:<\/em><\/strong> Thanks, <a rel=\"noreferrer noopener\" href=\"https:\/\/twitter.com\/uschwinn\" target=\"_blank\">Ulrike Schwinn<\/a> for the tip to use <a rel=\"noreferrer noopener\" href=\"https:\/\/blogs.oracle.com\/coretec\/post\/get-sqlid-without-execution-really\" target=\"_blank\">the function dbms_sql_translator.sql_id to get the SQL_ID<\/a> of the statement.<\/p>\n\n\n\n<p><strong><em>Line 13:<\/em><\/strong> find out the child number<\/p>\n\n\n\n<p><strong><em>Lines 16-29:<\/em><\/strong> build the command for creating a CBO trace file and then execute it<\/p>\n\n\n\n<p><strong><em>Lines 31-34:<\/em><\/strong> find out the trace file name<\/p>\n\n\n\n<p><strong><em>Lines 36-45: <\/em><\/strong>prepare the query reading the content of the trace file and using row pattern matching to find the line following &#8220;Final Query&#8221;.<\/p>\n\n\n<script async src=\"\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js?client=ca-pub-2925154690547867\" crossorigin=\"anonymous\"><\/script><ins class=\"adsbygoogle\" style=\"display:block; text-align:center;\" data-ad-client=\"ca-pub-2925154690547867\" \ndata-ad-slot=\"2727403138\" \ndata-ad-layout=\"in-article\"\ndata-ad-format=\"fluid\"><\/ins>\n<script> \n(adsbygoogle = window.adsbygoogle || []).push({}); \n<\/script>\n\n\n\n<p>Let&#8217;s test it!<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; select *\n  2  from   definer_user.resolve_sqlmacro(\n  3          sys.odcivarchar2list(\n  4              q&#039;&#x5B;select ename\n  5                 ,      duration_years(hiredate, sysdate) as years\n  6                 ,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\n  7                 from   emp e]&#039;)) k;\n\nTRCLINE                                                                    \n---------------------------------------------------------------------------\nThe SQL_ID 0b8w4khcapnw6 was not found, please execute the statement first.\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 5<\/em><\/strong><\/p>\n\n\n\n<p>So far so good, now we run the SQL statement to get it hard parsed and then run the macro again:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; select ename\n  2  ,      duration_years(hiredate, sysdate) as years\n  3  ,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\n  4  from   emp e;\n\nENAME           YEARS  YEARS_Y2K\n---------- ---------- ----------\nSMITH              42         23\nALLEN              42         23\nWARD               42         23\n...\n\nSQL&gt; select *\n  2  from   definer_user.resolve_sqlmacro(\n  3          sys.odcivarchar2list(\n  4              q&#039;&#x5B;select ename\n  5                 ,      duration_years(hiredate, sysdate) as years\n  6                 ,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\n  7                 from   emp e]&#039;)) k;\n\nTRCLINE                                                                    \n---------------------------------------------------------------------------\nThe SQL_ID 0b8w4khcapnw6 was not found, please execute the statement first.\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 6<\/em><\/strong><\/p>\n\n\n\n<p>What?! The same message again? But the SQL_ID must be in the shared pool this time! <\/p>\n\n\n\n<p>It&#8217;s so easy to fall into this trap again and again! It is just how SQL macros are supposed to work: <\/p>\n\n\n\n<p>First run:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>the statement from listing 5 has been hard parsed<\/li>\n\n\n\n<li>the SQL Macro <em><strong>resolve_sqlmacro<\/strong><\/em> was executed but could not find the SQL_ID<\/li>\n\n\n\n<li>The query with the hard-coded message was constructed and returned to the main query<\/li>\n<\/ul>\n\n\n\n<p>Second run:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>we run the test SQL statement from the listing 2, now we should have the SQL_ID there <\/li>\n\n\n\n<li>we re-run the &#8220;resolving&#8221; SQL from listing 5 again<\/li>\n\n\n\n<li>this SQL has been run and hard parsed during first run &#8211; no hard parse this time<\/li>\n\n\n\n<li>the SQL Macro <em><strong>resolve_sqlmacro<\/strong><\/em> was <strong>NOT<\/strong> executed at all <\/li>\n<\/ul>\n\n\n\n<p>If we want to do really dynamic things, we have to work around this behavior (notice, I didn&#8217;t call it a problem or a limitation, because it is the way it works). And indeed, there is a brilliant solution that again can be found on <a rel=\"noreferrer noopener\" href=\"https:\/\/stewashton.wordpress.com\/2022\/01\/14\/sql-table-macros-11-just_pivot-for-21c-and-19c\/\" target=\"_blank\">Stew Ashtons&#8217;s Blog<\/a> (proposed by <a rel=\"noreferrer noopener\" href=\"https:\/\/twitter.com\/mentzel_iudith\" target=\"_blank\">Iudith Mentzel<\/a> as he mentions). The idea is to use <strong>dbms_utility.invalidate<\/strong> call to invalidate the macro and place it in the calling SQL statement. The SQL macro will run at parse time, then it will be invalidated at run time of the statement which is not a problem at all. But it invalidates all cursors using this macro and will force a hard parse!  Please refer <a rel=\"noreferrer noopener\" href=\"https:\/\/stewashton.wordpress.com\/2022\/01\/14\/sql-table-macros-11-just_pivot-for-21c-and-19c\/\" target=\"_blank\">Stew&#8217;s Blog<\/a> for further details and the implementation of the function INVALIDATE_OBJECT which I will use without description  here.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\ncreate or replace  function resolve_sqlmacro (P_query in sys.odcivarchar2list) \n  return varchar2 sql_macro is\n    v_result varchar2(4000);\n    v_tracefile varchar2(100) ;\n    v_dump_trace_cmd varchar2(500);\n    v_sql_id varchar2(20);\n    v_cur sys_refcursor;\n    v_child number;\nbegin\n  v_sql_id := dbms_sql_translator.sql_id(p_query(1));\n -- will not work for DML \n -- open v_cur for p_query (1);\n  select max(child_number) into v_child from sys.v_$sql where sql_id = v_sql_id;\n  \n  if v_child is not null then  \n    v_dump_trace_cmd := \n            replace(\n                replace(\n                     replace( q&#039;&#x5B;\n                        begin \n                            DBMS_SQLDIAG.DUMP_TRACE(   p_sql_id=&gt;&#039;#sql_id#&#039;\n                                                    ,  p_child_number=&gt; &#039;#child#&#039;\n                                                    ,  p_component=&gt;&#039;Compiler&#039;\n                                                    ,  p_file_id=&gt;&#039;#v_trace_identifier#&#039;);\n                        end;]&#039;,&#039;#sql_id#&#039;,v_sql_id)\n                    , &#039;#v_trace_identifier#&#039;, &#039;SQM_&#039;||to_char(sysdate, &#039;HH24MISS&#039;))\n                , &#039;#child#&#039;, v_child) ;\n    -- create a trace file            \n    execute immediate v_dump_trace_cmd;\n    -- what is the name of the trace file?\n    select substr(regexp_substr(value, &#039;\/&#x5B;^\/]*$&#039;),2)\n    into   v_tracefile \n    from   sys.V_$diag_info\n    where name = &#039;Default Trace File&#039;;\n    -- return the query to find the relevant line \n    v_result := q&#039;&#x5B; select x.trcline from v$diag_trace_file_contents \n                match_recognize (\n                    partition by trace_filename\n                    order by line_number\n                    measures payload as trcline\n                    pattern (a b)\n                    define a as (payload like &#039;Final query after%&#039;)\n                    ) x \n                  where trace_filename = &#039;#TRACEFILENAME#&#039; \n                  and  0 = (\n                            select invalidate_object(&#039;]&#039;\n                            ||$$PLSQL_UNIT_OWNER||&#039;&#039;&#039;,&#039;&#039;&#039;||$$PLSQL_UNIT||\n                            &#039;&#039;&#039; ) from dual)&#039;;\n    return replace (v_result,&#039;#TRACEFILENAME#&#039;, v_tracefile);\n  else\n    v_result := replace(q&#039;&#x5B;select &#039;The SQL_ID #sql_id# was not found, please execute the statement first.&#039; trcline\n                          where 0 = (\n                                select invalidate_object(&#039;]&#039;\n                                ||$$PLSQL_UNIT_OWNER||&#039;&#039;&#039;,&#039;&#039;&#039;||$$PLSQL_UNIT\n                                ||&#039;&#039;&#039; ) from dual)&#039;, &#039;#sql_id#&#039;, v_sql_id );\n    return v_result;    \n  end if;\nend;\n\/\n\nFunction RESOLVE_SQLMACRO compiled\n\nSQL&gt; select *\n  2  from   definer_user.resolve_sqlmacro(\n  3          sys.odcivarchar2list(\n  4              q&#039;&#x5B;select ename\n  5                 ,      duration_years(hiredate, sysdate) as years\n  6                 ,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\n  7                 from   emp e]&#039;)) k;\n\nTRCLINE                                                                    \n---------------------------------------------------------------------------\nThe SQL_ID 0b8w4khcapnw6 was not found, please execute the statement first.\n\nSQL&gt; select ename\n  2  ,      duration_years(hiredate, sysdate) as years\n  3  ,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\n  4  from   emp e;\n\nENAME           YEARS  YEARS_Y2K\n---------- ---------- ----------\nSMITH              42         23\nALLEN              42         23\n...\n\nSQL&gt; select *\n  2  from   definer_user.resolve_sqlmacro(\n  3          sys.odcivarchar2list(\n  4              q&#039;&#x5B;select ename\n  5                 ,      duration_years(hiredate, sysdate) as years\n  6                 ,      duration_years(date &#039;2000-01-01&#039;, sysdate) as years_y2k\n  7                 from   emp e]&#039;)) k;\n\nTRCLINE                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       \n-----------------------------------------------------------------------------------------------------------\nSELECT &quot;E&quot;.&quot;ENAME&quot; &quot;ENAME&quot;,FLOOR(MONTHS_BETWEEN(SYSDATE@!,&quot;E&quot;.&quot;HIREDATE&quot;)\/12)\n &quot;YEARS&quot;,FLOOR(MONTHS_BETWEEN(SYSDATE@!,TO_DATE(&#039; 2000-01-01 00:00:00&#039;, \n&#039;syyyy-mm-dd hh24:mi:ss&#039;))\/12) &quot;YEARS_Y2K&quot; FROM &quot;SCOTT&quot;.&quot;EMP&quot; &quot;E&quot;\n\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 7<\/em><\/strong><\/p>\n\n\n\n<p>Finally! Now we can see the query after expansion of all SQL macros.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Was it worth it?<\/h2>\n\n\n\n<p>Well, I think it can be interesting to check how the actual SQL statement will look like. And we could demonstrate how one can automate it. In this respect it was already worth it. But using a SQL macro for  this felt like overkill to me (passing parameters as collections, adding a function call to force a hard parse next time).  I would just use a pipelined function like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\ncreate or replace  function resolve_sqm (P_query in varchar2) \n  return sys.odcivarchar2list pipelined is\n    v_result varchar2(4000);\n    v_tracefile varchar2(100) ;\n    v_dump_trace_cmd varchar2(500);\n    v_sql_id varchar2(20);\n    v_cur sys_refcursor;\n    v_child number;\n    v_trcline varchar2(32000);\nbegin\n  v_sql_id := dbms_sql_translator.sql_id(p_query);\n -- will not work for DML \n -- open v_cur for p_query (1);\n  select max(child_number) into v_child from sys.v_$sql where sql_id = v_sql_id;\n  \n  if v_child is not null then  \n    v_dump_trace_cmd := \n            replace(\n                replace(\n                     replace( q&#039;&#x5B;\n                        begin \n                            DBMS_SQLDIAG.DUMP_TRACE(   p_sql_id=&gt;&#039;#sql_id#&#039;\n                                                    ,  p_child_number=&gt; &#039;#child#&#039;\n                                                    ,  p_component=&gt;&#039;Compiler&#039;\n                                                    ,  p_file_id=&gt;&#039;#v_trace_identifier#&#039;);\n                        end;]&#039;,&#039;#sql_id#&#039;,v_sql_id)\n                    , &#039;#v_trace_identifier#&#039;, &#039;SQM_&#039;||to_char(sysdate, &#039;HH24MISS&#039;))\n                , &#039;#child#&#039;, v_child) ;\n    dbms_output.put_line (v_dump_trace_cmd);            \n    -- create a trace file            \n    execute immediate v_dump_trace_cmd;\n    -- what is the name of the trace file?\n    select substr(regexp_substr(value, &#039;\/&#x5B;^\/]*$&#039;),2)\n    into   v_tracefile \n    from   sys.V_$diag_info\n    where name = &#039;Default Trace File&#039;;\n    -- return the query to find the relevant line \n    v_result := q&#039;&#x5B; select x.trcline from v$diag_trace_file_contents \n                match_recognize (\n                    partition by trace_filename\n                    order by line_number\n                    measures payload as trcline\n                    pattern (a b)\n                    define a as (payload like &#039;Final query after%&#039;)\n                    ) x \n                  where trace_filename = &#039;#TRACEFILENAME#&#039; \n                ]&#039;;\n    open v_cur for replace (v_result,&#039;#TRACEFILENAME#&#039;, v_tracefile);\n    loop\n      fetch v_cur into v_trcline;\n        exit when v_cur%notfound;\n        pipe row(v_trcline);\n    end loop;\n  else\n    pipe row (replace(q&#039;&#x5B;The SQL_ID #sql_id# was not found, please execute the statement first.]&#039;, &#039;#sql_id#&#039;, v_sql_id ));    \n  end if;\nend;\n\/\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 8<\/em><\/strong><\/p>\n\n\n\n<p>Having said that, there are still some points  open for me. <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>What else can we see in these trace files? <\/li>\n\n\n\n<li>Can it be useful for overall understanding how SQL macros work? <\/li>\n\n\n\n<li>What  about SQL macros in DML statements? (That was the reason why I refrained from parsing the input SQL and expect the SQL_ID to be there, but it&#8217;s not that simple)<\/li>\n<\/ul>\n\n\n\n<p>I plan to cover this in my next post.<\/p>\n\n\n<script async src=\"\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js?client=ca-pub-2925154690547867\" crossorigin=\"anonymous\"><\/script><ins class=\"adsbygoogle\" style=\"display:block; text-align:center;\" data-ad-client=\"ca-pub-2925154690547867\" \ndata-ad-slot=\"2727403138\" \ndata-ad-layout=\"in-article\"\ndata-ad-format=\"fluid\"><\/ins>\n<script> \n(adsbygoogle = window.adsbygoogle || []).push({}); \n<\/script>\n\n\n<div class=\"crp-list-container\"><h3 class=\"crp-list-title\">Related Posts<\/h3><ul class=\"crp-list\"><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/oracle-20c-sql-macros\/\" target=\"_blank\">Oracle 20c: SQL Macros<\/a><\/div><\/li><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-what-is-in-the-cbo-trace\/\" target=\"_blank\">SQL Macros &#8211; what is in the CBO trace?<\/a><\/div><\/li><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-changing-the-query-shape-using-parameters\/\" target=\"_blank\">SQL Macros \u2013 Changing The Query Shape Using Parameters?<\/a><\/div><\/li><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-and-invoker-rights\/\" target=\"_blank\">SQL-Macros and Invoker Rights<\/a><\/div><\/li><\/ul><\/div>","protected":false},"excerpt":{"rendered":"<p>In my very first post about SQL macros, I mentioned that for table macros, there is a simple way to see the SQL statement after macro expansion using dbms_utility.expand_sql_text. However, for scalar SQL macros, there is no such straightforward method. We can activate a CBO trace (also known as event 10053) and find the final [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1891,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[143,1,2,3,100],"tags":[51,103],"class_list":["post-1885","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-23ai","category-general","category-oracle","category-sql","category-sql-macros","tag-sql","tag-sql-macros"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1885","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/comments?post=1885"}],"version-history":[{"count":44,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1885\/revisions"}],"predecessor-version":[{"id":1936,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1885\/revisions\/1936"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/1891"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=1885"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=1885"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=1885"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}