QUALIFY filter analytical function without subquery in Oracle Database 23ai/26ai

If you’ve ever written analytics queries with RANK(), DENSE_RANK(), ROW_NUMBER(), AVG() OVER(...), etc., you know the pain:

  • you compute a window value in the SELECT
  • then you can’t reference it in WHERE
  • so you wrap everything in a subquery… just to filter on that computed value

Oracle Database 26ai fixes this nicely with QUALIFY.

SELECT
  ...
  RANK() OVER (ORDER BY revenue DESC) AS rev_rank
FROM test_tbl
QUALIFY rev_rank <= 5
ORDER BY rev_rank, name;

QUALIFY is evaluated after window functions are computed, so you can filter on them directly — no nesting needed.

Lets create test data and do some tests

DROP TABLE IF EXISTS test_tbl CASCADE CONSTRAINTS;

CREATE TABLE test_tbl
(
id NUMBER,
name VARCHAR2(30) NOT NULL,
val1 NUMBER NOT NULL,
val2 NUMBER NOT NULL,
val3 NUMBER NOT NULL
);

INSERT INTO test_tbl VALUES (1, 'Name1', 100, 10, 20);
INSERT INTO test_tbl VALUES (2, 'Name2', 100, 10, 30);
INSERT INTO test_tbl VALUES (3, 'Name3', 100, 20, 30);
INSERT INTO test_tbl VALUES (4, 'Name4', 100, 20, 30);
INSERT INTO test_tbl VALUES (5, 'Name5', 200, 30, 30);
INSERT INTO test_tbl VALUES (6, 'Name6', 200, 30, 30);
INSERT INTO test_tbl VALUES (7, 'Name7', 200, 40, 30);
INSERT INTO test_tbl VALUES (8, 'Name8', 200, 40, 20);

COMMIT;

Check what we have inside the table with some analytical functions

SELECT
id, name, val1, val2,
RANK() OVER (ORDER BY val1 DESC) AS val1_rank,
RANK() OVER (ORDER BY val2 DESC) AS val2_rank,
RANK() OVER (ORDER BY val3 DESC) AS val3_rank
FROM test_tbl
ORDER BY 1;

ID NAME VAL1 VAL2 VAL3 VAL1_RANK VAL2_RANK VAL3_RANK -- ----- ----- ----- ----- ---------- ---------- ---------- 1 Name1 100 10 20 5 7 7 2 Name2 100 10 30 5 7 1 3 Name3 100 20 30 5 5 1 4 Name4 100 20 30 5 5 1 5 Name5 200 30 30 1 3 1 6 Name6 200 30 30 1 3 1 7 Name7 200 40 30 1 1 1 8 Name8 200 40 20 1 1 7

To filter records by columns with analytical functions you had to use before 26ai extra query

SELECT * FROM (
SELECT
id, name, val1, val2, val3,
RANK() OVER (ORDER BY val1 DESC) AS val1_rank,
RANK() OVER (ORDER BY val2 DESC) AS val2_rank,
RANK() OVER (ORDER BY val3 DESC) AS val3_rank
FROM test_tbl
)
WHERE val1_rank> 1 AND val3_rank < 2
ORDER BY 1;
ID NAME VAL1 VAL2 VAL3 VAL1_RANK VAL2_RANK VAL3_RANK -- ------ ----- ----- ----- ---------- ---------- ---------- 2 Name2 100 10 30 5 7 1 3 Name3 100 20 30 5 5 1 4 Name4 100 20 30 5 5 1

New solution is very elegant

SELECT
id, name, val1, val2, val3,
RANK() OVER (ORDER BY val1 DESC) AS val1_rank,
RANK() OVER (ORDER BY val2 DESC) AS val2_rank,
RANK() OVER (ORDER BY val3 DESC) AS val3_rank
FROM test_tbl
QUALIFY val1_rank> 1 AND val3_rank < 2
ORDER BY 1
ID NAME VAL1 VAL2 VAL3 VAL1_RANK VAL2_RANK VAL3_RANK -- ------ ----- ----- ----- ---------- ---------- ---------- 2 Name2 100 10 30 5 7 1 3 Name3 100 20 30 5 5 1 4 Name4 100 20 30 5 5 1
 

Execution order (mental model)

When you read a query with QUALIFY, this is the useful “pipeline”:

FROM test_tbl                   ← Get rows
WHERE ... (if present)          ← Filter base rows
... OVER(...)                   ← Compute window functions
QUALIFY ...                     ← Filter using those results
ORDER BY ...                    ← Sort final output

Have a fun 🙂

  Tomasz

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.