{"id":2248,"date":"2024-08-08T07:54:24","date_gmt":"2024-08-08T05:54:24","guid":{"rendered":"https:\/\/blog.sqlora.com\/en\/?p=2248"},"modified":"2024-08-12T08:40:33","modified_gmt":"2024-08-12T06:40:33","slug":"sql-macros-and-the-with-clause","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/sql-macros-and-the-with-clause\/","title":{"rendered":"SQL macros and the WITH clause"},"content":{"rendered":"\n<p>SQL macros and WITH clause are not known to be great friends: you cannot call a SQL macro in a WITH clause and if you want to define a table macro returning a query containing a WITH subquery(ies), then you won&#8217;t be able to use scalar parameters in this subquery. In the <a href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-changing-the-query-shape-using-parameters\/\" target=\"_blank\" rel=\"noreferrer noopener\">previous post<\/a> I tried to explain another restriction from the technical perspective looking at the SQL macro expansion trace information. In this post I will try to understand the WITH clause related limitations using the same approach.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Let&#8217;s first create a simple table SQL macro and show how to obtain SQM expansion trace for it. I&#8217;m using a pipelined function <strong><em>sqm_trace_info<\/em><\/strong> introduced in the <a href=\"https:\/\/blog.sqlora.com\/en\/sql-macros-changing-the-query-shape-using-parameters\/\" target=\"_blank\" rel=\"noreferrer noopener\">previous post<\/a> for this.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; highlight: [32,33,34]; title: ; notranslate\" title=\"\">\nSQL&gt; create or replace function my_emp(p_ename in varchar2) \n  2  return varchar2 sql_macro is\n  3  begin\n  4    return &#039;select * from emp where ename = p_ename&#039;;\n  5  end;\n  6  \/\n\nFunction MY_EMP compiled\n\nSQL&gt; \nSQL&gt; select * from sqm_trace_info(q&#039;&#x5B;select * from my_emp(&#039;KING&#039;)]&#039;);\n\nCOLUMN_VALUE                                                                                                                                                                                            \n---------------------------------------------------------------\nqksptfSQM_GetTxt(): Anonymous Block\n===================================\n                                                               \ndeclare                                                         \n                                                              \nbegin                                                           \n:macro_text := &quot;MY_EMP&quot;(:3 );                                          \nend;  \n                                                          \nqksptfSQM_GetTxt(): Macro Text\n==============================\n\nselect * from emp where ename = p_ename\n\nqksptfSQM_Template(): Template Text\n===================================\n\nselect &quot;SYS__$&quot;.* \nfrom (select NULL &quot;P_ENAME&quot; from SYS.DUAL) &quot;MY_EMP&quot;,\n     lateral(select * from emp where ename = p_ename) &quot;SYS__$&quot;\n\nSQL&gt; select empno from my_emp(&#039;KING&#039;);\n\n     EMPNO\n----------\n      7839\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 1: Simple table SQM with a scalar parameter<\/em><\/strong><\/p>\n\n\n\n<p>Note that it was not mandatory to prefix the parameter name with SQL macro function name in the result string. The lines 32-34 show the template query. All scalar parameters are gathered in one subquery selecting from dual. This subquery has the same name as our SQM function. The subquery in the LATERAL clause is actually what we return from SQM.<\/p>\n\n\n\n<p>Now let&#8217;s change the SQM and introduce a WITH clause in the returned query, so that a scalar parameter is referenced inside this subquery.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; create or replace function my_emp_with(p_ename in varchar2) \n  2  return varchar2 sql_macro is\n  3  begin\n  4    return &#039;with data as (select * from emp where ename = p_ename) \n  5            select * from data&#039;;\n  6  end;\n  7  \/\n\nFunction MY_EMP_WITH compiled\n\nSQL&gt; \nSQL&gt; \nSQL&gt; select * from sqm_trace_info(q&#039;&#x5B;select * from my_emp_with(&#039;KING&#039;)]&#039;);\n\nError starting at line : 10 in command -\nselect * from sqm_trace_info(q&#039;&#x5B;select * from my_emp_with(&#039;KING&#039;)]&#039;)\nError at Command Line : 10 Column : 15\nError report -\nSQL Error: ORA-00904: &quot;P_ENAME&quot;: invalid identifier\nORA-06512: at &quot;ONFTEST.SQM_TRACE_INFO&quot;, line 14\n00904. 00000 -  &quot;%s: invalid identifier&quot;\n\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 2: Putting scalar parameter in a WITH clause fails<\/em><\/strong><\/p>\n\n\n\n<p>The parsing fails with ORA-00904. We cannot get SQM expansion trace output in this case. But we can imagine how the template query could look like looking at Listing 1. Just substitute the query in the LATERAL clause with the actual one. Nothing else has changed.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; select &quot;SYS__$&quot;.* \n  2  from (select NULL &quot;P_ENAME&quot; from SYS.DUAL) &quot;MY_EMP_WITH&quot;,\n  3       lateral(with data as (select * from emp where ename = p_ename) \n  4               select * from data) &quot;SYS__$&quot;;\n\nError starting at line : 1 in command -\nselect &quot;SYS__$&quot;.* \nfrom (select NULL &quot;P_ENAME&quot; from SYS.DUAL) &quot;MY_EMP_WITH&quot;,\n     lateral(with data as (select * from emp where ename = p_ename) \n             select * from data) &quot;SYS__$&quot;\nError at Command Line : 3 Column : 60\nError report -\nSQL Error: ORA-00904: &quot;P_ENAME&quot;: invalid identifier\n00904. 00000 -  &quot;%s: invalid identifier&quot;\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 3: Inspect the assumed template query <\/em><\/strong><\/p>\n\n\n\n<p>Now, we can see why it doesn&#8217;t work. It is just the question of the scope in SQL and exposed (implicit and explicit) table names. The column P_ENAME coming from the subquery &#8220;MY_EMP_WITH&#8221; can be referenced in the main query of the LATERAL clause (<strong><em>select * from data<\/em><\/strong>), but it is not reachable from its WITH clause over two levels of nesting.<\/p>\n\n\n\n<p>Quite odd, if we prefix the scalar parameter with the function name, we will observe another error.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; create or replace function my_emp_with(p_ename in varchar2) \n  2  return varchar2 sql_macro is\n  3  begin\n  4    return &#039;with data as (select * from emp where ename = my_emp_with.p_ename) \n  5            select * from data&#039;;\n  6  end;\n  7  \/\n\nFunction MY_EMP_WITH compiled\n\nSQL&gt; \nSQL&gt; select * from sqm_trace_info(q&#039;&#x5B;select * from my_emp_with(&#039;KING&#039;)]&#039;);\n\nError starting at line : 9 in command -\nselect * from sqm_trace_info(q&#039;&#x5B;select * from my_emp_with(&#039;KING&#039;)]&#039;)\nError at Command Line : 9 Column : 15\nError report -\nSQL Error: ORA-06553: PLS-306: wrong number or types of arguments in call to &#039;MY_EMP_WITH&#039;\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 4. With prefixed scalar parameters we got ORA-06553<\/em><\/strong><\/p>\n\n\n\n<p>This is because the parser cannot see the subquery named as &#8216;MY_EMP_WITH&#8217; out of the WITH clause. But it is aware of the existing function with this name (our SQL macro). And it tries to interpret the reference as a call of it. <\/p>\n\n\n\n<p>Once again, it is <strong>allowed <\/strong>to define a table SQM that returns a query containing a WITH clause. However, this WITH subquery cannot contain any scalar parameters.<\/p>\n\n\n\n<p>By the way, there is a workaround suggested by Stew Ashton using a table parameter DBMS_TF.TABLE_T, you can find it <a href=\"https:\/\/paulzipblog.wordpress.com\/2022\/11\/08\/sql-macros-with-clauses-prohibited\/comment-page-1\/\" target=\"_blank\" rel=\"noreferrer noopener\">here.<\/a><\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">No macros inside a WITH clause<\/h2>\n\n\n\n<p>In contrast to the upper restriction, the next one is documented, at least it has a dedicated error message: <strong>ORA-64630: unsupported use of SQL macro: use of SQL macro inside WITH clause is not supported<\/strong><\/p>\n\n\n\n<p>It is not easy to understand why it shouldn&#8217;t be possible. At least for table macros, I think it is a consequence of the fact, that you are allowed to return a query with a WITH clause as a result string. The final query would have a nested WITH clause and end up with <strong>ORA-32034: unsupported use of WITH clause<\/strong>. In case you don&#8217;t return a WITH clause from your SQM, it works just fine.<\/p>\n\n\n\n<p>Another reason could be that using table parameters leads to a template query like in Listing 5. Every table parameter becomes a WITH subquery.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\ncreate or replace function sqm_tab_noop (t dbms_tf.table_t) return varchar2 sql_macro\nis\nbegin\n  return &#039;select * from t&#039;;\nend;\n\/\n\nselect * from sqm_trace_info(&#039;select * from sqm_tab_noop (dual)&#039;);\n\n...\n\nqksptfSQM_Template(): Template Text\n===================================\n\nwith  T as (select  \/*+ INLINE *\/ * from &quot;SYS&quot;.&quot;DUAL&quot;)\nselect &quot;SYS__$&quot;.* \nfrom (select * from t) &quot;SYS__$&quot; );\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 5: Using table parameters<\/em><\/strong><\/p>\n\n\n\n<p>So, if there is a need to combine this templating query with the main query which is calling this macro, then again, we will have two nested WITH clauses.<\/p>\n\n\n\n<p>In my eyes, not being able to call a SQL macro in the WITH subquery, not even a scalar SQM,  is a really critical limitation. WITH clauses (CTE) are really a very good way of making complex SQL more readable. This is also one of the primary goals of the SQL macros. Unfortunately, they prevent each other in this case. I really hope that this restriction will soon be removed.<\/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\/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><\/ul><\/div>","protected":false},"excerpt":{"rendered":"<p>SQL macros and WITH clause are not known to be great friends: you cannot call a SQL macro in a WITH clause and if you want to define a table macro returning a query containing a WITH subquery(ies), then you won&#8217;t be able to use scalar parameters in this subquery. In the previous post I [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":2227,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,100],"tags":[51,103],"class_list":["post-2248","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sql","category-sql-macros","tag-sql","tag-sql-macros"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/2248","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=2248"}],"version-history":[{"count":16,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/2248\/revisions"}],"predecessor-version":[{"id":2286,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/2248\/revisions\/2286"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/2227"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=2248"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=2248"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=2248"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}