<p>I've written PL/SQL code to denormalize a table into a much-easer-to-query form. The code uses a temporary table to do some of its work, merging some rows from the original table together.</p>

<p>The logic is written as a <a href="http://www.oreillynet.com/lpt/a/3136" rel="nofollow noreferrer">pipelined table function</a>, following the pattern from the linked article. The table function uses a <code>PRAGMA AUTONOMOUS_TRANSACTION</code> declaration to permit the temporary table manipulation, and also accepts a cursor input parameter to restrict the denormalization to certain ID values.</p>

<p>I then created a view to query the table function, passing in all possible ID values as a cursor (other uses of the function will be more restrictive).</p>

<p>My question: is this all really necessary? Have I completely missed a much more simple way of accomplishing the same thing?</p>

<p>Every time I touch PL/SQL I get the impression that I'm typing way too much.</p>

<p><strong>Update:</strong> I'll add a sketch of the table I'm dealing with to give everyone an idea of the denormalization that I'm talking about. The table stores a history of employee jobs, each with an activation row, and (possibly) a termination row. It's possible for an employee to have multiple simultaneous jobs, as well as the same job over and over again in non-contiguous date ranges. For example:</p>

<pre><code>| EMP_ID | JOB_ID | STATUS | EFF_DATE    | other columns...
|      1 |     10 | A      | 10-JAN-2008 |
|      2 |     11 | A      | 13-JAN-2008 |
|      1 |     12 | A      | 20-JAN-2008 |
|      2 |     11 | T      | 01-FEB-2008 |
|      1 |     10 | T      | 02-FEB-2008 |
|      2 |     11 | A      | 20-FEB-2008 |
</code></pre>

<p>Querying that to figure out who is working when in what job is non-trivial. So, my denormalization function populates the temporary table with just the date ranges for each job, for any <code>EMP_ID</code>s passed in though the cursor. Passing in <code>EMP_ID</code>s 1 and 2 would produce the following:</p>

<pre><code>| EMP_ID | JOB_ID | START_DATE  | END_DATE    |
|      1 |     10 | 10-JAN-2008 | 02-FEB-2008 |
|      2 |     11 | 13-JAN-2008 | 01-FEB-2008 |
|      1 |     12 | 20-JAN-2008 |             |
|      2 |     11 | 20-FEB-2008 |             |
</code></pre>

