0% found this document useful (0 votes)
43 views32 pages

Dani Schnider-Function Calls in SQL

1. There is a context switch between SQL and PL/SQL whenever the function is called 2. The embedded SQL query in the function is not visible to the optimizer 3. This prevents the optimizer from pushing predicates into the SQL query

Uploaded by

Florin Nedelcu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
43 views32 pages

Dani Schnider-Function Calls in SQL

1. There is a context switch between SQL and PL/SQL whenever the function is called 2. The embedded SQL query in the function is not visible to the optimizer 3. This prevents the optimizer from pushing predicates into the SQL query

Uploaded by

Florin Nedelcu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

FUNCTION CALLS IN SQL –

BLACK BOX FOR THE OPTIMIZER?


Dani Schnider
2 ABOUT ME
DANI SCHNIDER

! Senior Principal Consultant at Trivadis


! Trainer of several Trivadis courses
! Co-Author of Books “Data Warehousing mit
Oracle” and “Data Warehouse Blueprints”

! Oracle ACE Director

! Hobby: Craft Beer Brewing

@dani_schnider danischnider.wordpress.com
3 FUNCTION CALLS IN SQL

WHERE f(attr)= expr


4 ISSUES WITH FUNCTION CALLS

Nested
SQL Calls
Cardinality
Estimation

Index
Usage

Context
Switch
SQL FUNCTION CALLS
IN WHERE CONDITION
6 SIMPLE EXAMPLE
De
mo

SELECT * ! 357 of 23941 rows (1.5%)


FROM addresses ! Cardinality = 357
WHERE ctr_code = 'GB' ! Index Range Scan is used

SELECT * ! 9344 of 23941 rows (39%)


FROM addresses ! Cardinality = 9344
WHERE ctr_code = 'DE' ! Full Table Scan is used
7 SAME EXAMPLE WITH FUNCTION CALLS

SELECT * ! 357 of 23941 rows (1.5%)


FROM addresses ! Cardinality = 239
WHERE UPPER(ctr_code) = 'GB' ! Full Table Scan is used

Cardinality Index
Estimation Usage

SELECT * ! 9344 of 23941 rows (39%)


FROM addresses ! Cardinality = 239
WHERE UPPER(ctr_code) = 'DE' ! Full Table Scan is used
8 CARDINALITY

Cardinality = Selectivity * TotalNumberOfRows

WHERE ctr_code = 'DE'


! Cardinality is derived from
o Table statistics (total number of rows)
o Column statistics (number of distinct values, histograms)
o WHERE condition
9 CARDINALITY

0.01
Cardinality = Selectivity * TotalNumberOfRows

WHERE UPPER(ctr_code)= 'DE'

! Function call is a black box for the optimizer


o Selectivity is always 1%
10 INDEX USAGE

WHERE ctr_code = 'GB'

! Index is used for strong selectivity


! Full table scan is used for weak selectivity

WHERE UPPER(ctr_code)= 'GB'

! Index cannot be used at all


! Full table scan is the only option
11 SOLUTION 1: FUNCTION-BASED INDEX
De
mo

CREATE INDEX adr_upper_ctr_code_fbi


ON addresses(UPPER(ctr_code))

! Index can be used for function calls


! Better cardinality estimation after gathering
new statistics
Cardinality Index
Estimation Usage
12 SOLUTION 2: VIRTUAL COLUMN
De
mo

ALTER TABLE addresses


ADD (upper_ctr_code VARCHAR2(30)
AS (UPPER(ctr_code)) VIRTUAL)

! Better cardinality estimation due to column


statistics on virtual column
Cardinality Index
! Index on virtual column can be created Estimation Usage
(= function-based index)
! WHERE condition can use virtual column
name or expression/function call
13 SOLUTION 3: EXTENDED STATISTICS
De
mo

SELECT dbms_stats.create_extended_stats
(USER, 'addresses', '(UPPER(ctr_code))')
FROM dual

! Additional statistics for expressions,


function calls or column correlations
Cardinality Index
! Better cardinality estimation due to Estimation Usage
additional statistics
! Function-based index not possible
14 FEATURES OVERVIEW

Function-based Index Virtual Column Extended Statistics

Cardinality Estimation

Index Usage

Virtual Column Yes Yes Yes

Hidden Column Yes No Yes


PL/SQL FUNCTION CALLS
16 EXAMPLE FOR PL/SQL FUNCTION

CREATE OR REPLACE FUNCTION f_price_category


(in_quantity order_items.quantity%TYPE,
in_price order_items.price_per_unit%TYPE) RETURN VARCHAR2
IS
v_total_price order_items.price_per_unit%TYPE;
v_category VARCHAR2(6);
BEGIN
v_total_price := in_quantity * in_price;
IF v_total_price < 100 THEN
v_category := 'low';
ELSIF v_total_price < 1000 THEN
v_category := 'medium';
ELSE
v_category := 'high';
END IF;
RETURN v_category;
END f_price_category;
17 PL/SQL FUNCTION CALL IN SQL QUERY
De
mo

SELECT COUNT(*), SUM(quantity)


FROM order_items
WHERE f_price_category(quantity, price_per_unit) = 'high'
18 HOW CAN WE IMPROVE THE QUERY PERFORMANCE?

! Function-based Index
Index
o Function must be DETERMINISTIC Usage
o Index creation takes time and disk space
o Index is not optimal choice for all type of queries

! What is the root cause for the bad response time?

Context
Switch
19 CONTEXT SWITCH BETWEEN PL/SQL AND SQL
De
mo

