Oracle Database 21c 文档存储(JSON)新功能

大家好,我是只谈技术不剪发的 Tony 老师。

Oracle Database 21c 引入了许多 JSON 相关的增强功能,包括原生的 JSON 数据类型以及相关的处理函数。

本文就来介绍一下具体的内容,如果你觉得文章有用,欢迎评论📝、点赞👍、推荐🎁

JSON 数据类型

Oracle Database 20c 预览版引入了原生的 JSON 数据类型,并且改善了 JSON 数据处理的性能。如今在 Oracle Database 21c 正式版本中已经可以使用该功能了。

Oracle 提供的 JSON 数据类型是一个优化的二进制 JSON,被称为 OSON(Oracle JSON)。新的数据类型可以为 20c/21c 以后的数据库服务器和客户端提供更快的查询和 DML 性能。

创建 JSON 字段的方法和其他数据类型类似,例如:

CREATE TABLE  t1 (
  id         INTEGER GENERATED ALWAYS AS IDENTITY,
  json_data  JSON,
  CONSTRAINT t1_pk PRIMARY KEY (id)
);

我们可以利用各种 JSON 构造函数将不同的数据类型转换为 JSON 数据,例如:

declare
  l_varchar2  varchar2(32767);
  l_clob      clob;
  l_blob      blob;
begin
  l_varchar2 := '{"fruit":"apple","quantity":10}';
  l_clob     := '{"fruit":"orange","quantity":20}';
  l_blob     := utl_raw.cast_to_raw('{"fruit":"banana","quantity":30}');

  insert into t1 (json_data) values (json(l_varchar2));
  insert into t1 (json_data) values (json(l_clob));
  insert into t1 (json_data) values (json(l_blob));
  commit;
end;
/

虽然某些数据类型可以不用指定 JSON 构造函数,通过隐式类型转换为 JSON 数据,但是推荐显式指定这种类型转换。

JSON 数据使用二进制格式进行存储,所以直接查询这种字段返回的结果不易使用。例如:

select * from t1;

        ID JSON_DATA
---------- -----------------------------------------------------------------
         1 7B226672756974223A226170706C65222C227175616E74697479223A31307D
         2 7B226672756974223A226F72616E6765222C227175616E74697479223A32307D
         3 7B226672756974223A2262616E616E61222C227175616E74697479223A33307D

查询 JSON 数据

Oracle Database 19c 引入了 JSON_SERIALIZE 函数,用于将 JSON 数据(VARCHAR2、CLOB、BLOB)转换为文本格式。该函数现在也可以支持原生 JSON 数据类型。例如:

select id, json_serialize(json_data) as json_data from t1;

        ID JSON_DATA
---------- --------------------------------------------------
         1 {"fruit":"apple","quantity":10}
         2 {"fruit":"orange","quantity":20}
         3 {"fruit":"banana","quantity":30}

我们同样可以使用之前版本中的 SQL/JSON 函数处理 JSON 数据类型。例如,以下是一个 JSON_VALUE 函数示例:

select a.id,
       json_value(a.json_data, '$.fruit') as fruit,
       json_value(a.json_data, '$.quantity' returning number) as quantity
from t1 a
order by 1;

        ID FRUIT        QUANTITY
---------- ---------- ----------
         1 apple              10
         2 orange             20
         3 banana             30

以下是一个 JSON_QUERY 函数的示例:

select a.id,
       json_query(a.json_data, '$.fruit' returning varchar2) as fruit,
       json_query(a.json_data, '$.quantity' returning varchar2) as quantity
from t1 a
order by 1;

        ID FRUIT      QUANTITY
---------- ---------- ----------
         1 "apple"    10
         2 "orange"   20
         3 "banana"   30

以下是一个 JSON_TABLE 函数的示例:

select a.id,
       jt.fruit,
       jt.quantity
from t1 a,
     json_table(a.json_data, '$'
         columns (fruit    varchar2(10 char) path '$.fruit',
                  quantity number path '$.quantity')) jt;

        ID FRUIT        QUANTITY
---------- ---------- ----------
         1 apple              10
         2 orange             20
         3 banana             30