<p>(<code>END_DATE</code> allows <code>NULL</code>s for jobs that don't have a predetermined termination date.)</p>

<p>As you can imagine, this denormalized form is much, much easier to query, but creating it--so far as I can tell--requires a temporary table to store the intermediate results (e.g., job records for which the activation row has been found, but not the termination...yet). Using the pipelined table function to populate the temporary table and then return its rows is the only way I've figured out how to do it.</p>

[{'answer_id': 22033, 'author': 'hollystyles', 'author_id': 2083160, 'author_profile': 'https://Stackoverflow.com/users/2083160', 'pm_score': 1, 'selected': False, 'text': "<p>Rather than having the input parameter as a cursor, I would have a table variable (don't know if Oracle has such a thing I'm a TSQL guy) or populate another temp table with the ID values and join on it in the view/function or wherever you need to.</p>\n\n<p>The only time for cursors in my honest opinion is when you <em>have</em> to loop. And when you have to loop I always recommend to do that outside of the database in the application logic.</p>\n"}, {'answer_id': 22680, 'author': 'yukondude', 'author_id': 726, 'author_profile': 'https://Stackoverflow.com/users/726', 'pm_score': 0, 'selected': False, 'text': "<p>I couldn't agree with you more, HollyStyles. I also used to be a TSQL guy, and find some of Oracle's idiosyncrasies more than a little perplexing. Unfortunately, temp tables aren't as convenient in Oracle, and in this case, other existing SQL logic is expecting to directly query a table, so I give it this view instead. There's really no application logic that exists outside of the database in this system.</p>\n\n<p>Oracle developers do seem to use cursors much more eagerly than I would have thought. Given the bondage &amp; discipline nature of PL/SQL, that's all the more surprising.</p>\n"}, {'answer_id': 89084, 'author': 'Sergey Stadnik', 'author_id': 10557, 'author_profile': 'https://Stackoverflow.com/users/10557', 'pm_score': 0, 'selected': False, 'text': '<p>The simplest solution is:</p>\n\n<ol>\n<li><p>Create a <a href="http://www.oracle-base.com/articles/8i/TemporaryTables.php" rel="nofollow noreferrer">global temporary table</a> containing just IDs you need:</p>\n\n<pre><code>CREATE GLOBAL TEMPORARY TABLE tab_ids (id INTEGER)  \nON COMMIT DELETE ROWS;\n</code></pre></li>\n<li><p>Populate the temporary table with the IDs you need.</p></li>\n<li><p>Use EXISTS operation in your procedure to select the rows that are only in the IDs table:</p>\n\n<pre><code>  SELECT yt.col1, yt.col2 FROM your\\_table yt  \n   WHERE EXISTS (  \n      SELECT \'X\' FROM tab_ids ti  \n       WHERE ti.id = yt.id  \n   )\n</code></pre></li>\n</ol>\n\n<p>You can also pass a comma-separated string of IDs as a function parameter and parse it into a table. This is performed by a single SELECT. Want to know more - ask me how :-) But it\'s got to be a separate question.</p>\n'}, {'answer_id': 92283, 'author': 'mathewbutler', 'author_id': 2288, 'author_profile': 'https://Stackoverflow.com/users/2288', 'pm_score': 1, 'selected': False, 'text': '<p>It sounds like you are giving away some read consistency here ie: it will be possible for the contents of your temporary table to be out of sync with the source data, if you have concurrent modification data modification.</p>\n\n<p>Without knowing the requirements, nor complexity of what you want to achieve. I would attempt</p>\n\n<ol>\n<li>to define a view, containing (possibly complex) logic in SQL, else I\'d add some PL/SQL to the mix with;</li>\n<li>A pipelined table function, but using an SQL collection type (instead of the temporary table ). A simple example is here: <a href="http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109" rel="nofollow noreferrer">http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109</a></li>\n</ol>\n\n<p>Number 2 would give you less moving parts and solve your consistency issue.</p>\n\n<p>Mathew Butler</p>\n'}, {'answer_id': 97091, 'author': 'Nick Pierpoint', 'author_id': 4003, 'author_profile': 'https://Stackoverflow.com/users/4003', 'pm_score': 3, 'selected': True, 'text': '<p>I think a way to approach this is to use analytic functions...</p>\n\n<p>I set up your test case using:</p>\n\n<pre><code>create table employee_job (\n    emp_id integer,\n    job_id integer,\n    status varchar2(1 char),\n    eff_date date\n    );  \n\ninsert into employee_job values (1,10,\'A\',to_date(\'10-JAN-2008\',\'DD-MON-YYYY\'));\ninsert into employee_job values (2,11,\'A\',to_date(\'13-JAN-2008\',\'DD-MON-YYYY\'));\ninsert into employee_job values (1,12,\'A\',to_date(\'20-JAN-2008\',\'DD-MON-YYYY\'));\ninsert into employee_job values (2,11,\'T\',to_date(\'01-FEB-2008\',\'DD-MON-YYYY\'));\ninsert into employee_job values (1,10,\'T\',to_date(\'02-FEB-2008\',\'DD-MON-YYYY\'));\ninsert into employee_job values (2,11,\'A\',to_date(\'20-FEB-2008\',\'DD-MON-YYYY\'));\n\ncommit;\n</code></pre>\n\n<p>I\'ve used the <strong>lead</strong> function to get the next date and then wrapped it all as a sub-query just to get the "A" records and add the end date if there is one.</p>\n\n<pre><code>select\n    emp_id,\n    job_id,\n    eff_date start_date,\n    decode(next_status,\'T\',next_eff_date,null) end_date\nfrom\n    (\n    select\n        emp_id,\n        job_id,\n        eff_date,\n        status,\n        lead(eff_date,1,null) over (partition by emp_id, job_id order by eff_date, status) next_eff_date,\n        lead(status,1,null) over (partition by emp_id, job_id order by eff_date, status) next_status\n    from\n        employee_job\n    )\nwhere\n    status = \'A\'\norder by\n    start_date,\n    emp_id,\n    job_id\n</code></pre>\n\n<p>I\'m sure there\'s some use cases I\'ve missed but you get the idea. Analytic functions are your friend :)</p>\n\n<pre><code>EMP_ID   JOB_ID     START_DATE     END_DATE            \n  1        10       10-JAN-2008    02-FEB-2008         \n  2        11       13-JAN-2008    01-FEB-2008         \n  2        11       20-FEB-2008                              \n  1        12       20-JAN-2008                              \n</code></pre>\n'}, {'answer_id': 102942, 'author': 'Tony Andrews', 'author_id': 18747, 'author_profile': 'https://Stackoverflow.com/users/18747', 'pm_score': 1, 'selected': False, 'text': '<p>The real problem here is the "write-only" table design - by which I mean, it\'s easy to insert data into it, but tricky and inefficient to get useful information out of it!  Your "temporary" table has the structure the "permanent" table should have had in the first place.</p>\n\n<p>Could you perhaps do this:</p>\n\n<ul>\n<li>Create a permanent table with the better structure</li>\n<li>Populate it to match the data in the first table</li>\n<li>Define a database trigger on the original table to keep the new table in sync from now on</li>\n</ul>\n\n<p>Then you can just select from the new table to perform your reporting.</p>\n'}]
['https://Stackoverflow.com/questions/20081', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/726/']