SQL Language Extension: common table expressions

Author:
    Vlad Horsun <hvlad@users.souceforge.net>
	based on work by Paul Ruizendaal for Fyracle project


Function:
	Common Table Expressions is like view’s, locally defined within main query.
From the engine point of view CTE is a derived table so no intermediate 
materialization is performed. 

	Recursive CTEs allow to create recursive queries. It works as below :
engine starts execution from non-recursive members,
for each evaluated row engine starts execution of each recursive member using 
current row values as parameters,
if current instance of recursive member produced no rows engine returns one 
step back and get next row from previous resultset 

	Memory and CPU overhead of recursive CTE is much less than overhead of 
recursive stored procedures. Currently recursion depth is limited by hardcoded 
value of 1024

	
Syntax:

select		: select_expr for_update_clause lock_clause

select_expr	: with_clause select_expr_body order_clause rows_clause
			| select_expr_body order_clause rows_clause
 
with_clause	: WITH RECURSIVE with_list
			| WITH with_list
 
with_list	: with_item 
			| with_item ',' with_list

with_item	: symbol_table_alias_name derived_column_list AS '(' select_expr ')'

select_expr_body	: query_term
		| select_expr_body UNION distinct_noise query_term
		| select_expr_body UNION ALL query_term


	Or, in less formal format :

WITH [RECURSIVE]
	CTE_A [(a1, a2, …)] 
	AS ( SELECT … ),

	CTE_B [(b1, b2, …)]
	AS ( SELECT … ),
...
SELECT ...
  FROM CTE_A, CTE_B, TAB1, TAB2 ...
 WHERE ...
 


Rules of non-recursive common table expressions :
 
	Several table expressions can be defined at one query
	Any SELECT’s clause can be used in table expressions
	Table expressions can reference each other
	Table expressions can be used within any part of main query or another 
table expression
	The same table expression can be used several times in main query
	Table expressions can be used in INSERT, UPDATE and DELETE statements 
(as subqueries of course)
	Table expressions can be used in procedure language also 
	WITH statements can not be nested 
	References between expressions should not have loops


Example of non-recursive common table expressions :

WITH
  DEPT_YEAR_BUDGET AS (
    SELECT FISCAL_YEAR, DEPT_NO, SUM(PROJECTED_BUDGET) AS BUDGET
      FROM PROJ_DEPT_BUDGET
    GROUP BY FISCAL_YEAR, DEPT_NO
  )
SELECT D.DEPT_NO, D.DEPARTMENT,
       B_1993.BUDGET AS B_1993, B_1994.BUDGET AS B_1994,
       B_1995.BUDGET AS B_1995, B_1996.BUDGET AS B_1996
  FROM DEPARTMENT D
       LEFT JOIN DEPT_YEAR_BUDGET B_1993
    ON D.DEPT_NO = B_1993.DEPT_NO AND B_1993.FISCAL_YEAR = 1993
       LEFT JOIN DEPT_YEAR_BUDGET B_1994
    ON D.DEPT_NO = B_1994.DEPT_NO AND B_1994.FISCAL_YEAR = 1994
       LEFT JOIN DEPT_YEAR_BUDGET B_1995
    ON D.DEPT_NO = B_1995.DEPT_NO AND B_1995.FISCAL_YEAR = 1995
       LEFT JOIN DEPT_YEAR_BUDGET B_1996
    ON D.DEPT_NO = B_1996.DEPT_NO AND B_1996.FISCAL_YEAR = 1996

WHERE EXISTS (SELECT * FROM PROJ_DEPT_BUDGET B WHERE D.DEPT_NO = B.DEPT_NO)


Rules of recursive common table expressions :

	Recursive CTE has reference to itself
	Recursive CTE is an UNION of recursive and non-recursive members	
	At least one non-recursive member (anchor) must be present
	Non-recursive members are placed first in UNION
	Recursive members are separated from anchor members and from each 
other with UNION ALL clause
	References between CTE’s should not have loops
	Aggregates (DISTINCT, GROUP BY, HAVING) and aggregate functions (SUM, 
COUNT, MAX etc) are not allowed in recursive members
	Recursive member can have only one reference to itself and only in FROM clause
	Recursive reference can not participate in outer joins


Example of recursive common table expressions :

WITH RECURSIVE
  DEPT_YEAR_BUDGET AS
  (
    SELECT FISCAL_YEAR, DEPT_NO, SUM(PROJECTED_BUDGET) AS BUDGET
      FROM PROJ_DEPT_BUDGET
    GROUP BY FISCAL_YEAR, DEPT_NO
  ),

  DEPT_TREE AS 
  (
    SELECT DEPT_NO, HEAD_DEPT, DEPARTMENT, CAST('' AS VARCHAR(255)) AS INDENT
      FROM DEPARTMENT
     WHERE HEAD_DEPT IS NULL

    UNION ALL

    SELECT D.DEPT_NO, D.HEAD_DEPT, D.DEPARTMENT, H.INDENT || '  '
      FROM DEPARTMENT D JOIN DEPT_TREE H
        ON D.HEAD_DEPT = H.DEPT_NO
  )

SELECT D.DEPT_NO, 
	D.INDENT || D.DEPARTMENT AS DEPARTMENT,
	B_1993.BUDGET AS B_1993, 
	B_1994.BUDGET AS B_1994,
	B_1995.BUDGET AS B_1995, 
	B_1996.BUDGET AS B_1996

  FROM DEPT_TREE D
       LEFT JOIN DEPT_YEAR_BUDGET B_1993
    ON D.DEPT_NO = B_1993.DEPT_NO AND B_1993.FISCAL_YEAR = 1993

       LEFT JOIN DEPT_YEAR_BUDGET B_1994
    ON D.DEPT_NO = B_1994.DEPT_NO AND B_1994.FISCAL_YEAR = 1994

       LEFT JOIN DEPT_YEAR_BUDGET B_1995
    ON D.DEPT_NO = B_1995.DEPT_NO AND B_1995.FISCAL_YEAR = 1995

       LEFT JOIN DEPT_YEAR_BUDGET B_1996
    ON D.DEPT_NO = B_1996.DEPT_NO AND B_1996.FISCAL_YEAR = 1996
