{"id":495,"date":"2010-11-26T15:40:00","date_gmt":"2010-11-26T15:40:00","guid":{"rendered":"http:\/\/bar-solutions.com\/weblog\/?p=495"},"modified":"2010-11-26T10:35:00","modified_gmt":"2010-11-26T09:35:00","slug":"","status":"publish","type":"post","link":"https:\/\/blog.bar-solutions.com\/?p=495","title":{"rendered":"Pipelined Table Functions"},"content":{"rendered":"<p align=\"left\">I have had trouble with a certain view I have to create at the customer site I am currently working at. The view involves 3 SQL queries combined by using a <a title=\"The UNION [ALL], INTERSECT, MINUS Operators\" href=\"http:\/\/download.oracle.com\/docs\/cd\/B19306_01\/server.102\/b14200\/queries004.htm\" rel=\"nofollow\" target=\"_blank\">UNION ALL<\/a>, since the separate queries are mutually exclusive. Using the UNION ALL makes it a bit faster since <a title=\"Oracle\" href=\"http:\/\/www.oracle.com\" rel=\"nofollow\" target=\"_blank\">Oracle<\/a> doesn&#8217;t have to do all the work of sorting and checking for duplicate rows. <\/p>\n<p>Retrieving data from this view resulted in memory errors since there is too much data for the server to come up with and to return to me. After checking all kinds of initialization parameters I decided to try and build a couple of <a title=\"Using Pipelined and Parallel Table Functions\" href=\"http:\/\/download.oracle.com\/docs\/cd\/B14117_01\/appdev.101\/b10800\/dcitblfns.htm\" rel=\"nofollow\" target=\"_blank\">pipelined table functions<\/a> that would retrieve the same data for me, but I assumed it wouldn&#8217;t put such a big pressure on my memory usage. <\/p>\n<p><!--more--><\/p>\n<p align=\"left\">A pipelined table function will come up with a single row for the result set and pipe it out of the function when it&#8217;s available. In my opinion there would only be need for enough memory for this single row instead of enough memory for all the rows of the entire result set. <\/p>\n<p>Since the function gets called from SQL the SQL engine needs to be aware of the result it produces. So I need to create types that define a row of the result. And since the function will return more than a single row, I also need to define a <a title=\"PL\/SQL Collections and Records\" href=\"http:\/\/download.oracle.com\/docs\/cd\/B10501_01\/appdev.920\/a96624\/05_colls.htm\" rel=\"nofollow\" target=\"_blank\">NESTED TABLE<\/a>, a collection of these rows. Using two types I can mimic the appearance of a table. <\/p>\n<p>Now all I have to do is create functions that will give me the result I am interested in. <\/p>\n<p align=\"left\">My \u2018normal\u2019 view looks like this:<\/p>\n<p><font size=\"2\" face=\"Courier New\">create or replace view v_emps as<br \/>\n  <br \/>select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm<br \/>\n  <br \/>&#160;&#160; from emp e<br \/>\n  <br \/>&#160; where e.deptno = 10<br \/>\n  <br \/>union all<br \/>\n  <br \/>select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm<br \/>\n  <br \/>&#160; from emp e<br \/>\n  <br \/>where e.deptno = 20<br \/>\n  <br \/>union all<br \/>\n  <br \/>select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm<br \/>\n  <br \/>&#160; from emp e<br \/>\n  <br \/>where e.deptno = 30<br \/>\n  <br \/>union all<br \/>\n  <br \/>select e.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm<br \/>\n  <br \/>&#160; from emp e<br \/>\n  <br \/>where e.deptno = 40<br \/>\n  <br \/>\/<\/font> <\/p>\n<p align=\"left\">In the following example I create a new view based on the table functions for the EMP table. The SQL approach is better, easier to read and faster, but it shows what should be done if you want to move over to table functions.<\/p>\n<p>The way such a function look is kind of strange at first sight. <\/p>\n<p><font size=\"2\" face=\"Courier New\">Function f1 return mynestedtabletype pipelined<br \/>\n  <br \/>Is<br \/>\n  <br \/>&#160; &lt;declaration&gt;<br \/>\n  <br \/>&#160; l_record myrecordtype;<br \/>\n  <br \/>begin<br \/>\n  <br \/>&#160; loop<br \/>\n  <br \/>&#160;&#160;&#160; &lt;do stuff to define l_record&gt;<br \/>\n  <br \/>&#160;&#160;&#160; pipe row(l_record); &#8212; send the row out<br \/>\n  <br \/>&#160; end loop;<br \/>\n  <br \/>&#160; return; &#8212; return nothing but control<br \/>\n  <br \/>end f1;<br \/>\n  <br \/><\/font><\/p>\n<p align=\"left\">First I create a type for the records I return from my function:<\/p>\n<p><font size=\"2\" face=\"Courier New\">CREATE TYPE emp_t AS OBJECT<br \/>\n    <br \/>(<br \/>\n    <br \/>&#160; empno&#160;&#160;&#160; NUMBER(4),<br \/>\n    <br \/>&#160; ename&#160;&#160;&#160; VARCHAR2(10),<br \/>\n    <br \/>&#160; job&#160;&#160;&#160;&#160;&#160; VARCHAR2(9),<br \/>\n    <br \/>&#160; mgr&#160;&#160;&#160;&#160;&#160; NUMBER(4),<br \/>\n    <br \/>&#160; hiredate DATE,<br \/>\n    <br \/>&#160; sal&#160;&#160;&#160;&#160;&#160; NUMBER(7, 2),<br \/>\n    <br \/>&#160; comm&#160;&#160;&#160;&#160; NUMBER(7, 2)<br \/>\n    <br \/>)<br \/>\n    <br \/>\/<\/font><\/p>\n<p align=\"left\">Then I create a nested table of the record type I just created. The functions will return (or say they will return) a variable of this type:<\/p>\n<p><font size=\"2\" face=\"Courier New\">CREATE TYPE emps_tt AS TABLE OF emp_t<br \/>\n    <br \/>\/<\/font><\/p>\n<p align=\"left\">Next is the package which will contain the table functions. I create 4 functions to retrieve all the datasets I need.<\/p>\n<p><font size=\"2\" face=\"Courier New\">CREATE OR REPLACE PACKAGE emp_tf IS&#160; <br \/>&#160; &#8212; Author&#160; : Patrick Barel<br \/>\n    <br \/>&#160; &#8212; Purpose : Table Functions for EMP<br \/>\n<br \/>&#160; &#8212; Public function and procedure declarations<br \/>\n    <br \/>&#160; FUNCTION get_accounting RETURN emps_tt PIPELINED;<br \/>\n    <br \/>&#160; FUNCTION get_research RETURN emps_tt PIPELINED;<br \/>\n    <br \/>&#160; FUNCTION get_sales RETURN emps_tt PIPELINED;<br \/>\n    <br \/>&#160; FUNCTION get_operations RETURN emps_tt PIPELINED;<br \/>\n    <br \/>END emp_tf;<br \/>\n    <br \/>\/<\/font><\/p>\n<p><font size=\"2\" face=\"Courier New\">CREATE OR REPLACE PACKAGE BODY emp_tf IS<br \/>\n    <br \/>&#160; &#8212; Function and procedure implementations<br \/>\n    <br \/>&#160; FUNCTION get_accounting RETURN emps_tt<br \/>\n    <br \/>&#160;&#160;&#160; PIPELINED IS<br \/>\n    <br \/>&#160;&#160;&#160; CURSOR c_emps IS<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; SELECT e.empno, e.ename, e.job, e.mgr<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,e.hiredate, e.sal, e.comm<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM emp e<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160; WHERE e.deptno = 10;<br \/>\n    <br \/>&#160;&#160;&#160; r_emps&#160;&#160;&#160;&#160;&#160;&#160;&#160; c_emps%ROWTYPE;<br \/>\n    <br \/>&#160;&#160;&#160; l_returnvalue emp_t;<br \/>\n    <br \/>&#160; BEGIN<br \/>\n    <br \/>&#160;&#160;&#160; OPEN c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; LOOP<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; FETCH c_emps INTO r_emps;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; l_returnvalue := emp_t(r_emps.empno, r_emps.ename, r_emps.job<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.mgr, r_emps.hiredate<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.sal, r_emps.comm);<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; EXIT WHEN c_emps%NOTFOUND;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; PIPE ROW(l_returnvalue);<br \/>\n    <br \/>&#160;&#160;&#160; END LOOP;<br \/>\n    <br \/>&#160;&#160;&#160; CLOSE c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; RETURN;<br \/>\n    <br \/>&#160; END get_accounting;<br \/>\n    <br \/>&#160; &#8212;<br \/>\n    <br \/>&#160; FUNCTION get_research RETURN emps_tt<br \/>\n    <br \/>&#160;&#160;&#160; PIPELINED IS<br \/>\n    <br \/>&#160;&#160;&#160; CURSOR c_emps IS<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; SELECT e.empno, e.ename, e.job, e.mgr<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,e.hiredate, e.sal, e.comm<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM emp e<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160; WHERE e.deptno = 20;<br \/>\n    <br \/>&#160;&#160;&#160; r_emps&#160;&#160;&#160;&#160;&#160;&#160;&#160; c_emps%ROWTYPE;<br \/>\n    <br \/>&#160;&#160;&#160; l_returnvalue emp_t;<br \/>\n    <br \/>&#160; BEGIN<br \/>\n    <br \/>&#160;&#160;&#160; OPEN c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; LOOP<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; FETCH c_emps<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; INTO r_emps;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; l_returnvalue := emp_t(r_emps.empno, r_emps.ename, r_emps.job<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.mgr, r_emps.hiredate<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.sal, r_emps.comm);<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; EXIT WHEN c_emps%NOTFOUND;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; PIPE ROW(l_returnvalue);<br \/>\n    <br \/>&#160;&#160;&#160; END LOOP;<br \/>\n    <br \/>&#160;&#160;&#160; CLOSE c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; RETURN;<br \/>\n    <br \/>&#160; END get_research;<br \/>\n    <br \/>&#160; &#8212;<br \/>\n    <br \/>&#160; FUNCTION get_sales RETURN emps_tt<br \/>\n    <br \/>&#160;&#160;&#160; PIPELINED IS<br \/>\n    <br \/>&#160;&#160;&#160; CURSOR c_emps IS<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; SELECT e.empno, e.ename, e.job, e.mgr<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,e.hiredate, e.sal, e.comm<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM emp e<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160; WHERE e.deptno = 30;<br \/>\n    <br \/>&#160;&#160;&#160; r_emps&#160;&#160;&#160;&#160;&#160;&#160;&#160; c_emps%ROWTYPE;<br \/>\n    <br \/>&#160;&#160;&#160; l_returnvalue emp_t;<br \/>\n    <br \/>&#160; BEGIN<br \/>\n    <br \/>&#160;&#160;&#160; OPEN c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; LOOP<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; FETCH c_emps<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; INTO r_emps;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; l_returnvalue := emp_t(r_emps.empno, r_emps.ename, r_emps.job<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.mgr, r_emps.hiredate<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.sal, r_emps.comm);<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; EXIT WHEN c_emps%NOTFOUND;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; PIPE ROW(l_returnvalue);<br \/>\n    <br \/>&#160;&#160;&#160; END LOOP;<br \/>\n    <br \/>&#160;&#160;&#160; CLOSE c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; RETURN;<br \/>\n    <br \/>&#160; END get_sales;<br \/>\n    <br \/>&#160; &#8212;<br \/>\n    <br \/>&#160; FUNCTION get_operations RETURN emps_tt<br \/>\n    <br \/>&#160;&#160;&#160; PIPELINED IS<br \/>\n    <br \/>&#160;&#160;&#160; CURSOR c_emps IS<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; SELECT e.empno, e.ename, e.job, e.mgr<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,e.hiredate, e.sal, e.comm<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM emp e<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160; WHERE e.deptno = 40;<br \/>\n    <br \/>&#160;&#160;&#160; r_emps&#160;&#160;&#160;&#160;&#160;&#160;&#160; c_emps%ROWTYPE;<br \/>\n    <br \/>&#160;&#160;&#160; l_returnvalue emp_t;<br \/>\n    <br \/>&#160; BEGIN<br \/>\n    <br \/>&#160;&#160;&#160; OPEN c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; LOOP<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; FETCH c_emps<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; INTO r_emps;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; l_returnvalue := emp_t(r_emps.empno, r_emps.ename, r_emps.job<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.mgr, r_emps.hiredate<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,r_emps.sal, r_emps.comm);<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; EXIT WHEN c_emps%NOTFOUND;<br \/>\n    <br \/>&#160;&#160;&#160;&#160;&#160; PIPE ROW(l_returnvalue);<br \/>\n    <br \/>&#160;&#160;&#160; END LOOP;<br \/>\n    <br \/>&#160;&#160;&#160; CLOSE c_emps;<br \/>\n    <br \/>&#160;&#160;&#160; RETURN;<br \/>\n    <br \/>&#160; END get_operations;<br \/>\n    <br \/>&#160; &#8212;<br \/>\n    <br \/>BEGIN<br \/>\n    <br \/>&#160; NULL;<br \/>\n    <br \/>END emp_tf;<br \/>\n    <br \/>\/<\/font><\/p>\n<p align=\"left\">In the implementation of the functions I could of course use BULK OPERATIONS to speed up the processing instead of the row-by-row (or slow-by-slow) approach I used now, but using bulk operations use more memory (the retrieved data is loaded into memory) and that is exactly what I am trying to avoid. Using BULK COLLECT with a limit clause might be an option, but that is something to explore later on.<\/p>\n<p align=\"left\">The new view looks like this:<\/p>\n<p><font size=\"2\" face=\"Courier New\">create or replace view v_emps_tf as<br \/>\n    <br \/>select * from table(emp_tf.get_accounting)<br \/>\n    <br \/>union all<br \/>\n    <br \/>select * from table(emp_tf.get_research)<br \/>\n    <br \/>union all<br \/>\n    <br \/>select * from table(emp_tf.get_sales)<br \/>\n    <br \/>union all<br \/>\n    <br \/>select * from table(emp_tf.get_operations)<br \/>\n    <br \/>\/<\/font><\/p>\n<p align=\"left\">Pretty much the same as the original one, but now based on my functions.<\/p>\n<p align=\"left\">Let\u2019s take a look at the \u2018strange\u2019 things here. When you look at the function specification it says it will return a nested table type, but in fact it will return nothing. Every result gets piped out of the function (PIPE ROW) when it is done.<br \/>\n  <br \/>Another thing is the from part of the queries. We tell the SQL engine to treat the results of the function as if it were a table. For this we use the TABLE() operator.<\/p>\n<p>Doing some checks (select * from v_emp minus select * from v_emp_tf) tells me the result of both views is the same, since there are no rows returned. When I turn timing on I notice that the \u2018normal\u2019 SQL approach is faster than the table function approach. But the issue wasn\u2019t speed, it was memory usage.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have had trouble with a certain view I have to create at the customer site I am currently working at. The view involves 3 SQL queries combined by using a UNION ALL, since the separate queries are mutually exclusive. Using the UNION ALL makes it a bit faster since Oracle doesn&#8217;t have to do [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,5,9],"tags":[],"class_list":["post-495","post","type-post","status-publish","format-standard","hentry","category-oracle","category-plsql","category-sql"],"_links":{"self":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts\/495","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=495"}],"version-history":[{"count":0,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts\/495\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}