{"id":1510,"date":"2020-10-03T23:50:49","date_gmt":"2020-10-03T21:50:49","guid":{"rendered":"https:\/\/blog.sqlora.com\/en\/?p=1510"},"modified":"2020-10-27T14:48:04","modified_gmt":"2020-10-27T12:48:04","slug":"sql-macros-in-oracle-19c","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/sql-macros-in-oracle-19c\/","title":{"rendered":"SQL macros in Oracle 19c"},"content":{"rendered":"\n<p>Now that I have <a rel=\"noreferrer noopener\" aria-label=\"created a patched Oracle database docker image (opens in a new tab)\" href=\"https:\/\/blog.sqlora.com\/en\/upgrading-a-19c-database-on-docker\/\" target=\"_blank\">created a patched Oracle database docker image<\/a> with Release Update 19.8 (19.8.0.0.DBRU:200714) to play with, I can finally start testing the backport of SQL macros! (<strong>UPDATE:<\/strong> As of now you can play with SQL macros on Autonomous Databases in Oracle Cloud (also free tier!) and on <a rel=\"noreferrer noopener\" aria-label=\"livesql.oracle.com (opens in a new tab)\" href=\"https:\/\/livesql.oracle.com\" target=\"_blank\">livesql.oracle.com<\/a>) <\/p>\n\n\n\n<p><a rel=\"noreferrer noopener\" aria-label=\"SQL Macros (opens in a new tab)\" href=\"https:\/\/blog.sqlora.com\/en\/category\/oracle\/sql\/sql-macros\/\" target=\"_blank\">SQL Macros<\/a> have been introduced in Oracle 20c. As of now, Oracle 20c is only available as preview version in Oracle Cloud. That&#8217;s why I was quite excited when <a rel=\"noreferrer noopener\" aria-label=\"Connor McDonald pointed out on Twitter that they were backported to 19c (opens in a new tab)\" href=\"https:\/\/twitter.com\/Mautro\/status\/1220931842401980416\" target=\"_blank\">Connor McDonald pointed out on Twitter that they were backported to 19c<\/a>. Let&#8217;s see if it works, and how.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>It is a <a rel=\"noreferrer noopener\" aria-label=\"bug 30324180 (opens in a new tab)\" href=\"https:\/\/support.oracle.com\/epmos\/faces\/DocumentDisplay?id=30324180.8\" target=\"_blank\">bug 30324180<\/a>, where the backport is mentioned. And the fix is first included in 19.6. So, strictly speaking, you do not need 19.8 that I&#8217;m working with &#8211; 19.6 should also be just fine. (<strong>UPDATE:<\/strong> 19.6 is mentioned in the bug document, but I was able to run the example from this post on an ADW database in the cloud with 19.5 right now)<\/p>\n\n\n\n<p>Let&#8217;s start with a basic example from my <a href=\"https:\/\/blog.sqlora.com\/en\/oracle-20c-sql-macros\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"previous post (opens in a new tab)\">previous post<\/a> &#8211; a scalar macro function JOB_DURATION:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; CREATE OR REPLACE FUNCTION job_duration RETURN VARCHAR2 \n  2  SQL_MACRO(SCALAR) \n  3  AS\n  4  BEGIN\n  5     RETURN q&#039;! FLOOR(MONTHS_BETWEEN (SYSDATE, hiredate)\/12) !&#039;;\n  6  END;\n  7  \/\n\nFunction JOB_DURATION compiled\n\nLINE\/COL  ERROR\n--------- -----------------------------------------------\n2\/10      PLS-00103: Encountered the symbol &quot;(&quot; \nwhen expecting one of the following:     ...\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 1: no scalar macros in 19c<\/em><\/strong><\/p>\n\n\n\n<p>Couldn&#8217;t compile&#8230; Why? The error message complains about the parenthesis around <strong><code>SCALAR<\/code><\/strong>, but not about the word <strong><code>SQL_MACRO<\/code><\/strong>. There is a good hint in the  description of the &#8220;backport&#8221; bug 30324180 to understand this. Scalar macros were intentionally <strong>not backported<\/strong>! So you only have table semantic macros (which are also the default in 20c) and it seems that you cannot explicitly specify the type. The good news here is that you don&#8217;t have to change anything when you migrate to 20c. So let&#8217;s focus on the table semantic functions then. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Get your data CSV formatted<\/h2>\n\n\n\n<p>As an example, I wanted to take something that would be useful in itself and at the same time can show the other capabilities of what can be achieved with table SQL macros. Since I work a lot as an ETL engineer, I am often faced with the task of exporting data as CSV. Not my favorite format, to be honest, especially because you lose a lot of metadata. But there are often project requirements you have no control over. And if you <strong>have to<\/strong> export data as CSV, it is very important that it is done consistently in the same way in larger projects. And that&#8217;s where SQL macros can be very helpful! Let&#8217;s see why and how. <\/p>\n\n\n\n<p>Our goal is to develop a <strong>generic<\/strong> function:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>which we can feed with arbitrary data stream<\/li><li>which handles incoming data in a consistent way according  to established conventions<\/li><li>in particular, the function should produce a header row with column names, format all date and timestamp fields according to ISO 8601 and enclose all text fields with quotation marks, whereby these must first be &#8220;escaped&#8221; in the text<\/li><li>which we can query from getting CSV-formatted output<\/li><\/ul>\n\n\n\n<p>For example having the view EMPV defined in previous posts, the invocation will be like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; quick-code: false; notranslate\" title=\"\">\nSQL&gt; select * from get_csv(empv);\n\nCSV_ROW                                                                                             \n----------------------------------------------------------------------------------------------------\n&quot;EMPVID&quot;,&quot;EMPNO&quot;,&quot;ENAME&quot;,&quot;JOBNO&quot;,&quot;MGR&quot;,&quot;HIREDATE&quot;,&quot;SAL&quot;,&quot;COMM&quot;,&quot;DEPTNO&quot;,&quot;VALID_FROM&quot;,&quot;VALID_TO&quot;\n2,7499,&quot;ALLEN&quot;,2,7698,1981-02-20,1600,300,30,1981-02-20,1989-12-31\n3,7521,&quot;WARD&quot;,2,7698,1981-02-22,1250,500,30,1981-02-22,1989-12-31\n4,7566,&quot;JONES&quot;,4,7839,1981-04-02,2975,,20,1981-04-02,1989-12-31\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 2: the desired usage and output<\/em><\/strong><\/p>\n\n\n\n<p>What then happens with this output, for example, persisting it as a file, is out of scope here.<\/p>\n\n\n\n<p>I wrote about the basics of table SQL macros in a couple of previous posts, for example <a rel=\"noreferrer noopener\" aria-label=\" (opens in a new tab)\" href=\"https:\/\/blog.sqlora.com\/en\/building-hash-keys-using-sql-macros-in-oracle-20c\/\" target=\"_blank\">about building hash keys with SQL macros<\/a>, so I&#8217;m not going to repeat all explanations but rather to show that things are working exactly the same in 19c.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncreate or replace function get_csv (p_input_data dbms_tf.table_t\n                                   ,p_delimiter varchar2 default &#039;,&#039;\n                                   ,p_quote varchar2 default &#039;&quot;&#039;\n                                   ,p_escape varchar2 default &#039;\\&#039;\n                                   ) return clob sql_macro as\nv_sql clob;\nv_name dbms_id;\nv_header clob;\nv_delimiter_rec varchar2(9):= &#039;||&#039;&#039;&#039;||p_delimiter||&#039;&#039;&#039;||&#039;;\nv_record clob;\nv_rec varchar2(300);\nbegin\n  for i in 1..p_input_data.column.count loop\n    v_name := p_input_data.column(i).description.name;\n    if p_input_data.column(i).description.type \n         in (dbms_tf.type_varchar2\n           , dbms_tf.type_char\n           , dbms_tf.type_clob) then \n      if p_quote is not null then \n          -- escape quotation characters in text fields\n          v_rec := &#039;replace(&#039;||v_name||&#039;,&#039;&#039;&#039;||p_quote||&#039;&#039;&#039;,&#039;&#039;&#039;||p_escape||p_quote||&#039;&#039;&#039;)&#039;;\n          -- enclose text in quotation marks\n          v_rec :=  &#039;&#039;&#039;&#039;||p_quote||&#039;&#039;&#039;||&#039;||v_rec||&#039;||&#039;&#039;&#039;||p_quote||&#039;&#039;&#039;&#039;;\n      else \n          v_rec := v_name;\n      end if;\n    elsif p_input_data.column(i).description.type = dbms_tf.type_number then\n      v_rec := &#039;to_char(&#039;||v_name||&#039;)&#039;;\n    elsif p_input_data.column(i).description.type = dbms_tf.type_date then\n      v_rec := &#039;to_char(&#039;||v_name||&#039;,&#039;&#039;YYYY-MM-DD&#039;&#039;)&#039;;\n    elsif p_input_data.column(i).description.type = dbms_tf.type_timestamp then\n      v_rec := &#039;to_char(&#039;||v_name||&#039;,&#039;&#039;YYYY-MM-DD&quot;T&quot;HH24:MI:SS.FF6&#039;&#039;)&#039;;      \n    end if;\n    v_header := v_header || p_delimiter || v_name;\n    v_record := v_record || v_delimiter_rec || v_rec;\n  end loop;\n  v_header := SUBSTR(v_header, LENGTH(p_delimiter) + 1);\n  v_record := SUBSTR(v_header, LENGTH(v_delimiter_rec) + 1);\n  v_sql := q&#039;&#x5B;\n     with d as (\n       select &#039;%PH_HEADER%&#039; as csv_row, 1 as row_order  from dual \n       union all \n       select %PH_RECORD% as csv_row , 2 as row_order from get_csv.p_input_data\n       ) \n     select csv_row from d order by row_order\n           ]&#039;;\n  v_sql := replace(replace (v_sql, &#039;%PH_RECORD%&#039;, v_record), &#039;%PH_HEADER%&#039; , v_header);\n  return v_sql;\nend;\n\/\n<\/pre><\/div>\n\n\n<p><em><strong>Listing 3: Implementation of SQL macro function GET_CSV<\/strong><\/em><\/p>\n\n\n\n<p> Let&#8217;s look at the implementation in more detail.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Line 1-5: Declare the function as sql_macro. If we want a generic function capable to work with arbitrary input data sets we need a parameter of type <code>DBMS_TF.TABLE_T.<\/code> This is the way we get metadata about the data set helping us to implement the required logic. As with any other PL\/SQL function, we can also declare &#8220;scalar&#8221; parameter when needed along with their default values  as well.<\/li><li>Line 13: Loop over the column collection we got in <code><strong>P_INPUT_DATA.<\/strong><\/code> This contains column names and also data types<\/li><li>Lines 15-33: Implement business requirements for handling different data types, e.g. escaping characters, quoting  texts, formatting dates, etc. These rules can be as simple or as complex as you need. Important is that you can establish them only once and expose through SQL macro function<\/li><li>Lines 34-38: We prepare the strings containing a select list for the column header and the data itself: in a loop and also some post processing<\/li><li>Lines 39-47: Now we are ready to build the result string that will be returned from the function. It should be a valid SQL query that will be substituted in  in FROM clause of the original query instead of a SQL macro function invocation. In our case this is a <code><strong>UNION ALL<\/strong><\/code> combining the header row and the actual formatted data. Ordering by artificial column row_order helps us to output header first. <strong>IMPORTANT!<\/strong> Note how we are using the parameter <code><strong>P_INPUT_DATA<\/strong><\/code> at line 43! It doesn&#8217;t make sense at first sight. But keep in mind, this reference will be substituted with the real table name or view or subquery, whatever you have used in original query as a parameter for the function. <strong>EVEN MORE IMPORTANT!<\/strong> This substitution seems only to take place upon returning the string from the function. If you were using the parameter  <code><strong>P_INPUT_DATA<\/strong><\/code> elsewhere in the function, for instance, to query some data from the table for some reason, this wouldn&#8217;t work. See an example in  my post about <a href=\"https:\/\/blog.sqlora.com\/en\/dynamic-pivot-with-sql-macros-in-oracle-20c\/\" target=\"_blank\" rel=\"noreferrer noopener\" aria-label=\"dynamic pivot (opens in a new tab)\">dynamic pivot<\/a>.<\/li><\/ul>\n\n\n\n<p>Now we can call the function GET_CSV and produce the desired output:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; select * from get_csv(empv);\n\nCSV_ROW                                                                                             \n----------------------------------------------------------------------------------------------------\n&quot;EMPVID&quot;,&quot;EMPNO&quot;,&quot;ENAME&quot;,&quot;JOBNO&quot;,&quot;MGR&quot;,&quot;HIREDATE&quot;,&quot;SAL&quot;,&quot;COMM&quot;,&quot;DEPTNO&quot;,&quot;VALID_FROM&quot;,&quot;VALID_TO&quot;\n2,7499,&quot;ALLEN&quot;,2,7698,1981-02-20,1600,300,30,1981-02-20,1989-12-31\n3,7521,&quot;WARD&quot;,2,7698,1981-02-22,1250,500,30,1981-02-22,1989-12-31\n4,7566,&quot;JONES&quot;,4,7839,1981-04-02,2975,,20,1981-04-02,1989-12-31\n5,7654,&quot;MARTIN&quot;,2,7698,1981-09-28,1250,1400,30,1981-09-28,1989-12-31\n6,7698,&quot;BLAKE&quot;,4,7839,1981-05-01,2850,,30,1981-05-01,1989-12-31\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 4: the function works as designed<\/em><\/strong><\/p>\n\n\n\n<p>If you have already learned a little about SQL macros, you would know, that rather a different query is executed at runtime. Our function GET_CSV will run only at parse time and return a piece of  SQL  the original query will select from. How to see what will actually be executed? In Oracle 19c, as well as in Oracle 20c, I have yet only found one way to do this: using <code><strong>DBMS_UTILITY.expand_sql_text<\/strong><\/code>. I have formatted the output a bit: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; DECLARE\n  2    l_clob CLOB;\n  3  BEGIN\n  4    DBMS_UTILITY.expand_sql_text (\n  5      input_sql_text  =&gt; q&#039;&#x5B;select * from get_csv (empv)]&#039;,\n  6      output_sql_text =&gt; l_clob  );\n  7    DBMS_OUTPUT.put_line(l_clob);\n  8  END;\n  9  \/\n\nPL\/SQL procedure successfully completed.\n\nSELECT &quot;A1&quot;.&quot;CSV_ROW&quot; &quot;CSV_ROW&quot; \nFROM  (\n  SELECT &quot;A4&quot;.&quot;CSV_ROW&quot; &quot;CSV_ROW&quot; \n  FROM  (\n     SELECT &quot;A5&quot;.&quot;CSV_ROW&quot; &quot;CSV_ROW&quot; \n     FROM  ( \n       (SELECT &#039;&quot;EMPVID&quot;,&quot;EMPNO&quot;,&quot;ENAME&quot;,&quot;JOBNO&quot;,&quot;MGR&quot;,&quot;HIREDATE&quot;,&quot;SAL&quot;,&quot;COMM&quot;,&quot;DEPTNO&quot;,&quot;VALID_FROM&quot;,&quot;VALID_TO&quot;&#039; &quot;CSV_ROW&quot;,1 &quot;ROW_ORDER&quot; \n        FROM &quot;SYS&quot;.&quot;DUAL&quot; &quot;A7&quot;) \n        UNION ALL  \n       (SELECT TO_CHAR(&quot;A6&quot;.&quot;EMPVID&quot;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;EMPNO&quot;)||&#039;,&#039;||\n               &#039;&quot;&#039;||REPLACE(&quot;A6&quot;.&quot;ENAME&quot;,&#039;&quot;&#039;,&#039;\\&quot;&#039;)||&#039;&quot;&#039;||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;JOBNO&quot;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;MGR&quot;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;HIREDATE&quot;,&#039;YYYY-MM-DD&#039;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;SAL&quot;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;COMM&quot;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;DEPTNO&quot;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;VALID_FROM&quot;,&#039;YYYY-MM-DD&#039;)||&#039;,&#039;||\n               TO_CHAR(&quot;A6&quot;.&quot;VALID_TO&quot;,&#039;YYYY-MM-DD&#039;) &quot;CSV_ROW&quot;\n               , 2 &quot;ROW_ORDER&quot; \n        FROM  (\n            SELECT &quot;A3&quot;.&quot;EMPVID&quot; &quot;EMPVID&quot;,&quot;A3&quot;.&quot;EMPNO&quot; &quot;EMPNO&quot;\n                    ,&quot;A3&quot;.&quot;ENAME&quot; &quot;ENAME&quot;,&quot;A3&quot;.&quot;JOBNO&quot; &quot;JOBNO&quot;\n                    ,&quot;A3&quot;.&quot;MGR&quot; &quot;MGR&quot;,&quot;A3&quot;.&quot;HIREDATE&quot; &quot;HIREDATE&quot;\n                    ,&quot;A3&quot;.&quot;SAL&quot; &quot;SAL&quot;,&quot;A3&quot;.&quot;COMM&quot; &quot;COMM&quot;\n                    ,&quot;A3&quot;.&quot;DEPTNO&quot; &quot;DEPTNO&quot;,&quot;A3&quot;.&quot;VALID_FROM&quot; &quot;VALID_FROM&quot;\n                    ,&quot;A3&quot;.&quot;VALID_TO&quot; &quot;VALID_TO&quot; \n            FROM &quot;ONFTEST&quot;.&quot;EMPV&quot; &quot;A3&quot;\n               ) &quot;A6&quot;\n        )\n     ) &quot;A5&quot; \n     ORDER BY &quot;A5&quot;.&quot;ROW_ORDER&quot;\n   ) &quot;A4&quot;\n) &quot;A1&quot;\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 5: what happens in the background<\/em><\/strong><\/p>\n\n\n\n<p>As you can see, Oracle does a lot of work in background rewriting the original query. Just by comparing the original and the actually executed query, the key advantage of SQL macros becomes obvious. We can implement complex business logic in a consistent way and provide a simple interface through a SQL macro function. This business logic is not encapsulated in a PL\/SQL function at run time (causing the context switch) but fully exposed to the database and query optimizer. The SQL macro function is not tied to particular table or view but can be generically used with different data sets, even subqueries. <\/p>\n\n\n\n<p>Speaking of subqueries, you cannot pass them directly as the following listing shows, but you can do it with named queries defined in WITH clause.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; quick-code: false; notranslate\" title=\"\">\nSQL&gt; select * from get_csv ((select empno, ename from empv));\n\nSQL Error: ORA-00913: too many values\n\nSQL&gt; \nSQL&gt; \nSQL&gt; with subq as (select empno, ename from empv)\n  2  select * from get_csv(subq);\n\nCSV_ROW                       \n------------------------------\n&quot;EMPNO&quot;,&quot;ENAME&quot;\n7499,&quot;ALLEN&quot;\n7521,&quot;WARD&quot;\n7566,&quot;JONES&quot;\n7654,&quot;MARTIN&quot;\n7698,&quot;BLAKE&quot;\n...\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 6: using with a subquery<\/em><\/strong><\/p>\n\n\n\n<p>Looks good, but I also came across something that looks like a bug to me. When using a subquery as an input for SQL macro and generating some fields directly in this subquery, it will throw ORA-62558, even for some supported  data types. In the following listing we can see no problem with the column <code><strong>HIRE_DATE<\/strong><\/code> coming from the table, but generating another date column using sysdate is not possible. Converting the generated date to string solves the problem though.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; -- this works\nSQL&gt; with subq as (select empno, ename, hiredate \n  2                from empv \n  3                fetch first 5 rows only)\n  4  select * from get_csv(subq);\n\nCSV_ROW                                           \n--------------------------------------------------\n&quot;EMPNO&quot;,&quot;ENAME&quot;,&quot;HIREDATE&quot;\n7654,&quot;MARTIN&quot;,1981-09-28\n7521,&quot;WARD&quot;,1981-02-22\n7566,&quot;JONES&quot;,1981-04-02\n7369,&quot;SMITH&quot;,1980-12-17\n7499,&quot;ALLEN&quot;,1981-02-20\n\n6 rows selected. \n\nSQL&gt; \nSQL&gt; -- doesn&#039;t work\nSQL&gt; with subq as (select empno, ename, hiredate, sysdate as extraction_dt \n  2                from empv\n  3                fetch first 5 rows only)\n  4  select * from get_csv(subq);\n\nError at Command Line : 235 Column : 15\nError report -\nSQL Error: ORA-62558: Unsupported data types (DATE) for polymorphic table function\n62558. 00000 -  &quot;Unsupported data types (%s) for polymorphic table function&quot;\n*Cause:    Non-scalar data types were used in polymorphic table function.\n           Only scalar data types are supported.\n*Action:   Use only scalar data types in polymorphic table function.\nSQL&gt; \nSQL&gt; -- works after casting to char datatype\nSQL&gt; with subq as (select empno, ename, hiredate\n  2                     , to_char(sysdate,&#039;yyyy-mm-dd&#039;) as extraction_dt \n  3                from empv\n  4                fetch first 5 rows only)\n  5  select * from get_csv(subq);\n\nCSV_ROW                                           \n--------------------------------------------------\n&quot;EMPNO&quot;,&quot;ENAME&quot;,&quot;HIREDATE&quot;,&quot;EXTRACTION_DT&quot;\n7654,&quot;MARTIN&quot;,1981-09-28,&quot;2020-10-03&quot;\n7521,&quot;WARD&quot;,1981-02-22,&quot;2020-10-03&quot;\n7566,&quot;JONES&quot;,1981-04-02,&quot;2020-10-03&quot;\n7369,&quot;SMITH&quot;,1980-12-17,&quot;2020-10-03&quot;\n7499,&quot;ALLEN&quot;,1981-02-20,&quot;2020-10-03&quot;\n\n6 rows selected. \n<\/pre><\/div>\n\n\n<p><strong><em>Listing 7: bug with subquery-generated columns<\/em><\/strong><\/p>\n\n\n\n<p>I could experience the same behavior with other data types like TIMESTAMP or INTERVAL and not only with SQL macros but also with polymorphic table functions as well. I think I should open an SR for this. But I don&#8217;t really think it is a show stopper for using SQL macros.<\/p>\n\n\n\n<p><strong>UPDATE:<\/strong> I&#8217;ve investigated the reasons for this behavior in the follow-up post  <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.sqlora.com\/en\/using-subqueries-with-ptf-or-sql-macros\/\" target=\"_blank\">Using Subqueries and Views with PTF or SQL macros<\/a>. After all, I&#8217;m not sure, whether it is a bug at all. You can better workaround  the problem by casting SYSDATE as DATE again.<\/p>\n\n\n\n<p>As you can probably guess, I have become a big fan of SQL macros over the last few months. Okay, they are to be taken with a certain amount of caution, but they bring clear advantages. All in all, I am very excited to be able to work with SQL macros in 19c now. After all, 19c will be the release that I will have to work with for a few more years at most of my customers.<\/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\/building-hash-keys-using-sql-macros-in-oracle-20c\/\" target=\"_blank\">Building Hash Keys using SQL Macros in Oracle 20c<\/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\/comparing-rows-using-a-sql-macro\/\" target=\"_blank\">Comparing Rows using a SQL Macro<\/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\/parameterized-views-in-oracle-no-problem-with-sql-macros\/\" target=\"_blank\">Parameterized Views in Oracle? No problem! With 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\/upgrading-a-19c-database-on-docker\/\" target=\"_blank\">Upgrading a 19c database on docker<\/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\/using-subqueries-with-ptf-or-sql-macros\/\" target=\"_blank\">Using Subqueries and Views with PTF or SQL macros<\/a><\/div><\/li><\/ul><\/div>","protected":false},"excerpt":{"rendered":"<p>SQL macros have been backported to 19c! This blog post shows an example of using a table SQL macro to produce CSV output on arbitrary data set.<\/p>\n","protected":false},"author":1,"featured_media":1545,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,2,3,100],"tags":[109,103],"class_list":["post-1510","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-general","category-oracle","category-sql","category-sql-macros","tag-19c","tag-sql-macros"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1510","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=1510"}],"version-history":[{"count":36,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1510\/revisions"}],"predecessor-version":[{"id":1597,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1510\/revisions\/1597"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/1545"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=1510"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=1510"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=1510"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}