Oracle Database 21c 文档存储(JSON)新功能
Posted 不剪发的Tony老师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 数据的索引和性能优化,可以参考官方文档。
以上是关于Oracle Database 21c 文档存储(JSON)新功能的主要内容,如果未能解决你的问题,请参考以下文章
[译] Data Guard:Oracle Database 21c 中的 PREPARE DATABASE FOR DATA GUARD 命令
[译] Oracle Database 21c 中的空间管理增强功能
Oracle LiveLabs实验:Install Oracle Database 21c
Oracle LiveLabs实验:Manage Database Instance and Memory for Oracle Database 21c