我们也可以使用点号标记法查询 JSON 数据。注意,JSON 数据是二进制,我们需要使用 JSON_SERIALIZE 函数将它转换为本文数据,例如:

select a.id,
       json_serialize(a.json_data.fruit) as fruit,
       json_serialize(a.json_data.quantity) as quantity
from t1 a
order by 1;

        ID FRUIT      QUANTITY
---------- ---------- ----------
         1 "apple"    10
         2 "orange"   20
         3 "banana"   30

除了这些函数之外,Oracle Database 21c 还增加了几个新的 JSON 函数。

JSON_SCALAR 函数

JSON_SCALAR 函数可以基于一个 SQL 标量值创建一个 JSON 数据。例如:

select json_scalar(1) as scalar_number,
       json_scalar('string') as scalar_string,
       json_scalar(date '2020-12-13') as scalar_date
from   dual;

SCALAR_NUMBER        SCALAR_STRING        SCALAR_DATE
-------------------- -------------------- --------------------------------------------------
31                   22737472696E6722     22323032302D31322D31335430303A30303A303022

我们可以利用 JSON_SERIALIZE 函数将 JSON 对象再转换为文本数据,验证 JSON_SCALAR 函数的结果:

select json_serialize(json_scalar(1)) as scalar_number,
       json_serialize(json_scalar('string')) as scalar_string,
       json_serialize(json_scalar(date '2020-12-13')) as scalar_date
from   dual;

SCALAR_NUMBER        SCALAR_STRING        SCALAR_DATE
-------------------- -------------------- --------------------------------------------------
1                    "string"             "2020-12-13T00:00:00"

JSON_TRANSFORM 函数

JSON_TRANSFORM 函数可以简化 JSON 数据的修改操作。Oracle 19c 引入了 JSON_MERGEPATCH 函数,用于更新 JSON 文档。最新的 JSON_TRANSFORM 函数提供了更多的功能,而且使用更加简单。

以下语句用于更新 JSON 文档中的 lastUpdated 节点:

UPDATE t SET jcol = JSON_TRANSFORM(jcol, SET '$.lastUpdated' = SYSTIMESTAMP)

以下语句在返回数据到客户端之前删除文档中的 ssn 节点:

SELECT JSON_TRANSFORM (jcol, REMOVE '$.ssn') 
FROM t 
WHERE

JSON_TRANSFORM 函数支持的操作包括 SET、INSERT、APPEND、REMOVE、RENAME、REPLACE 以及 KEEP,并且可以在一个函数中使用多个操作。例如:

UPDATE t
SET json_data = json_transform(json_data,
                                  SET '$.created_date' = systimestamp,
                                  SET '$.updated_date' = systimestamp,
                                  RENAME '$.fruit' = 'fruit_type',
                                  REPLACE '$.quantity' = 20
                                  RETURNING json)
WHERE id = 1;

关于 JSON_TRANSFORM 函数的详细介绍,可以参考官方文档

支持 JSON 文档的 PL/SQL 对象类型

对于 Oracle 21c,使用 JSON_TRANSFORM 函数操作 JSON 文档更加方便。但是,我们仍然可以使用 PL/SQL 对象类型实现 JSON 文档操作。

JSON_OBJECT_T 构造函数可以支持最新的 JSON 数据类型。以下示例从 T1 表中返回一个 JSON 数据并将它转换为 JSON_OBJECT_T 类型,随后使用 PL/SQL JSON 对象类型处理该数据:

set serveroutput on
declare
  l_json  JSON;
  l_obj   json_object_t;
begin
  -- Get the JSON data.
  select json_data
  into   l_json
  from   t1
  where  id = 1;
  
  -- Create a JSON_OBJECT_T object and output the contents.
  l_obj := json_object_t(l_json);
  dbms_output.put_line('l_obj.stringify = ' || l_obj.stringify);
end;
/

l_obj.stringify = {"fruit":"apple","quantity":10}

PL/SQL procedure successfully completed.

SQL>

