Emulating SQL FILTER with Oracle JSON Aggregate Functions

0
1Кб

How to implement FILTER semantics with Oracle JSON aggregate functions

A cool standard SQL:2003 feature is the aggregate FILTER clause, which is supported natively by at least these RDBMS:

  • ClickHouse
  • CockroachDB
  • DuckDB
  • Firebird
  • H2
  • HSQLDB
  • PostgreSQL
  • SQLite
  • Trino
  • YugabyteDB

The following aggregate function computes the number of rows per group which satifsy the FILTER clause:

SELECT
  COUNT(*) FILTER (WHERE BOOK.TITLE LIKE 'A%'),
  COUNT(*) FILTER (WHERE BOOK.TITLE LIKE 'B%'),
  ...
FROM BOOK

This is useful for pivot style queries, where multiple aggregate values are computed in one go. For most basic types of aggregate function, it can be emulated simply by using CASE expressions, because standard aggregate functions ignore NULL values when aggregating. The following is equivalent to the above, in all RDBMS:

SELECT
  COUNT(CASE WHEN BOOK.TITLE LIKE 'A%' THEN 1 END),
  COUNT(CASE WHEN BOOK.TITLE LIKE 'B%' THEN 1 END),
  ...
FROM BOOK

What if we’re aggregating JSON?

Things are a bit different when aggregating JSON. Look at the following example, where we don’t want to count the books, but list them in a JSON array, or object:

SELECT
  JSON_ARRAYAGG(BOOK.TITLE)
    FILTER (WHERE BOOK.LANGUAGE_ID = 1),
  JSON_OBJECTAGG('id-' || BOOK.ID, BOOK.TITLE)
    FILTER (WHERE BOOK.LANGUAGE_ID = 2),
  ...
FROM BOOK

Things are different with these collection aggregate functions, because NULL values are actually interesting there, so we want to list them in the resulting JSON document. Assuming there are books with a NULL title, we might get:

|JSON_ARRAYAGG                |JSON_OBJECTAGG                      |
|-----------------------------|------------------------------------|
|["1984", "Animal Farm", null]|{ "id-4" : "Brida", "id-17" : null }|

This makes emulating the FILTER clause (e.g. on Oracle) much harder, because we cannot just use ABSENT ON NULL like this:

SELECT
  JSON_ARRAYAGG(
    CASE WHEN T_BOOK.LANGUAGE_ID = 1 THEN T_BOOK.TITLE END 
    ABSENT ON NULL
  ),
  JSON_OBJECTAGG(
    'id-' || T_BOOK.ID, 
    CASE WHEN T_BOOK.LANGUAGE_ID = 2 THEN T_BOOK.TITLE END
    ABSENT ON NULL
  )
FROM T_BOOK;

Because now, the legitimate null titled books are missing and we’re getting this instead:

|JSON_ARRAYAGG         |JSON_OBJECTAGG  |
|----------------------|----------------|
|["1984","Animal Farm"]|{"id-4":"Brida"}|

We cannot use NULL ON NULL either, because that would just turn the FILTER semantics into a mapping semantics, and produce too many values:

|JSON_ARRAYAGG                        |JSON_OBJECTAGG                                                   |
|-------------------------------------|-----------------------------------------------------------------|
|["1984","Animal Farm",null,null,null]|{"id-1":null,"id-4":"Brida","id-3":null,"id-2":null,"id-17":null}|

E.g. while id-3 and id-2 values are NULL because the FILTER emulating CASE expression maps them to NULL, the id-17 value really has a NULL title.

Workaround: Wrap data in an array

As a workaround, we can:

  • Wrap legitimate data into an array
  • Apply ABSENT ON NULL to remove rows due to the FILTER emulation
  • Unwrap data again from the array

For the unwrapping, we’re going to be using JSON_TRANSFORM:

SELECT
  JSON_TRANSFORM(
    JSON_ARRAYAGG(
      CASE 
        WHEN T_BOOK.LANGUAGE_ID = 1 

        -- Wrap legitimate data into an array, including nulls
        THEN JSON_ARRAY(T_BOOK.TITLE NULL ON NULL)
      END 

      -- Remove NULLs due to FILTER emulation
      ABSENT ON NULL
    ),

    -- Unwrap data gain from the array
    NESTED PATH '$[*]' (REPLACE '@' = PATH '@[0]')
  ),

  JSON_TRANSFORM(
    JSON_OBJECTAGG(
      'id-' || T_BOOK.ID, 
      CASE 
        WHEN T_BOOK.LANGUAGE_ID = 2 

        -- Wrap legitimate data into an array, including nulls
        THEN JSON_ARRAY(T_BOOK.TITLE NULL ON NULL)
      END

      -- Remove NULLs due to FILTER emulation
      ABSENT ON NULL
    ),

    -- Unwrap data gain from the array
    NESTED PATH '$.*' (REPLACE '@' = PATH '@[0]')
  )
FROM T_BOOK;

jOOQ support

jOOQ 3.20 will implement the above emulations for:

This way, you can continue to transparently use FILTER on any aggregate function, also in Oracle.

Спонсоры
Спонсоры
Спонсоры
Спонсоры
Спонсоры
Поиск
Спонсоры
Virtuala FansOnly
CDN FREE
Cloud Convert
Категории
Больше
Другое
未上市股票如何買賣:投資攻略五大要點讓您買賣順利
未上市股票是指那些沒有在證券交易所或櫃檯買賣中心掛牌的股票, 包括未公開發行公司的股票和公開發行但沒有交易平台的股票。 未上市股票有時會被視為高風險高報酬的投資標的,但也有可...
От Shabirkhan 7sk 2025-09-12 07:21:03 0 476
Другое
【新訊息】昇陽電池股價|未上市股票即時行情與安全交易平台 - IPO贏家
昇陽電池未上市股票即時報價查詢!最新行情更新、安全交割保障、完整公司資料與投資討論區。專業平台嚴格把關,降低未上市股票交易風險,點擊查看詳情! 昇陽電池股價 與 三信商銀股價 投資觀察...
От Shabirkhan 7sk 2025-09-17 06:19:21 0 506
Другое
Psoriatic Arthritis Treatment Market Size to Reach USD 22.4 Billion by 2032
According to a new report published by Introspective Market Research, Psoriatic Arthritis...
От Amit Patil 2025-12-24 06:38:32 0 673
Art
Tomb Raider VR – Der ultimative Test für eine immersive Erfahrung
Tomb Raider VR, immersive Erfahrung, Virtual Reality, realistische Abenteuer, Gaming-Kritik, Lara...
От Mia Luisa 2025-08-09 09:05:23 1 398
Другое
Why Geothermal Petrophysics Is the Future of Renewable Energy Exploration
Advancing Renewable Energy Through Geothermal Petrophysics We are witnessing a decisive...
От Emma Marie 2025-12-23 11:40:01 0 842
Спонсоры
Virtuala FansOnly https://virtuala.site