{"id":1012,"date":"2018-12-24T15:47:51","date_gmt":"2018-12-24T13:47:51","guid":{"rendered":"http:\/\/blog.sqlora.com\/en\/?p=1012"},"modified":"2018-12-24T22:38:27","modified_gmt":"2018-12-24T20:38:27","slug":"polymorphic-table-functions-ptf-part-4-table-semantic-ptf","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-ptf-part-4-table-semantic-ptf\/","title":{"rendered":"Polymorphic Table Functions (PTF), Part 4 &#8211; Table Semantic PTF"},"content":{"rendered":"<p>In the first three parts of the series we have seen how a PTF basically works, but we have focused on row-semantic PTF only. In this part we&#8217;ll look at the table semantic PTF. As a reminder, the main difference to row-semantic PTF is that it is not enough to look at the currently processed row to produce the result. With table semantic PTF we often also need some summarized state from the previously processed rows. They are useful to implement user defined aggregate or window functions. Let&#8217;s first try to implement a very basic example of table semantic PTF and learn more theory as we go. <!--more--><\/p>\n<p>What will be our task? In the <a href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-ptf-part-2-more-basics-with-some-deep-dive\/\" rel=\"noopener\" target=\"_blank\">second part<\/a> we have previously developed a PTF to concatenate fields into a CSV-list. Let&#8217;s take this new column and summarize its length. Again, it&#8217;s very basic and doesn&#8217;t actually require any PTF, it can be simply done with SQL. We are doing it this way just to show how table semantic PTF works. <\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nCREATE OR REPLACE PACKAGE  sumlen_package AS\r\n\r\nFUNCTION describe ( tab      IN OUT dbms_tf.table_t\r\n                  , cols2sum IN     dbms_tf.columns_t ) \r\n         RETURN dbms_tf.describe_t ;\r\n         \r\nPROCEDURE fetch_rows;\r\n\r\nEND sumlen_package; \r\n\/\r\n\r\nCREATE OR REPLACE FUNCTION sumlen_ptf (tab TABLE, cols2sum COLUMNS )\r\n    RETURN TABLE PIPELINED TABLE POLYMORPHIC USING sumlen_package;\r\n\/\r\n\r\n<\/pre>\n<p><em>Listing 1: Package specification<\/em><\/p>\n<p>There are no differences in package specification to what we&#8217;ve seen so far. We&#8217;ll be using the parameter <em><strong>cols2sum<\/strong><\/em> to pass the column(s) whose length we want to summarize. The definition of the PTF itself is where we tell the database about the semantic of the function. It is the word <strong>TABLE<\/strong> before the word <strong>POLYMORPHIC<\/strong>.<\/p>\n<p><strong>DESCRIBE<\/strong><\/p>\n<p>After three previous parts of this series we now have some experience as PTF developer and can guess how the implementation of DESCRIBE could look like (see Listing 2). But it is important to note that we are making some sort of a shortcut here. Our implementation of DESCRIBE assumes the list of columns can be passed with parameter <strong>cols2sum<\/strong>, if we wanted to aggregate multiple columns at once. That means, we cannot just return one hard coded new column. We have to define them in a loop, because we just don&#8217;t know in advance how many there will be. But this shortcut is just okay for now. I will show the more generic case in one of the next posts. <\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nCREATE OR REPLACE PACKAGE BODY sumlen_package AS\r\n\r\nFUNCTION describe (tab IN OUT dbms_tf.table_t\r\n                 , cols2sum IN dbms_tf.columns_t ) \r\n         RETURN dbms_tf.describe_t IS\r\nBEGIN\r\n  FOR I IN 1 .. tab.COLUMN.COUNT LOOP  \r\n     IF tab.COLUMN(i).description.name MEMBER OF cols2sum THEN\r\n          tab.column(i).for_read := true;\r\n     END IF;\r\n  END LOOP;\r\n \r\n  RETURN dbms_tf.describe_t( \r\n           new_columns =&gt; \r\n               dbms_tf.columns_new_t( \r\n                  1 =&gt; dbms_tf.column_metadata_t(\r\n                           name =&gt; 'SUM_LEN',  \r\n                           TYPE =&gt; dbms_tf.type_number)));\r\n     \r\nEND; \r\n...\r\n\r\n<\/pre>\n<p><em>Listing 2: Implementation of the DESCRIBE method<\/em><\/p>\n<p><strong>FETCH_ROWS<\/strong><\/p>\n<p>What is to consider while implementing FETCH_ROWS? As already mentioned, you have no control over how often FETCH_ROWS is executed. The processing is done in chunks; one chunk is not larger than 1024 rows (not documented, but what I&#8217;ve seen so far). It makes sense because all the collections are kept in the PGA.<\/p>\n<p>So if we have to aggregate something, we can only build a subtotal based on our actual chunk and then we need a mechanism to pass these subtotals from one execution to the next. There is a simple <strong>XSTORE<\/strong> interface for storing key-value pairs during the execution which we can use for our example. We are using the variable <strong>v_len<\/strong> to get and set the current sum (line 10 and 16) at the beginning and the end of the current execution of <strong>FETCH_ROWS.<\/strong> <\/p>\n<p>Another possibility to handle the execution state using your own structures and the special execution ID returned by <strong>DBMS_TF.GET_XID<\/strong> function will be considered in later posts.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nPROCEDURE fetch_rows IS\r\n  rowset dbms_tf.row_set_t; \r\n  colcnt PLS_INTEGER;\r\n  rowcnt PLS_INTEGER;\r\n  len_curr_col  dbms_tf.tab_number_t;   \r\n  v_len pls_integer;\r\n  v_currlen pls_integer := 0;\r\nBEGIN \r\n    dbms_tf.get_row_set(rowset, rowcnt, colcnt);\r\n    dbms_tf.xstore_get('len', v_len);\r\n    v_currlen := nvl(v_len, 0);\r\n    FOR r IN 1..rowcnt LOOP\r\n      v_currlen := v_currlen + length (rowset(1).tab_varchar2(r));\r\n      len_curr_col(r) := v_currlen ;\r\n    END LOOP;\r\n    dbms_tf.xstore_set('len', v_len+v_currlen);\r\n    dbms_tf.put_col(1, len_curr_col);         \r\nEND fetch_rows;\r\n\r\nEND sumlen_package; \r\n\/\r\n\r\n<\/pre>\n<p><em>Listing 3: Implementation of the FETCH_ROWS method<\/em><\/p>\n<p>We are ready to call the function (see Listing 4). The first attempt runs against a documented restriction: it is not allowed to nest polymorphic functions. But it is very easy to workaround this using a WITH clause.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nSQL&gt; -- Nesting PTF doesn't work\r\nSELECT  * \r\nFROM    sumlen_ptf(my_ptf(scott.emp, COLUMNS(deptno))\r\n                 , COLUMNS(agg_col))\r\n\r\nORA-62569: nested polymorphic table function is disallowed\r\n\r\nSQL&gt; \r\nSQL&gt; -- But it works via CTE  (WITH clause)\r\nSQL&gt; WITH agg as (SELECT * FROM my_ptf(scott.emp, COLUMNS(deptno)))\r\n  2  SELECT * \r\n  3  FROM   sumlen_ptf(agg , COLUMNS(agg_col));\r\n\r\n    DEPTNO AGG_COL                      ...     SUM_LEN\r\n---------- -----------------------------...  ----------\r\n        20 7369;&quot;SMITH&quot;;&quot;CLERK&quot;;7902;&quot;17...          52\r\n        30 7499;&quot;ALLEN&quot;;&quot;SALESMAN&quot;;7698;...         111\r\n        30 7521;&quot;WARD&quot;;&quot;SALESMAN&quot;;7698;&quot;...         169\r\n        20 7566;&quot;JONES&quot;;&quot;MANAGER&quot;;7839;&quot;...         224\r\n        30 7654;&quot;MARTIN&quot;;&quot;SALESMAN&quot;;7698...         285\r\n        30 7698;&quot;BLAKE&quot;;&quot;MANAGER&quot;;7839;&quot;...         340\r\n        10 7782;&quot;CLARK&quot;;&quot;MANAGER&quot;;7839;&quot;...         395\r\n        20 7788;&quot;SCOTT&quot;;&quot;ANALYST&quot;;7566;&quot;...         450\r\n        10 7839;&quot;KING&quot;;&quot;PRESIDENT&quot;;;&quot;17....         502\r\n        30 7844;&quot;TURNER&quot;;&quot;SALESMAN&quot;;7698...         560\r\n        20 7876;&quot;ADAMS&quot;;&quot;CLERK&quot;;7788;&quot;23...         613\r\n        30 7900;&quot;JAMES&quot;;&quot;CLERK&quot;;7698;&quot;03...         665\r\n        20 7902;&quot;FORD&quot;;&quot;ANALYST&quot;;7566;&quot;0...         719\r\n        10 7934;&quot;MILLER&quot;;&quot;CLERK&quot;;7782;&quot;2...         773\r\n\r\n14 rows selected. \r\n\r\n<\/pre>\n<p><em>Listing 4: Calling the PTF <\/em><\/p>\n<p><strong>Partitioning and Ordering <\/strong><\/p>\n<p>With table semantic PTF the incoming data can be optionally partitioned and\/or ordered like in Listing 5. Note that this is not allowed for row semantic PTF. There are few limitations, however. You cannot simply use comma separated column lists, but need to enclose them in parenthesis. And it seems not to be possible to use any SQL expressions as in corresponding clauses of analytical functions, just simple column names. <\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nSQL&gt; SELECT  deptno, empno, ename, sum_len \r\n  2  FROM    sumlen_ptf (scott.emp PARTITION BY deptno \r\n  3                                ORDER BY empno\r\n  4                    , COLUMNS(ename));\r\n\r\n    DEPTNO      EMPNO ENAME         SUM_LEN\r\n---------- ---------- ---------- ----------\r\n        10       7782 CLARK               5\r\n        10       7839 KING                9\r\n        10       7934 MILLER             15\r\n        20       7369 SMITH               5\r\n        20       7566 JONES              10\r\n        20       7788 SCOTT              15\r\n        20       7876 ADAMS              20\r\n        20       7902 FORD               24\r\n        30       7499 ALLEN               5\r\n        30       7521 WARD                9\r\n        30       7654 MARTIN             15\r\n        30       7698 BLAKE              20\r\n        30       7844 TURNER             26\r\n        30       7900 JAMES              31\r\n\r\n14 rows selected. \r\n\r\nSQL&gt; \r\nSQL&gt; -- You need parenthesis for column lists in partition by or order by\r\nSQL&gt; \r\nSQL&gt; SELECT  deptno, empno, ename, sum_len \r\n  2  FROM    sumlen_ptf (scott.emp PARTITION BY deptno, mgr\r\n  3                                ORDER BY empno\r\n  4                    , COLUMNS(ename));\r\n\r\nORA-62555: Invalid use of PARTITION BY or ORDER BY clause.\r\n\r\nSQL&gt; \r\nSQL&gt; -- but this works\r\nSQL&gt; SELECT  deptno, mgr, empno, ename, sum_len \r\n  2  FROM    sumlen_ptf (scott.emp PARTITION BY (deptno, mgr)\r\n  3                                ORDER BY empno\r\n  4                    , COLUMNS(ename));\r\n\r\n    DEPTNO        MGR      EMPNO ENAME         SUM_LEN\r\n---------- ---------- ---------- ---------- ----------\r\n        10       7782       7934 MILLER              6\r\n        10       7839       7782 CLARK               5\r\n        10                  7839 KING                4\r\n        20       7566       7788 SCOTT               5\r\n        20       7566       7902 FORD                9\r\n        20       7788       7876 ADAMS               5\r\n        20       7839       7566 JONES               5\r\n        20       7902       7369 SMITH               5\r\n        30       7698       7499 ALLEN               5\r\n        30       7698       7521 WARD                9\r\n        30       7698       7654 MARTIN             15\r\n        30       7698       7844 TURNER             21\r\n        30       7698       7900 JAMES              26\r\n        30       7839       7698 BLAKE               5\r\n\r\n14 rows selected. \r\n\r\nSQL&gt; \r\nSQL&gt; -- No expressions possible \r\nSQL&gt; \r\nSQL&gt; SELECT  deptno, empno, ename, sum_len \r\n  2  FROM    sumlen_ptf (scott.emp PARTITION BY SUBSTR(deptno,1,1)\r\n  3                                ORDER BY empno\r\n  4                    , COLUMNS(ename));\r\n\r\nORA-00907: missing right parenthesis\r\n\r\n<\/pre>\n<p><em>Listing 5: Partitioning  and ordering the  incoming data<\/em><\/p>\n<p>Note that if you partition the input, the FETCH_ROWS will be executed at least once per partition. In other words, you are always dealing with rows from one partition inside the FETCH_ROWS. But does this mean you don&#8217;t even need to know, whether or how the input was partitioned on PTF invocation? For the simplest case like our example this seems to be true. But in general I&#8217;m rather inconvenient with the fact that, inside a PTF, you don&#8217;t know what columns where referenced in a PARTITION BY or ORDER BY clause. For me, this information would be very helpful in some use cases. <\/p>\n<p><strong>Parallel execution<\/strong><\/p>\n<p>We haven&#8217;t looked at the parallel execution yet. Of course, SQL queries containing a polymorphic table function can be executed in parallel. For row semantic PTF the database can distribute the rows between the parallel slaves without any restrictions. On the other hand, if data is partitioned, as can be the case with table semantic functions, all rows of the same partition have to be processed  by the same parallel slave. It is due to the execution states that are stored and used by consequent executions of FETCH_ROWS: we don&#8217;t want the different partition-specific states are interchanged with each other. <\/p>\n<p>This also means, if we don&#8217;t partition the input of the table semantic PTF, we will actually  be having a serialization point in the query.<\/p>\n<p><strong>PASS_THROUGH columns<\/strong><\/p>\n<p>Do PASS_THROUGH columns behave something different with table semantic PTF? According to the documentation they should. That&#8217;s what we can read:<\/p>\n<blockquote><p>\nAll columns in the Row Semantics PTF are marked as pass-through by default. For Table Semantics PTF, the default value for pass-through is set to false. For the Table Semantics PTF, the partitioning columns are always pass-through and this cannot be changed by the DESCRIBE function.\n<\/p><\/blockquote>\n<p>But actually I couldn&#8217;t find any differences. I don&#8217;t know, whether it is a documentation or implementation bug, but it simply doesn&#8217;t work as described for me. First, we have already seen in Listing 2 that all columns were passed through, although we didn&#8217;t set this explicitly.  Let&#8217;s modify the function DESCRIBE so that we set PASS_THROUGH to FALSE for all columns :<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nFUNCTION describe (tab IN OUT dbms_tf.table_t\r\n                 , cols2sum IN dbms_tf.columns_t ) \r\n         RETURN dbms_tf.describe_t IS\r\nBEGIN\r\n  FOR I IN 1 .. tab.COLUMN.COUNT LOOP  \r\n     IF tab.COLUMN(i).description.name MEMBER OF cols2sum THEN\r\n          tab.column(i).for_read := true;\r\n     END IF;\r\n     tab.column(i).pass_through := false;\r\n  END LOOP;\r\n   \r\n  RETURN dbms_tf.describe_t( \r\n           new_columns =&gt; \r\n               dbms_tf.columns_new_t( \r\n                  1 =&gt; dbms_tf.column_metadata_t(\r\n                           name =&gt; 'SUM_LEN',  \r\n                           TYPE =&gt; dbms_tf.type_number)));\r\nEND; \r\n\r\n...\r\n\r\nSQL&gt; SELECT  *\r\n  2  FROM    sumlen_ptf (scott.emp PARTITION BY deptno \r\n  3                                ORDER BY empno\r\n  4                    , COLUMNS(ename));\r\n\r\n   SUM_LEN\r\n----------\r\n         5\r\n         9\r\n        15\r\n         5\r\n        10\r\n        15\r\n        20\r\n        24\r\n         5\r\n         9\r\n        15\r\n        20\r\n        26\r\n        31\r\n\r\n14 rows selected. \r\n\r\n<\/pre>\n<p><em>Listing 6: Partitioning  and ordering the  incoming data<\/em><\/p>\n<p>So we could set all columns including the partitioning column DEPTNO to be no PASS_THROUGH. Doesn&#8217;t make very much sense for me.<\/p>\n<p><strong>Aggregate functions<\/strong><\/p>\n<p>The PTF from our example behaves like analytical or window function. But could we build the real aggregate function? The one, that returns only one value (row) per table\/partition? Let&#8217;s try to emulate the simple function SUM() as an example. The idea is to use the row replication to remove all rows but one, which will be used to output the aggregation result.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\n-- What we want to emulate\r\nSQL&gt; select sum(sal) from scott.emp;\r\n\r\n  SUM(SAL)\r\n----------\r\n     29025\r\n\r\nCREATE OR REPLACE PACKAGE emulate_sum_pkg AS   \r\n \r\nFUNCTION describe(tab  IN OUT DBMS_TF.TABLE_T,   \r\n                   agg_col IN DBMS_TF.COLUMNS_T)   \r\nRETURN DBMS_TF.DESCRIBE_T;   \r\n   \r\nPROCEDURE fetch_rows; \r\n \r\nEND emulate_sum_pkg; \r\n\/ \r\nPackage created.\r\n\r\nCREATE OR REPLACE PACKAGE BODY emulate_sum_pkg AS   \r\nFUNCTION describe(tab  IN OUT DBMS_TF.TABLE_T,   \r\n                  agg_col IN DBMS_TF.COLUMNS_T)   \r\nRETURN DBMS_TF.DESCRIBE_T    \r\nAS   \r\nBEGIN   \r\n-- No pass-through columns \r\n  FOR I IN 1 .. tab.COLUMN.COUNT LOOP   \r\n      tab.column(i).pass_through := false;  \r\n      IF tab.COLUMN(i).description.name MEMBER OF agg_col THEN \r\n         TAB.COLUMN(i).for_read := TRUE;   \r\n      END IF; \r\n  END LOOP;   \r\n  RETURN DBMS_TF.DESCRIBE_T(new_columns =&gt;  \r\n            dbms_tf.columns_new_t( 1 =&gt; \r\n                   dbms_tf.column_metadata_t(name =&gt; 'SUM_COL' ,   \r\n                                         TYPE =&gt; dbms_tf.type_number)) \r\n                        , row_replication=&gt;true);   \r\nEND;   \r\n \r\nPROCEDURE fetch_rows  IS   \r\n    inp_rs DBMS_TF.row_set_t;   \r\n    env    dbms_tf.env_t := dbms_tf.get_env();   \r\n    colcnt PLS_INTEGER;   \r\n    rowcnt PLS_INTEGER;   \r\n    repfac dbms_tf.tab_naturaln_t;     \r\n    sumcol dbms_tf.tab_number_t;   \r\n    sumcurr number; \r\nBEGIN    \r\n    DBMS_TF.get_row_set(inp_rs, rowcnt, colcnt);   \r\n    IF NOT dbms_tf.xstore_exists('sumcurr') THEN \r\n      dbms_tf.xstore_set('sumcurr',0);  \r\n      sumcurr := 0; \r\n    ELSE \r\n      dbms_tf.xstore_get('sumcurr', sumcurr);  \r\n    END IF; \r\n         \r\n     FOR i IN 1 .. rowcnt LOOP  \r\n     -- Actually we should only leave one row in the last rowset, \r\n     -- after having summarized ALL data \r\n     -- Not knowing whether the row set is the last one, \r\n     -- we have to do this for every rowset \r\n      \r\n     -- Eliminate all rows except the first  \r\n       IF i = 1 THEN  \r\n           repfac(i) := 1;  \r\n       ELSE \r\n           repfac(i) := 0;  \r\n       END IF; \r\n         \r\n       sumcurr := sumcurr +  inp_rs(1).tab_number(i);      \r\n     END LOOP;  \r\n   dbms_tf.xstore_set('sumcurr',sumcurr);  \r\n   inp_rs(1).tab_number(1) := sumcurr; \r\n   dbms_tf.row_replication(replication_factor =&gt; repfac);   \r\n   dbms_tf.trace('Rows in rowset: '||env.row_count); \r\n   dbms_tf.put_row_set(inp_rs, repfac); \r\nEND;   \r\n \r\nEND emulate_sum_pkg;  \r\n\/\r\nPackage Body created.\r\n\r\nCREATE OR REPLACE  FUNCTION emulate_sum (tab TABLE, agg_col COLUMNS ) \r\n    RETURN TABLE PIPELINED table POLYMORPHIC USING emulate_sum_pkg; \r\n\/\r\nFunction created.\r\n\r\nSQL&gt; SELECT * \r\n  2  FROM   emulate_sum(scott.emp, COLUMNS(sal));\r\n\r\n   SUM_COL\r\n----------\r\n     29025\r\n\r\n<\/pre>\n<p><em>Listing 7: EMULATE_SUM aggregate function<\/em><\/p>\n<p>Seems to work, doesn&#8217;t it? It does, but only as long as we have very small data sets so that FETCH_ROWS will be executed only once. The problem is, you should eliminate all rows and just do the desired aggregation for all rowsets, except for the last one. But you don&#8217;t know whether the active rowset is the last one. So we are doing that thing for all rowsets. <\/p>\n<p>Look what happens using a data set containing more than 1024 rows (Listing 8):<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\r\nSQL&gt; WITH emp_big AS (SELECT  * \r\n  2                   FROM    scott.emp \r\n  3                           CROSS JOIN \r\n  4                           (SELECT   1 \r\n  5                            FROM  dual \r\n  6                            CONNECT BY level &lt;=100)) \r\n  7  SELECT  * \r\n  8  FROM    emulate_sum(emp_big, COLUMNS(sal));\r\n\r\n\r\n   SUM_COL\r\n----------\r\n   2121225\r\n   2902500\r\n\r\nRows in rowset: 1024\r\nRows in rowset: 376\r\n\r\n<\/pre>\n<p><em>Listing 8: EMULATE_SUM on a bigger data set<\/em><\/p>\n<p>In fact we are returning the sum once per rowset, but only the last result is correct and is actually needed. For me it is a significant limitation of the table semantic PTF at the moment.<\/p>\n<p><strong>What&#8217;s next<\/strong><br \/>\nThis was the last post in 2018. Of course, next year I will continue to investigate the polymorphic table functions. Stay tuned! Hoping that PTF will become more mature in 2019!<br \/>\n<em><strong><br \/>\nMerry Christmas and Happy New Year 2019! <\/strong><\/em><\/p>\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\/using-subqueries-with-ptf-or-sql-macros\/\" target=\"_blank\">Using Subqueries and Views with PTF or 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\/dynamic-pivot-with-polymorphic-table-function\/\" target=\"_blank\">Dynamic Pivot with Polymorphic Table Function?<\/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\/polymorphic-table-functions-ptf-part-3-row-replication\/\" target=\"_blank\">Polymorphic Table Functions (PTF) , Part 3 \u2013 Row Replication<\/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\/polymorphic-table-functions-ptf-part-2-more-basics-with-some-deep-dive\/\" target=\"_blank\">Polymorphic Table Functions (PTF) , Part 2 \u2013 More Basics With Some Deep-Dive<\/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\/polymorphic-table-functions-ptf-tinkering-with-rowsets\/\" target=\"_blank\">Polymorphic Table Functions (PTF) - Tinkering with Rowsets<\/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\/polymorphic-table-functions-ptf-part-1-basics\/\" target=\"_blank\">Polymorphic Table Functions (PTF) ,  Part 1 - Basics<\/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\/polymorphic-table-functions-example-transposing-columns-to-rows\/\" target=\"_blank\">Polymorphic Table Functions Example - Transposing Columns To Rows<\/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\/polymorphic-table-functions\/\" target=\"_blank\">Polymorphic Table Functions<\/a><\/div><\/li><\/ul><\/div>\n","protected":false},"excerpt":{"rendered":"<p>In the first three parts of the series we have seen how a PTF basically works, but we have focused on row-semantic PTF only. In this part we&#8217;ll look at the table semantic PTF. As a reminder, the main difference to row-semantic PTF is that it is not enough to look at the currently processed [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1054,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[66,9,67,3],"tags":[],"class_list":["post-1012","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-18c","category-plsql","category-ptf","category-sql"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1012","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=1012"}],"version-history":[{"count":40,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1012\/revisions"}],"predecessor-version":[{"id":1065,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1012\/revisions\/1065"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/1054"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=1012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=1012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=1012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}