| Lesson 3 |
Oracle query override functions |
| Objective |
Use BIFs to override Oracle SQL queries |
Oracle Query Override Functions
Oracle built-in functions (BIFs) allow you to override the default behavior of SQL queries by specifying directives that reformat, recalculate, and transform data within the query itself rather than in application code. This approach pushes computation into the Oracle engine where it executes at native speed, eliminates round trips between the database and the application tier, and produces results that are already formatted for display.
Overriding in this context means replacing application-layer logic — loops, arrays, conditional blocks, date calculators, format converters — with a declarative SQL function call that Oracle resolves as part of query execution. The result is faster queries, simpler code, and reduced maintenance overhead across every application that uses the same SQL.
Example 1: Replacing Array Logic with Aggregate Functions
Consider the requirement to determine the minimum, average, and maximum salary from the employee table. Without BIFs, the application must execute a full SELECT of every salary value, read every row into an internal array, iterate through the array to compute the three values, and then return them to the caller. This requires multiple round trips, application-layer memory allocation, and procedural code that must be maintained separately from the SQL.
Using BIFs, the entire procedure collapses into a single SQL statement:
SELECT
MIN(salary),
AVG(salary),
MAX(salary)
FROM
employee;
The MIN, MAX, and AVG built-in functions replace the array iteration with a single pass through the data inside the Oracle engine. No arrays, no loops, no application-layer memory allocation. Oracle computes all three values and returns the result set directly.
Example 2: Date Arithmetic with MONTHS_BETWEEN
Consider the requirement to compute the number of months from today until an employee is eligible to retire. Implementing this in application code requires date parsing, calendar arithmetic that accounts for month-length variation, and fractional month handling — a non-trivial procedural program.
Using the MONTHS_BETWEEN BIF, the calculation executes in a single SQL call:
SELECT
MONTHS_BETWEEN(
TO_DATE('02-02-2003', 'MM-DD-YYYY'),
SYSDATE
) AS months
FROM DUAL;
Months
----------
49.0322581
MONTHS_BETWEEN computes the fractional number of months between two dates natively within Oracle. TO_DATE converts the string literal to a DATE value. SYSDATE provides the current database server date. The entire calculation executes in the library cache with no application-layer date arithmetic required.
Key Techniques for Overriding Query Behavior with BIFs
1. Aggregate and Analytic Functions
Aggregate functions (MIN, MAX, AVG, SUM, COUNT) replace application-layer collection and computation. Analytic functions (ROW_NUMBER, RANK, LEAD, LAG) compute window-based values across ordered result sets without requiring self-joins or correlated subqueries:
-- Rank employees by salary within each department
SELECT
emp_name,
department_id,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank
FROM employee;
Without the RANK analytic function, producing this result would require a correlated subquery for each row or a self-join — both significantly more expensive operations.
2. Null Handling and Conditional Logic
NVL, NVL2, COALESCE, DECODE, and CASE allow conditional transformation of result values within the query, eliminating null-propagation errors and conditional logic in the application layer:
-- Replace null commission with zero; decode region code to readable name
SELECT
emp_name,
NVL(commission, 0) AS commission,
DECODE(region_code,
'NE', 'Northeast',
'SE', 'Southeast',
'MW', 'Midwest',
'Other') AS region
FROM employee;
The NVL function ensures that rows with null commission values return zero rather than propagating a null into downstream calculations. DECODE translates the raw region code into a readable label in a single pass — no application-layer lookup table required.
3. Date and Numeric Reformatting
4. Scalar Subquery Caching
Oracle automatically caches the results of scalar subqueries within a single query execution. When a correlated scalar subquery returns the same result for the same input value across multiple rows, Oracle returns the cached result rather than re-executing the subquery:
-- Oracle caches the department_name lookup per unique dept_id
SELECT
emp_name,
(SELECT department_name
FROM departments d
WHERE d.department_id = e.department_id) AS dept_name
FROM employee e;
If the employee table has 50,000 rows across 20 departments, Oracle executes the subquery at most 20 times — once per unique department_id — rather than 50,000 times.
5. DETERMINISTIC User-Defined Functions
The DETERMINISTIC keyword on a user-defined function tells Oracle that the function always returns the same output for the same input. This allows Oracle to cache the function result within a query, calling the function once per unique input value rather than once per row:
CREATE OR REPLACE FUNCTION get_tax_rate(p_state IN VARCHAR2)
RETURN NUMBER
DETERMINISTIC
IS
BEGIN
RETURN CASE p_state
WHEN 'CA' THEN 0.0725
WHEN 'TX' THEN 0.0625
ELSE 0.05
END;
END;
Without DETERMINISTIC, Oracle calls this function once for every row in the result set. With DETERMINISTIC, Oracle calls it once per unique state value and reuses the cached result for subsequent rows with the same input — a potentially large reduction in function call overhead for queries against large tables with low-cardinality columns.
6. Cross-Session PL/SQL Function Result Cache (Oracle 11g+)
The RESULT_CACHE clause caches function results across sessions in the shared pool. Any session that calls the function with the same parameters receives the cached result without executing the function body:
CREATE OR REPLACE FUNCTION get_country_name(p_code IN VARCHAR2)
RETURN VARCHAR2
RESULT_CACHE
IS
v_name VARCHAR2(100);
BEGIN
SELECT country_name INTO v_name
FROM countries
WHERE country_code = p_code;
RETURN v_name;
END;
This is most effective for functions that query reference data that changes infrequently — country codes, currency codes, product categories, and similar lookup tables. The cache is automatically invalidated when the underlying table data changes.
7. Pipelined Table Functions
Pipelined table functions stream large result sets row by row rather than materializing the entire result set in memory before returning it. They are useful for transforming large datasets within the SQL layer when the transformation logic is too complex for inline BIF calls:
-- Call a pipelined function as a table source
SELECT *
FROM TABLE(transform_employee_data(
CURSOR(SELECT * FROM employee WHERE hire_date > DATE '2020-01-01')
));
The pipelined function returns rows to the caller as they are produced rather than waiting for the entire computation to complete — reducing memory requirements and improving time-to-first-row for large result sets.
8. Function-Based Indexes
When a BIF is applied to an indexed column in a WHERE clause predicate, Oracle cannot use the standard column index and defaults to a full table scan. A function-based index resolves this by indexing the result of the function expression:
-- Create a function-based index on UPPER(emp_name)
CREATE INDEX emp_name_upper_idx ON employee(UPPER(emp_name));
-- This predicate can now use the function-based index
SELECT emp_name
FROM employee
WHERE UPPER(emp_name) = 'SMITH';
Without the function-based index, the predicate
WHERE UPPER(emp_name) = 'SMITH' forces Oracle to apply UPPER to every row in the table during the scan. The function-based index pre-computes UPPER(emp_name) for each row at index creation time, making the index lookup as fast as a standard equality search.
Limitations
Not all BIF-based override approaches are possible or advisable:
- Core built-in functions cannot be globally redefined — creating a synonym or user-defined function with the same name as a core BIF (USER, SYSDATE, UPPER) in a way that affects all sessions causes recursion errors or unpredictable behavior
- Functions on indexed columns in WHERE predicates suppress index use — wrapping an indexed column in a BIF in a filter predicate forces a full table scan unless a function-based index exists for that specific expression
- Non-deterministic functions cannot be used in query-rewrite materialized views — functions like SYSDATE or USER return different values on each execution and cannot be used in materialized views that support automatic query rewrite
Summary
Oracle BIFs override the need for application-layer computation by moving data transformation, calculation, reformatting, and null handling into the SQL engine. The techniques range from simple aggregate substitution — replacing procedural loops with MIN, AVG, and MAX — through advanced performance patterns including scalar subquery caching, DETERMINISTIC function caching, cross-session result caching, pipelined table functions, and function-based indexes. Used correctly, BIFs produce queries that are faster, shorter, more readable, and easier to maintain than equivalent procedural implementations in the application tier.
The next lesson examines how the null value clause can be used to improve Oracle query performance and accuracy.
