Share
Embed
Create reusable SQL expressions with SQL macros
A common criticism of SQL is it lacks composability - the ability to define small units you can combine into larger functions. Oracle Database addresses this with the introduction of SQL macros. These return SQL expressions, enabling you to define reusable components.
Using SQL macros you can SQL templates which you can pass tables, columns, and other values to at runtime. This enables you to build a library of functions to solve common problems, such as finding consecutive rows or the top-N rows per group.
This presentation explains how to use SQL macros.
Chris
May 15, 2023
Transcript
67 slides
-
Create reusable SQL expressions with SQL macros How to make
maintainable SQL Chris Saxon @ChrisRSaxon & @SQLDaily https://blogs.oracle.com/sql https://www.youtube.com/c/TheMagicofSQL -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Get the first three hires in each department Ryan McGuire / Gratisography -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL with rws as ( select t.*, row_number () over ( partition by department_id order by hire_date ) rn from hr.employees t ) select * from rws where rn <= 3 order by department_id, rn; -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL with rws as ( select t.*, row_number () over ( partition by department_id order by hire_date ) rn from hr.employees t ) select * from rws where rn <= 3 order by department_id, rn; -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Get the last three orders for each customer Ryan McGuire / Gratisography -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL with rws as ( select t.*, row_number () over ( partition by department_id order by hire_date ) rn from hr.employees t ) select * from rws where rn <= 3 order by department_id, rn; -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL with rws as ( select t.*, row_number () over ( partition by customer_id order by order_date desc ) rn from co.orders t ) select * from rws where rn <= 3 order by customer_id, rn; -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Can you make that reusable? Ryan McGuire / Gratisography -
Ryan McGuire / Gratisography no? Copyright © 2021 Oracle and/or
its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL open cur for 'with rws as ( select …, row_number () over ( partition by ? order by ? ) rn from ? ) … Can't bind identifiers -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL open cur for 'with rws as ( select …, row_number () over ( partition by ' || group_cols || ' order by ' || sort_cols || ' ) rn from ' || tab || ' ) … '; SQL -
SQL Macros Create reusable SQL expressions Copyright © 2021 Oracle
and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Create reusable SQL expressions with SQL macros How to make
maintainable SQL Chris Saxon @ChrisRSaxon & @SQLDaily https://blogs.oracle.com/sql https://www.youtube.com/c/TheMagicofSQL -
SQL Macros table scalar Copyright © 2021 Oracle and/or its
affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
SQL Macros table scalar in from clause "everywhere" else Copyright
© 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
SQL Macros table scalar in from clause "everywhere" else 19.6
21c Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function sql_macro ( … ) return clob sql_macro (
[ scalar | table ] ) as begin return ' string picture '; end sql_macro; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function sql_macro ( … ) return clob sql_macro (
[ scalar | table ] ) as begin return ' string picture '; end sql_macro; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function sql_macro ( … ) return clob deterministic sql_macro
( [ scalar | table ] ) as begin return ' string picture '; end sql_macro; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Implicit -
Table Macros Template queries for polymorphic and parameterized views Copyright
© 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function top_n ( tab dbms_tf.table_t, num_rows integer ) return
varchar2 sql_macro as begin return 'select * from tab fetch first num_rows rows only'; end top_n; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function top_n ( tab dbms_tf.table_t, num_rows integer ) return
varchar2 sql_macro as begin return 'select * from tab fetch first num_rows rows only'; end top_n; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function top_n ( tab dbms_tf.table_t, num_rows integer ) return
varchar2 sql_macro as begin return 'select * from tab fetch first num_rows rows only'; end top_n; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function top_n ( tab dbms_tf.table_t, num_rows integer ) return
varchar2 sql_macro as begin return 'select * from tab fetch first num_rows rows only'; end top_n; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Parse time "Find & replace" -
select * from top_n ( hr.employees, 3 ) Copyright ©
2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL select * from hr.employees fetch first 3 rows only -
select * from top_n ( co.orders, :N ) Copyright ©
2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL select * from co.orders fetch first :N rows only -
select * from top_n ( sh.sales, get_n() ) Copyright ©
2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL select * from sh.sales fetch first get_n() rows only -
create function top_n_per_group ( tab dbms_tf.table_t, group_cols dbms_tf.columns_t, order_cols dbms_tf.columns_t,
num_rows integer ) return clob sql_macro as grouping_cols clob; ordering_cols clob; stmt clob; begin Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
stmt := 'with rws as ( select t.*, row_number ()
over ( partition by @group_cols order by @order_cols ) rn from tab t ) select * from rws where rn <= num_rows'; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL These are substituted These are not -
table_t columns_t What table/view/CTE columns list Macro parameters Copyright ©
2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
table_t columns_t What table/view/CTE columns list Auto-replaced? Macro
parameters Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
table_t columns_t What table/view/CTE columns list Auto-replaced? Non-existent
objects? Macro parameters Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
for col in 1 .. group_cols.count loop grouping_cols := grouping_cols
|| group_cols ( col ) || ','; end loop; for col in 1 .. order_cols.count loop ordering_cols := ordering_cols || order_cols ( col ) || ',' ; end loop; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
stmt := replace ( stmt, '@group_cols', rtrim ( grouping_cols, ','
) ); stmt := replace ( stmt, '@order_cols', rtrim ( ordering_cols, ',' ) ); return stmt; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
select * from top_n_per_group ( hr.employees, columns ( department_id ),
columns ( hire_date ), 3 ); Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
select * from top_n_per_group ( co.orders, columns ( customer_id ),
columns ( order_datetime ), :num_rows ); Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL What about SQL Ryan McGuire / Gratisography -
create function inject_me ( tab dbms_tf.table_t, dodgy_string varchar2 ) return
clob sql_macro as stmt clob; begin stmt := 'select ' || dodgy_string || ' c from tab'; return stmt; end inject_me; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL This is <null> -
create function inject_me ( tab dbms_tf.table_t, dodgy_string varchar2 ) return
clob sql_macro as stmt clob; begin stmt := 'select dodgy_string c from tab'; return stmt; end inject_me; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL This is a literal -
Finding consecutive rows CSV to rows Date generator Table macro
use-cases Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Find invoice items > EUR100 Ryan McGuire / Gratisography -
Rounding? Multiply or divide? select * from invoice_items where total_paid
( ( quantity * unit_price ) / ex_rate ) > :total Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Make it a function Ryan McGuire / Gratisography -
create function total_paid ( quantity integer, unit_price number, ex_rate number
) return number as begin return ( quantity * unit_price ) / ex_rate; end total_paid; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
select * from invoice_items where total_paid ( quantity, unit_price, ex_rate
) > :total Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Ryan McGuire / Gratisography The query is 100x slooooower! Copyright
© 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create index item_total on invoices ( total_paid ( quantity, unit_price,
ex_rate ) ) Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function total_paid ( quantity integer, unit_price number, ex_rate number
) return number as pragma udf; begin return ( quantity * unit_price ) / ex_rate; end total_paid; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL (12.1) Faster context switches -
Ryan McGuire / Gratisography It's still 10x slooower! Copyright ©
2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Scalar Macros Create reusable SQL expressions Copyright © 2021 Oracle
and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create function total_paid ( quantity integer, unit_price number, ex_rate number
) return varchar2 sql_macro ( scalar ) as begin return ' ( quantity * unit_price ) / ex_rate '; end total_paid; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL (21c) -
select * from invoice_items where total_paid ( quantity, unit_price, ex_rate
) > :total Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL No context switch Optimizer can find better plans -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Ryan McGuire / Gratisography So make all functions macros? -
create or replace function first_not_null ( v1 int, v2 int
) return int as begin return coalesce ( v1, v2 ); end first_not_null; / Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create or replace function first_not_null ( v1 int, v2 int
) return int as begin return coalesce ( v1, v2 ); end first_not_null; / Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL select first_not_null ( 1, 1/0 ) from dual; ORA-01476: divisor is equal to zero -
create or replace function first_not_null ( v1 int, v2 int
) return varchar2 sql_macro ( scalar ) as begin return ' coalesce ( v1, v2 ) '; end first_not_null; / Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
create or replace function first_not_null ( v1 int, v2 int
) return varchar2 sql_macro ( scalar ) as begin return ' coalesce ( v1, v2 ) '; end first_not_null; / Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL select first_not_null ( 1, 1/0 ) from dual; FIRST_NOT_NULL(1,1/0) 1 -
begin dbms_output.put_line ( total_paid ( 1, 2, 4 ) );
end; Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL ( quantity * unit_price ) / ex_rate -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL How do you debug these? Ryan McGuire / Gratisography -
dbms_utility.expand_sql_text ( input_sql_text => q'!select * from macro … !',
output_sql_text => stmt ) Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Only expands table macros -
DEMO Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon
* blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
SQL template Table => from Scalar => PL/SQL function Find/replace
Injection safe Handle column names manually Parse time Runtime = pure SQL => better plans & faster SQL SQL Macros Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
select * from crazy_complex_sql Copyright © 2021 Oracle and/or its
affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Can you make that reusable? Ryan McGuire / Gratisography -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Yes we can! Ryan McGuire / Gratisography -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Further reading SQL Macros - Creating parameterized views https://livesql.oracle.com/apex/livesql/file/ tutorial_KQNYERE8ZF07EZMRR6KJ0RNIR.html How to select the top-N rows per group with SQL in Oracle Database https://blogs.oracle.com/sql/post/how-to-select-the- top-n-rows-per-group-with-sql-in-oracle-database -
Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon *
blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL Ryan McGuire / Gratisography See you soon! devgym.oracle.com #AskTOMOfficeHours Copyright © 2021 Oracle and/or its affiliates | @ChrisRSaxon * blogs.oracle.com/sql * www.youtube.com/c/TheMagicofSQL