Source: Oracle News Connect, PL/SQL 101 series by Steven Feuerstein, https://www.oracle.com/news/connect/plsql-101-part-9.html
20 SOLUTION 1: USER DEFINED FUNCTION

! PRAGMA UDF
! Useful for PL/SQL functions mainly used in SQL statements

CREATE OR REPLACE FUNCTION f_price_category


(in_quantity order_items.quantity%TYPE,
in_price order_items.price_per_unit%TYPE) RETURN VARCHAR2
IS
PRAGMA UDF;
v_total_price order_items.price_per_unit%TYPE;
v_category VARCHAR2(6);
BEGIN
v_total_price := in_quantity * in_price;
IF v_total_price < 100 THEN
v_category := 'low';
ELSIF v_total_price < 1000 THEN
v_category := 'medium';
ELSE
v_category := 'high';
21 SOLUTION 2: SQL MACRO

! Scalar expression that can be used in SELECT, WHERE, GROUP BY and ORDER BY clauses
! Processed in SQL statement directly, no context switch to PL/SQL

CREATE OR REPLACE FUNCTION f_price_category_macro


(in_quantity VARCHAR2,
in_price VARCHAR2) RETURN VARCHAR2 SQL_MACRO (SCALAR)
IS
v_category VARCHAR2(4000);
BEGIN
v_category := 'CASE
WHEN in_quantity * in_price < 100 THEN ''low''
WHEN in_quantity * in_price < 1000 THEN ''medium''
ELSE ''high''
END';
RETURN v_category;
END f_price_category_macro;
PL/SQL FUNCTION CALLS
WITH EMBEDDED SQL
23 PL/SQL FUNCTION WITH EMBEDDED SQL CALLS
De
mo
CREATE OR REPLACE FUNCTION f_price_category
(in_quantity order_items.quantity%TYPE,
in_price order_items.price_per_unit%TYPE) RETURN VARCHAR2
IS
v_total_price order_items.price_per_unit%TYPE;
v_category VARCHAR2(6);
BEGIN
v_total_price := in_quantity * in_price;

SELECT category CATEGORY MIN_PRICE MAX_PRICE


INTO v_category SQL Query
low 0 100
FROM categories
WHERE v_total_price >= min_price medium 100 1000
AND v_total_price < max_price;
high 1000 999999
RETURN v_category;
END f_price_category;
24 HOW CAN WE IMPROVE THE QUERY PERFORMANCE?

Any Ideas?
25 WHAT IS THE PROBLEM?

SELECT COUNT(*), SUM(quantity)


FROM order_items
WHERE f_price_category(quantity, price_per_unit) = 'high'
call count cpu elapsed disk query current rows Nested
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse
Execute
1
1
0.04
0.00
0.28
0.00
0
0
2
0
0
0
0
0
SQL Calls
Fetch 1 140.51 536.93 0 1728 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 3 140.56 537.21 0 1730 0 0
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
0 0 0 SORT AGGREGATE (cr=0 pr=0 pw=0 time=23 us starts=1)
33132 33132 33132 TABLE ACCESS FULL ORDER_ITEMS (cr=2015895 pr=2 pw=0 time=559816988 us starts=1 cost=1
250 size=66264 card=8283)
********************************************************************************
SELECT CATEGORY
FROM
CATEGORIES WHERE :B1 >= MIN_PRICE AND :B1 < MAX_PRICE

call count cpu elapsed disk query current rows


------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 335796 4.26 10.93 0 0 0 0
Fetch 335796 8.52 22.10 0 2014772 0 335795
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 671593 12.78 33.04 0 2014772 0 335795
Parsing user id: 90 (recursive depth: 1)
Rows (1st) Rows (avg) Rows (max) Row Source Operation
---------- ---------- ---------- ---------------------------------------------------
1 1 1 TABLE ACCESS FULL CATEGORIES (cr=6 pr=0 pw=0 time=85 us starts=1 cost=3 size=12 card=1)
27 SOLUTION 1: REPLACE FUNCTION CALL WITH SUBQUERY

SELECT COUNT(*), SUM(quantity)


FROM order_items
WHERE (SELECT category
FROM categories
WHERE quantity * price_per_unit >= min_price
AND quantity * price_per_unit < max_price) = 'high'
28 SOLUTION 2: JOIN WITH LOOKUP TABLE
De
mo

SELECT COUNT(*), SUM(quantity)


FROM order_items oi, categories cat
WHERE cat.category = 'high'
AND oi.quantity * oi.price_per_unit >= cat.min_price
AND oi.quantity * oi.price_per_unit < cat.max_price
29 CONCLUSION

SQL Functions PL/SQL Functions

No problem at all in SELECT list Same rules as for SQL functions

Try to avoid them in SELECT list and WHERE


Try to avoid them in WHERE condition
condition

Solve cardinality estimation issues: Solve context switch issues:


• Virtual Columns • PRAGMA UDF
• Extended Statistics • SQL MACRO (SCALAR)

For queries with strong selectivity: Never use PL/SQL function calls with embedded
• create Function-based Index SQL queries!
(or index on virtual column) • Try to rewrite the SQL statement
danischnider.wordpress.com

https://danischnider.wordpress.com/2022/05/15/
performance-tips-pl-sql-functions-in-sql-queries/

https://danischnider.wordpress.com/2022/02/28/
performance-tips-function-calls-in-where-
conditions/
https://asktom.oracle.com/pls/apex/f?p=100:551::::551:P551_CLASS_ID,P551_INVITED:17183

You might also like