一旦我们在 JSON_OBJECT_T 对象中处理完 JSON 数据,就可以使用 TO_JSON 成员函数将其转换回 JSON 数据类型,并且用于更新数据库中的值:

set serveroutput on
declare
  l_json  JSON;
  l_obj   json_object_t;
begin
  -- Get the JSON data.
  select json_data
  into   l_json
  from   t1
  where  id = 1;
  
  -- Create a JSON_OBJECT_T object and output the contents.
  l_obj := json_object_t(l_json);
  dbms_output.put_line('l_obj.stringify = ' || l_obj.stringify);

  -- Convert it back to JSON.
  l_json := l_obj.to_json;
  dbms_output.put_line('l_json = ' || json_serialize(l_json));
  
  -- Update the JSON column.
  update t1
  set    json_data = l_json
  where  id = 1;
end;
/

l_obj.stringify = {"fruit":"apple","quantity":10}
l_json = {"fruit":"apple","quantity":10}

PL/SQL procedure successfully completed.

SQL>

JSON_OBJECT_T 对象提供了许多成员函数,包括支持最新 JSON 数据类型的重载函数。具体内容可以参考官方文档

JSON 文档的多值函数索引

Oracle 多值函数索引支持 JSON 文档中多个标量值的索引,可以提高 JSON_EXISTS 查询条件的性能。

例如:

create table t1 (
  id         number generated always as identity,
  name       varchar2(20),
  json_data  json,
  constraint t1_pk primary key (id)
);


begin
  insert into t1 (name, json_data)
  values ('book 1', json('{"words":[
                             {"word":"apple","pages":[5, 10, 15, 20, 25, 30]},
                             {"word":"orange","pages":[10, 20, 30, 40, 50, 60]}
                          ]}'));

  insert into t1 (name, json_data)
  values ('book 2', json('{"words":[
                             {"word":"apple","pages":[1, 6, 11, 16, 21, 26]},
                             {"word":"orange","pages":[11, 21, 31, 41, 51, 61]}
                          ]}'));

  commit;
  dbms_stats.gather_table_stats(null, 't1');
end;
/

在没有索引的情况下,我们查看以下语句的执行计划:

alter system flush shared_pool;
set autotrace trace explain;

select *
from   t1
where  json_exists(json_data, '$.words.pages?(@.number() == 40)');

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   183 |    36   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   183 |    36   (0)| 00:00:01 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(JSON_EXISTS2("JSON_DATA" /*+ LOB_BY_VALUE */  FORMAT OSON
              , '$.words.pages?(@.number() == 40)' FALSE ON ERROR)=1)

没有索引的情况下只能通过全表扫描获取数据。

然后我们基于 pages 数组节点创建一个多值索引:

create multivalue index t1_mvi on t1 t
  (t.json_data.words.pages.number());

然后再次查看执行计划:

alter system flush shared_pool;
set autotrace trace explain;

select *
from   t1
where  json_exists(json_data, '$.words.pages?(@.number() == 40)');

Execution Plan
----------------------------------------------------------
Plan hash value: 1854116654

----------------------------------------------------------------------------------------------
| Id  | Operation                           | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |        |     1 |   185 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T1     |     1 |   185 |     2   (0)| 00:00:01 |
|   2 |   HASH UNIQUE                       |        |     1 |   185 |            |          |
|*  3 |    INDEX RANGE SCAN (MULTI VALUE)   | T1_MVI |     1 |       |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access(JSON_QUERY("JSON_DATA" /*+ LOB_BY_VALUE */  FORMAT OSON ,
              '$.words.pages.number()' RETURNING NUMBER ASIS  WITHOUT ARRAY WRAPPER ERROR ON ERROR
              NULL ON EMPTY NULL ON MISMATCH MULTIVALUE)=40)

执行计划显示使用了 INDEX RANGE SCAN (MULTI VALUE) 索引范围扫描。

关于 JSON 数据的索引和性能优化,可以参考官方文档

  • 3
    点赞
  • 6
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:酷酷鲨 设计师:CSDN官方博客 返回首页
评论

打赏作者

不剪发的Tony老师

为 5 个 C 币而折腰。

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值