Developers like to reuse code. In SQL, views are reusable queries with lots of advantages, but one drawback: no parameters to limit the queries to only those rows we want. Sure, we can add a WHERE
clause when selecting from a view, but the view might return lots of unnecessary data before the WHERE
clause filters the rows.
As Andrej Pashchenko recently wrote, SQL table macros can get the results we would want from parameterized views (if they existed), but they do not have all the advantages of real views: see https://blog.sqlora.com/en/parameterized-views-with-sql-macros-part-2/ He wants to combine the advantages of views and macros, and so do I.
(To skip the explanations and see what I came up with, go to My proposal.)
I’ll concentrate on these advantages of views:
- We can include them in queries anywhere a table could appear.
- The
*_DEPENDENCIES
views show the “dependency chain” between them and other objects.
Here is a super simple example:
create or replace view dept_emp as
select deptno, dname, loc, empno, ename, job, mgr, sal, comm
from dept
left join emp using(deptno);
select name, type, referenced_name, referenced_type
from user_dependencies where name = 'DEPT_EMP';
NAME TYPE REFERENCED_NAME REFERENCED_TYPE
DEPT_EMP VIEW DEPT TABLE
DEPT_EMP VIEW EMP TABLE
Now, what if we want to filter on DEPTNO
? We can just copy the query from the view and add a filter:
create or replace function dept_emp_macro(p_deptno number)
return varchar2 sql_macro is
begin
return '
select deptno, dname, loc, empno, ename, job, mgr, sal, comm
from dept
left join emp using(deptno)
WHERE DEPTNO = P_DEPTNO';
end dept_emp_macro;
/
select * from dept_emp_macro(10);
DEPTNO DNAME LOC EMPNO ENAME JOB MGR SAL COMM
10 ACCOUNTING NEW YORK 7839 KING PRESIDENT 5000
10 ACCOUNTING NEW YORK 7782 CLARK MANAGER 7839 2450
10 ACCOUNTING NEW YORK 7934 MILLER CLERK 7782 1300
select name, type, referenced_name, referenced_type
from user_dependencies where name = 'DEPT_EMP_MACRO';
NAME TYPE REFERENCED_NAME REFERENCED_TYPE
DEPT_EMP_MACRO FUNCTION STANDARD PACKAGE
With the macro, we have a parameterized query but we have lost the information about the dependency chain in USER_DEPENDENCIES
, so the second advantage of views is lost. Not only that, but the first advantage is limited as well: currently SQL macros inside WITH
clauses are not permitted, and WITH
clauses inside SQL macros cannot use scalar parameters!
My proposal
- I write and test a complete query. A
WITH
clause at the beginning contains hard-coded parameters. - I create a view based on that query, including the hard_coded parameters.
- A generic SQL macro returns the view text after stripping off the hard-coded WITH clause.
- I call the macro using a new
WITH
clause, that can include whatever values or bind variables I want.
create or replace function parameterized_view(
p_view dbms_tf.table_t,
P_PARMS dbms_tf.table_t
)
return clob sql_macro is
l_view_text long;
begin
select text into l_view_text from user_views
where view_name = trim('"' from p_view.table_name);
l_view_text :=
regexp_replace(
regexp_replace(l_view_text, 'with\s*p_parms[^)]*\)\s*', '', 1, 0, 'i'),
'^,', 'with '
);
return l_view_text;
end parameterized_view;
/
create or replace view dept_emp_pv as
with P_PARMS as (select 'RESEARCH' dname, 'CLERK' job from dual)
, d as (
select deptno, dname, loc from dept
where dname = (select dname from P_PARMS)
)
, e as (
select deptno, empno, ename, job, mgr, sal, comm
from emp
where job = (select job from P_PARMS)
)
select * from d left join e using(deptno);
with PARMS as (select 'SALES' dname, 'SALESMAN' job from dual)
select * from parameterized_view(dept_emp_pv, PARMS);
DEPTNO DNAME LOC EMPNO ENAME JOB MGR SAL COMM
30 SALES CHICAGO 7499 ALLEN SALESMAN 7698 1600 300
30 SALES CHICAGO 7521 WARD SALESMAN 7698 1250 500
30 SALES CHICAGO 7654 MARTIN SALESMAN 7698 1250 1400
30 SALES CHICAGO 7844 TURNER SALESMAN 7698 1500
Note that in the view, the “parameters” clause must have the same name as the second parameter in the macro: P_PARMS
. When I call the macro, I can give the clause any name I want, since I pass the name to the macro.
Remember, DEPT_EMP_PV
is the identifier of a table or view, so if it is invalidated then the query will automatically be reparsed and the new text will be picked up from the data dictionary. The dependency chain will work even though the macro itself has not been invalidated.
To conclude, using one 16-line generic macro we can have almost all the advantages of views while adding support for parameters! The views do have to be written with this usage in mind, and they are meant to be used as main queries, not subqueries.
Posts on SQL table macros
- SQL table macros 01: a moving target?
- SQL table macros 02: “polymorphic” is not “dynamic”
- SQL table macros 3: parameter strangeness
- SQL table macros 4: distribute into equal parts
- SQL table macros 5: dynamic but dangerous!
- SQL table macros 6: SQM_UTIL package
- SQL table macros 7: Select excluding
- SQL table macros 8: Print table
- SQL table macros 9: just pivot!
- SQL table macros 10: JUST_PIVOT examples
- SQL table macros 11: JUST_PIVOT for 21c (and 19c)
- SQL table macros 12: compare tables or queries
- SQL table macros 13: compare with primary key
- SQL table macros 14: parameterized views