本文为Oracle LiveLabs中实验Multitenant Advanced Capabilities的过程记录。
实验 1:环境设置
用putty连接到虚机即可,用户名为opc。
确认数据库和监听均已启动:
[opc@ll120088-instance-db19c ~]$ ps -ef|grep smon
oracle 3581 1 0 05:50 ? 00:00:00 ora_smon_ORCL
[opc@ll120088-instance-db19c ~]$ ps -ef|grep tnsl
oracle 2152 1 0 05:49 ? 00:00:00 /u01/app/oracle/product/19c/dbhome_1/bin/tnslsnr LISTENER -inherit
切换到oracle用户,验证数据库可以登录:
sql
$ sqlplus system/Ora_DB4U@localhost:1521/orclpdb
SQL*Plus: Release 19.0.0.0.0 - Production on Thu Dec 5 06:02:34 2024
Version 19.7.0.0.0
Copyright (c) 1982, 2020, Oracle. All rights reserved.
ERROR:
ORA-28002: the password will expire within 7 days
Last Successful login time: Tue Aug 11 2020 18:36:35 +00:00
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.7.0.0.0
SQL>
实验 2:示例Schema设置
任务:安装示例数据
下载并解压:
bash
wget https://github.com/oracle/db-sample-schemas/archive/v19c.zip
unzip v19c.zip
cd db-sample-schemas-19c
perl -p -i.bak -e 's#__SUB__CWD__#'$(pwd)'#g' *.sql */*.sql */*.dat
安装:
bash
$ sqlplus system/Ora_DB4U@localhost:1521/orclpdb
SQL>
@mksample Ora_DB4U Ora_DB4U Ora_DB4U Ora_DB4U Ora_DB4U Ora_DB4U Ora_DB4U Ora_DB4U users temp /home/oracle/db-sample-schemas-19c/ localhost:1521/orclpdb
其实这个过程和github上的Sample schema安装过程是完全一样的,我怀疑其中的数据不一样。
实验 3:Oracle DB 中的 JSON
简介
该实验室将探索 JSON 数据以及如何使用 SQL 和 PL/SQL 处理存储在 Oracle Database 19c 中的 JSON 数据。
JavaScript 对象表示法 (JSON) 在标准 ECMA-404(JSON 数据交换格式)和 ECMA-262(ECMAScript 语言规范,第三版)中定义。ECMAScript 的 JavaScript 方言是一种在 Web 浏览器和 Web 服务器中广泛使用的通用编程语言。Oracle 数据库原生支持 JavaScript 对象表示法 (JSON) 数据,并具有关系数据库功能,包括事务、索引、声明性查询和视图。
观看此视频以了解有关 Oracle 数据库中 JSON 的更多信息。
基于以 JSON 文档形式持久保存应用程序数据的无模式开发让您能够快速响应不断变化的应用程序需求。您可以更改和重新部署应用程序,而无需更改其使用的存储模式。SQL 和关系数据库为复杂的数据分析和报告提供灵活的支持,以及坚如磐石的数据保护和访问控制。NoSQL 数据库通常并非如此,过去 NoSQL 数据库通常与 JSON 的无模式开发相关联。Oracle 数据库为 JSON 数据提供了 SQL 和关系数据库的所有优势,您可以像存储和操作任何其他类型的数据库数据一样,以相同的方式和信心存储和操作 JSON 数据。
任务 1:环境准备
连接到数据库,并为OE用户赋权:
sql
sqlplus sys/Ora_DB4U@localhost:1521/orclpdb as SYSDBA
SQL>
GRANT SELECT ON v_$session TO oe;
GRANT SELECT ON v_$sql_plan_statistics_all TO oe;
GRANT SELECT ON v_$sql_plan TO oe;
GRANT SELECT ON v_$sql TO oe;
GRANT ALTER SYSTEM TO oe;
创建网络访问控制列表,因为我们的数据库需要连接到 Web 服务并通过 HTTP 检索信息,而这需要访问控制列表 (ACL)。此 ACL 可由具有 SYSDBA 权限的用户(在本例中为 SYS)通过执行以下步骤从名为 ORCLPDB(即当前PDB)可插拔数据库创建。
sql
begin
DBMS_NETWORK_ACL_ADMIN.append_host_ace (
host => 'api.geonames.org',
ace => xs$ace_type(privilege_list => xs$name_list('http','connect','resolve'),
principal_name => 'OE',
principal_type => xs_acl.ptype_db));
end;
/
以 OE 用户身份连接到可插入数据库 ORCLPDB。从此时起,数据库端的所有任务都将使用 OE 用户执行。
sql
sqlplus oe/Ora_DB4U@localhost:1521/orclpdb
并设置格式:
sql
set linesize 130
set serveroutput on
set pages 9999
set long 90000
column WORKSHOPNAME format a50
column LOCATION format a20
column COUNTRY format a8
column GEONAMEID format a10
column TITLE format a35
column NAME format a32
column REGION format a20
column SUB_REGION format a22
column REGION format a12
任务 2:注册 Geonames
为了完成本练习,我们将使用由 GeoNames - geonames.org 提供的 Web 服务,该服务以 JSON 格式返回信息。GeoNames 已获得 Creative Commons Attribution 4.0 许可。您可以自由地:
- 共享 --- 以任何媒介或格式复制和重新分发材料;
- 改编 --- 出于任何目的(甚至商业目的)重新混合、转换和基于材料进行创作。
-
单击 GeoNames 网站右上角的"登录"链接,然后创建一个新帐户。
注意:创建 GeoNames 帐户后,您将收到一封电子邮件以激活帐户。
-
在帐户页面 GeoNames 帐户页面上为帐户启用 Web 服务。
会有如下的提示:Welcome username. You are now logged in and can update placenames.
任务 3:生成 JSON 数据
第一步是生成一些 JSON 数据到数据库中,或者从 Web 服务中检索示例文档。Oracle 数据库原生支持 JavaScript 对象表示法 (JSON) 数据,并具有关系数据库功能,包括事务、索引、声明式查询和视图。
本实验涵盖如何使用数据库语言和功能来处理存储在 Oracle 数据库中的 JSON 数据。特别是,它涵盖了如何使用 SQL 和 PL/SQL 处理 JSON 数据。
注意:记得替换 GeoNames_username为你的用户名。在注册邮箱里可以找到用户名。
sql
set serveroutput on
declare
t_http_req utl_http.req;
t_http_resp utl_http.resp;
t_response_text clob;
begin
t_http_req:= utl_http.begin_request('http://api.geonames.org/countryInfoJSON?formatted=true' || '&' || 'country=ES' || '&' || 'username=GeoNames_username' || '&' || 'style=full', 'GET', 'HTTP/1.1');
t_http_resp:= utl_http.get_response(t_http_req);
UTL_HTTP.read_text(t_http_resp, t_response_text);
UTL_HTTP.end_response(t_http_resp);
DBMS_OUTPUT.put_line(t_response_text);
end;
/
输出如下,为JSON格式:
json
{"geonames": [{
"continent": "EU",
"capital": "Madrid",
"languages": "es-ES,ca,gl,eu,oc",
"geonameId": 2510769,
"south":
36.0001044260548,
"isoAlpha3": "ESP",
"north": 43.7913565913767,
"fipsCode": "SP",
"population": "46723749",
"east":
4.32778473043961,
"isoNumeric": "724",
"areaInSqKm": "504782.0",
"countryCode": "ES",
"west": -9.30151567231899,
"countryName": "Spain",
"postalCodeFormat": "#####",
"continentName": "Europe",
"currencyCode": "EUR"
}]}
任务 4:将 Json 文档存储到 Oracle 数据库中
创建一个新表来存储可插入数据库中的所有 JSON 文档。
sql
CREATE TABLE MYJSON (
id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY (CACHE 5) PRIMARY KEY,
doc CLOB CONSTRAINT valid_json CHECK (doc IS JSON));
在 Oracle 数据库中使用 JSON 非常灵活,不需要预定义的数据结构或特定模式。您可以将任何 JSON 文档存储在关系表中,就像我们刚刚创建的表一样,具有任何内部文档结构。这是另一个 JSON 文档示例,其结构与我们从 GeoNames 收到的结构完全不同,我们可以将其存储在同一个表中。
sql
INSERT INTO MYJSON (doc) VALUES (
'{
"workshopName": "Database 19c New Features for Developers",
"audienceType": "Partners Technical Staff",
"location": {
"company": "Oracle",
"office": "Customer Visiting Center",
"region": "EMEA"
}
}');
commit;
一旦存储,我们就可以查询这些文档并检索 JSON 值作为传统的关系数据。
sql
set pages 9999
set long 90000
SELECT j.doc FROM MYJSON j;
DOC
--------------------------------------------------------------------------------
{
"workshopName": "Database 19c New Features for Developers",
"audienceType": "Partners Technical Staff",
"location": {
"company": "Oracle",
"office": "Customer Visiting Center",
"region": "EMEA"
}
}
任务 5:单点符号
Oracle 数据库 SQL 引擎允许您对 JSON 数据使用简单点符号 (SDN) 语法。换句话说,您可以编写包含类似 TABLE_Alias.JSON_Column.JSON_Property.JSON_Property 的 SQL 查询,这非常方便,因为区域属性是 JSON 文档中嵌套对象位置的属性。请记住,JSDN 语法区分大小写。
点符号查询的返回值始终是一个表示 JSON 数据的字符串(数据类型为 VARCHAR2(4000))。字符串的内容取决于目标 JSON 数据,如下所示:
- 如果以单个 JSON 值为目标,则该值是字符串内容,无论它是 JSON 标量、对象还是数组。
- 如果以多个 JSON 值为目标,则字符串内容是一个 JSON 数组,其元素是这些值。
发出查询以从您之前加载的表中选择车间名称和地区。
sql
SQL> SELECT j.doc.workshopName, j.doc.location.region FROM MYJSON j;
WORKSHOPNAME LOCATION
-------------------------------------------------- --------------------
Database 19c New Features for Developers EMEA
任务 6:检索示例数据
我们实验室的目标是检索有关欧洲城堡的信息,并在不同场景中将它们用作 JSON 文档。假设您正在开始开发一款为游客提供建议的新移动应用程序。为了方便和舒适,我们可以将与 Web 服务的通信封装到一个函数中。这样,我们就不必编写简单请求所需的所有代码,在大多数情况下,这比我们这里的简单示例更复杂,因为它们需要更复杂的身份验证。
创建一个函数来获取国家信息。
注意:记得替换 GeoNames_username为你的用户名。
sql
create or replace function get_country_info (countryCode in VARCHAR2) return clob
is
t_http_req utl_http.req;
t_http_resp utl_http.resp;
t_response_text clob;
begin
t_http_req:= utl_http.begin_request('http://api.geonames.org/countryInfoJSON?formatted=true' || '&' || 'country=' || countryCode || '&' || 'username=GeoNames_username' || '&' || 'style=full', 'GET', 'HTTP/1.1');
t_http_resp:= utl_http.get_response(t_http_req);
UTL_HTTP.read_text(t_http_resp, t_response_text);
UTL_HTTP.end_response(t_http_resp);
return t_response_text;
end;
/
我们刚刚创建的函数的输入是某个国家/地区的 ISO 代码。例如,运行此查询以获取有关西班牙的信息。
sql
SQL> select get_country_info('ES') country_info from dual;
COUNTRY_INFO
--------------------------------------------------------------------------------
{"geonames": [{
"continent": "EU",
"capital": "Madrid",
"languages": "es-ES,ca,gl,eu,oc",
"geonameId": 2510769,
"south": 36.0001044260548,
"isoAlpha3": "ESP",
"north": 43.7913565913767,
"fipsCode": "SP",
"population": "46723749",
"east": 4.32778473043961,
"isoNumeric": "724",
"areaInSqKm": "504782.0",
"countryCode": "ES",
"west": -9.30151567231899,
"countryName": "Spain",
"postalCodeFormat": "#####",
"continentName": "Europe",
"currencyCode": "EUR"
}]}
SQL> select get_country_info('CN') country_info from dual;
COUNTRY_INFO
--------------------------------------------------------------------------------
{"geonames": [{
"continent": "AS",
"capital": "Beijing",
"languages": "zh-CN,yue,wuu,dta,ug,za",
"geonameId": 1814991,
"south": 15.775416,
"isoAlpha3": "CHN",
"north": 53.560974001,
"fipsCode": "CH",
"population": "1411778724",
"east": 134.7754563,
"isoNumeric": "156",
"areaInSqKm": "9596960.0",
"countryCode": "CN",
"west": 73.4994140000001,
"countryName": "China",
"postalCodeFormat": "######",
"continentName": "Asia",
"currencyCode": "CNY"
}]}
将从 Web 服务检索到的 JSON 文档插入到同一张表的 JSON 列中,即使该 JSON 文档具有完全不同的结构。
sql
insert into MYJSON (doc) values (get_country_info('ES'));
commit;
选择该表的内容,并注意我们使用相同的列。
sql
SQL> select * from MYJSON;
ID DOC
---------- --------------------------------------------------------------------------------
1 {
"workshopName": "Database 19c New Features for Developers",
"audienceType": "Partners Technical Staff",
"location": {
"company": "Oracle",
"office": "Customer Visiting Center",
"region": "EMEA"
}
}
2 {"geonames": [{
"continent": "EU",
"capital": "Madrid",
"languages": "es-ES,ca,gl,eu,oc",
"geonameId": 2510769,
"south": 36.0001044260548,
"isoAlpha3": "ESP",
"north": 43.7913565913767,
"fipsCode": "SP",
"population": "46723749",
"east": 4.32778473043961,
"isoNumeric": "724",
"areaInSqKm": "504782.0",
"countryCode": "ES",
"west": -9.30151567231899,
"countryName": "Spain",
"postalCodeFormat": "#####",
"continentName": "Europe",
"currencyCode": "EUR"
}]}
使用属性,我们可以从特定文档中获取所需的信息。我们可以为不匹配的属性分配默认值,并从应用程序中进一步处理问题。SQL/JSON 函数 JSON_VALUE 在 JSON 数据中查找指定的标量 JSON 值并将其作为 SQL 值返回。
sql
column GEONAMEID format a10
column COUNTRY format a40
SELECT JSON_VALUE(doc, '$.geonames.geonameId' NULL ON ERROR) AS GeoNameID,
JSON_VALUE(doc, '$.geonames.countryName' DEFAULT 'Not a country' ON ERROR) AS Country
FROM MYJSON;
GEONAMEID COUNTRY
---------- ----------------------------------------
Not a country
2510769 Spain
默认的NULL ON ERROR行为表示出错时返回NULL,其他还有ERROR ON ERROR,DEFAULT literal ON ERROR。
或者我们可以使用 SDN 语法过滤结果以仅接收对查询有用的文档。
sql
select j.doc.geonames.geonameId GeoNameID, j.doc.geonames.countryName Country
from MYJSON j where j.doc.geonames.isoAlpha3 IS NOT NULL;
GEONAMEID COUNTRY
---------- ----------------------------------------
2510769 Spain
如果不带WHERE条件,输出看上去是一样的,但实际是两行:
sql
SQL> set feedback on
SQL> /
GEONAMEID COUNTRY
---------- ----------------------------------------
2510769 Spain
2 rows selected.
需要一个新的函数来从 GeoNames Web 服务检索包含国家/地区信息的 JSON 文档。此函数需要国家/地区的 geonameId 以及 GeoNames Web 服务内部使用的样式值来指定详细程度。
注意:记得替换 GeoNames_username为你的用户名。
sql
create or replace function get_subdivision (geonameId in NUMBER, style in VARCHAR2) return clob
is
t_http_req utl_http.req;
t_http_resp utl_http.resp;
t_response_text clob;
begin
t_http_req:= utl_http.begin_request('http://api.geonames.org/childrenJSON?formatted=true' || '&' || 'geonameId=' || geonameId || '&' || 'username=GeoNames_username' || '&' || 'style=' || style, 'GET', 'HTTP/1.1');
t_http_resp:= utl_http.get_response(t_http_req);
UTL_HTTP.read_text(t_http_resp, t_response_text);
UTL_HTTP.end_response(t_http_resp);
return t_response_text;
end;
/
使用以下输入测试此功能。
sql
select get_subdivision(2510769, 'medium') regions_document from dual;
输出为:
sql
REGIONS_DOCUMENT
--------------------------------------------------------------------------------
{
"totalResultsCount": 19,
"geonames": [
{
"adminCode1": "51",
"lng": "-4.58333",
"geonameId": 2593109,
"toponymName": "Andalusia",
"countryId": "2510769",
"fcl": "A",
"population": 8494260,
"countryCode": "ES",
"name": "Andalusia",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "AN"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Andalusia",
"lat": "37.5",
"fcode": "ADM1"
},
{
"adminCode1": "52",
"lng": "-0.66667",
"geonameId": 3336899,
"toponymName": "Aragon",
"countryId": "2510769",
"fcl": "A",
"population": 1326261,
"countryCode": "ES",
"name": "Aragon",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "AR"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Aragon",
"lat": "41.5",
"fcode": "ADM1"
},
{
"adminCode1": "34",
"lng": "-6",
"geonameId": 3114710,
"toponymName": "Principality of Asturias",
"countryId": "2510769",
"fcl": "A",
"population": 1011792,
"countryCode": "ES",
"name": "Asturias",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "AS"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Asturias",
"lat": "43.33333",
"fcode": "ADM1"
},
{
"adminCode1": "07",
"lng": "2.90314",
"geonameId": 2521383,
"toponymName": "Comunitat Aut??noma de les Illes Balears",
"countryId": "2510769",
"fcl": "A",
"population": 1095426,
"countryCode": "ES",
"name": "Balearic Islands",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "IB"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Balearic Islands",
"lat": "39.58876",
"fcode": "ADM1"
},
{
"adminCode1": "59",
"lng": "-2.75",
"geonameId": 3336903,
"toponymName": "Euskal Autonomia Erkidegoa",
"countryId": "2510769",
"fcl": "A",
"population": 2189534,
"countryCode": "ES",
"name": "Basque Country",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "PV"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Basque Country",
"lat": "43",
"fcode": "ADM1"
},
{
"adminCode1": "53",
"lng": "-15.5",
"geonameId": 2593110,
"toponymName": "Canary Islands",
"countryId": "2510769",
"fcl": "A",
"population": 2153389,
"countryCode": "ES",
"name": "Canary Islands",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "CN"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Canary Islands",
"lat": "28",
"fcode": "ADM1"
},
{
"adminCode1": "39",
"lng": "-4.03333",
"geonameId": 3336898,
"toponymName": "Cantabria",
"countryId": "2510769",
"fcl": "A",
"population": 580229,
"countryCode": "ES",
"name": "Cantabria",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "CB"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Cantabria",
"lat": "43.2",
"fcode": "ADM1"
},
{
"adminCode1": "55",
"lng": "-4.25",
"geonameId": 3336900,
"toponymName": "Castilla y Le??n",
"countryId": "2510769",
"fcl": "A",
"population": 2447519,
"countryCode": "ES",
"name": "Castille and Le??n",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "CL"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Castille and Le??n",
"lat": "41.66667",
"fcode": "ADM1"
},
{
"adminCode1": "54",
"lng": "-3",
"geonameId": 2593111,
"toponymName": "Castilla-La Mancha",
"countryId": "2510769",
"fcl": "A",
"population": 2041631,
"countryCode": "ES",
"name": "Castille-La Mancha",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "CM"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Castille-La Mancha",
"lat": "39.5",
"fcode": "ADM1"
},
{
"adminCode1": "56",
"lng": "1.86768",
"geonameId": 3336901,
"toponymName": "Catalunya",
"countryId": "2510769",
"fcl": "A",
"population": 7899056,
"countryCode": "ES",
"name": "Catalonia",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "CT"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Catalonia",
"lat": "41.82046",
"fcode": "ADM1"
},
{
"adminCode1": "CE",
"lng": "-5.3075",
"geonameId": 2519582,
"toponymName": "Ceuta",
"countryId": "2510769",
"fcl": "A",
"population": 85144,
"countryCode": "ES",
"name": "Ceuta",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "CE"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Ceuta",
"lat": "35.89028",
"fcode": "ADM1"
},
{
"adminCode1": "57",
"lng": "-6.16667",
"geonameId": 2593112,
"toponymName": "Extremadura",
"countryId": "2510769",
"fcl": "A",
"population": 1087778,
"countryCode": "ES",
"name": "Extremadura",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "EX"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Extremadura",
"lat": "39.16667",
"fcode": "ADM1"
},
{
"adminCode1": "58",
"lng": "-7.86621",
"geonameId": 3336902,
"toponymName": "Galicia",
"countryId": "2510769",
"fcl": "A",
"population": 2701819,
"countryCode": "ES",
"name": "Galicia",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "GA"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Galicia",
"lat": "42.75508",
"fcode": "ADM1"
},
{
"adminCode1": "27",
"lng": "-2.5",
"geonameId": 3336897,
"toponymName": "La Rioja",
"countryId": "2510769",
"fcl": "A",
"population": 315675,
"countryCode": "ES",
"name": "La Rioja",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "RI"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "La Rioja",
"lat": "42.3",
"fcode": "ADM1"
},
{
"adminCode1": "29",
"lng": "-3.69063",
"geonameId": 3117732,
"toponymName": "Comunidad de Madrid",
"countryId": "2510769",
"fcl": "A",
"population": 6661949,
"countryCode": "ES",
"name": "Madrid",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "MD"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Madrid",
"lat": "40.42526",
"fcode": "ADM1"
},
{
"adminCode1": "ML",
"lng": "-2.94434",
"geonameId": 6362988,
"toponymName": "Melilla",
"countryId": "2510769",
"fcl": "A",
"population": 86384,
"countryCode": "ES",
"name": "Melilla",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "ML"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Melilla",
"lat": "35.29215",
"fcode": "ADM1"
},
{
"adminCode1": "31",
"lng": "-1.5",
"geonameId": 2513413,
"toponymName": "Regi??n de Murcia",
"countryId": "2510769",
"fcl": "A",
"population": 1511251,
"countryCode": "ES",
"name": "Murcia",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "MC"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Murcia",
"lat": "38",
"fcode": "ADM1"
},
{
"adminCode1": "32",
"lng": "-1.66667",
"geonameId": 3115609,
"toponymName": "Navarra",
"countryId": "2510769",
"fcl": "A",
"population": 661537,
"countryCode": "ES",
"name": "Navarre",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "NC"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Navarre",
"lat": "42.75",
"fcode": "ADM1"
},
{
"adminCode1": "60",
"lng": "-0.75",
"geonameId": 2593113,
"toponymName": "Comunitat Valenciana",
"countryId": "2510769",
"fcl": "A",
"population": 5057353,
"countryCode": "ES",
"name": "Valencia",
"fclName": "country, state, region,...",
"adminCodes1": {"ISO3166_2": "VC"},
"countryName": "Spain",
"fcodeName": "first-order administrative division",
"adminName1": "Valencia",
"lat": "39.5",
"fcode": "ADM1"
}
]
}
1 row selected.
如果测试成功,则将此新的 JSON 文档插入同一张表中。
sql
insert into MYJSON (doc) values (get_subdivision(2510769, 'medium'));
commit;
SQL/JSON 函数 JSON_TABLE 创建 JSON 数据的关系视图。它将 JSON 数据评估的结果映射到关系行和列中。您可以使用 SQL 查询该函数返回的结果作为虚拟关系表。JSON_TABLE 的主要目的是为 JSON 数组内的每个对象创建一行关系数据,并将该对象内的 JSON 值输出为单独的 SQL 列值。NESTED 子句允许您将嵌套 JSON 对象或 JSON 数组中的 JSON 值与来自父对象或数组的 JSON 值一起展平为单行中的单独列。您可以递归使用此子句将多层嵌套对象或数组中的数据投影到单行中。此路径表达式与 JSON_TABLE 函数中指定的 SQL/JSON 行路径表达式相关。
sql
column TITLE format a35
column NAME format a32
SELECT jt.countryName Country, jt.fcode, convert(jt.toponymName,'WE8ISO8859P1','AL32UTF8') Title,
convert(jt.name,'WE8ISO8859P1','AL32UTF8') Name, jt.geonameId GeoNameID FROM MYJSON,
JSON_TABLE(DOC, '$' COLUMNS
(NESTED PATH '$.geonames[*]'
COLUMNS (countryName VARCHAR2(80) PATH '$.countryName',
toponymName VARCHAR2(120) PATH '$.toponymName',
geonameId VARCHAR2(20) PATH '$.geonameId',
name VARCHAR2(80) PATH '$.name',
fcode VARCHAR2(6) PATH '$.fcode')))
AS jt WHERE (fcode = 'ADM1');
COUNTRY FCODE TITLE NAME GEONAMEID
---------------------------------------- ------ ----------------------------------- -------------------------------- ----------
Spain ADM1 Andalusia Andalusia 2593109
Spain ADM1 Aragon Aragon 3336899
Spain ADM1 Principality of Asturias Asturias 3114710
Spain ADM1 Comunitat Autonoma de les Illes Bal Balearic Islands 2521383
ears
Spain ADM1 Euskal Autonomia Erkidegoa Basque Country 3336903
Spain ADM1 Canary Islands Canary Islands 2593110
Spain ADM1 Cantabria Cantabria 3336898
Spain ADM1 Castilla y Leon Castille and Leon 3336900
Spain ADM1 Castilla-La Mancha Castille-La Mancha 2593111
Spain ADM1 Catalunya Catalonia 3336901
Spain ADM1 Ceuta Ceuta 2519582
Spain ADM1 Extremadura Extremadura 2593112
Spain ADM1 Galicia Galicia 3336902
Spain ADM1 La Rioja La Rioja 3336897
Spain ADM1 Comunidad de Madrid Madrid 3117732
Spain ADM1 Melilla Melilla 6362988
Spain ADM1 Region de Murcia Murcia 2513413
Spain ADM1 Navarra Navarre 3115609
Spain ADM1 Comunitat Valenciana Valencia 2593113
19 rows selected.
有了西班牙的所有地区,我们可以向 GeoNames 网络服务询问每个地区的更多信息,例如安达卢西亚的 geonameId 为 2593109。
sql
SELECT get_subdivision(2593109, 'full') sub_regions FROM dual;
-- 输出太长,此处不显示了
我们的下一个目标是获取有关每个地区的更多详细信息,为此我们需要每个地区的 geonameId。一种选择是使用 JSON_TABLE 仅返回该列,或者使用以下 SDN 语法。
sql
SELECT j.doc.geonames.geonameId FROM MYJSON j WHERE j.doc.geonames.fcode like '%ADM1%';
GEONAMES
----------------------------------------------------------------------------------------------------------------------------------------------------------
[2593109,3336899,3114710,2521383,3336903,2593110,3336898,3336900,2593111,3336901,2519582,2593112,3336902,3336897,3117732,6362988,2513413,3115609,2593113]
1 row selected.
SDN 语法返回一个数组,而不是一列中的 JSON 数据的关系视图。
实验 5:Oracle DB 中的 JSON - 高级主题
简介
该实验将探讨有关 JSON 数据的高级概念以及如何使用 SQL 和 PL/SQL 处理存储在 Oracle Database 19c 中的 JSON 数据。
在本实验中,我们将使用 Oracle 数据库安装随附的订单录入 (OE) 示例模式。如果您之前已完成设置,则您已经安装了 OE 模式。
任务 1:连接到环境
sql
sqlplus oe/Ora_DB4U@localhost:1521/orclpdb
任务 2:以 JSON 格式检索子区域信息
使用游标和您最喜欢的查询,我们可以运行循环,以检索西班牙每个地区的子区域。此过程将在我们的表中存储一个 JSON 文档,其中包含每个地区的子区域(19 个文档)。
sql
declare
cursor c1 is
WITH ids ( GEONAMES, start_pos, end_pos ) AS
( SELECT GEONAMES, 1, INSTR( GEONAMES, ',' ) FROM
(SELECT substr(j.doc.geonames.geonameId, 2, length(j.doc.geonames.geonameId)-2) as GEONAMES
FROM MYJSON j WHERE j.doc.geonames.fcode like '%ADM1%')
UNION ALL
SELECT GEONAMES,
end_pos + 1,
INSTR( GEONAMES, ',', end_pos + 1 )
FROM ids
WHERE end_pos > 0
)
SELECT SUBSTR(GEONAMES, start_pos, DECODE(end_pos, 0, LENGTH( GEONAMES ) + 1, end_pos) - start_pos) AS geonameId
FROM ids;
begin
FOR subregionID in c1
LOOP
insert into MYJSON (doc) values (get_subdivision(subregionID.geonameId, 'full'));
END LOOP;
commit;
end;
/
PL/SQL procedure successfully completed.
查询这 19 个文档中存储的区域和子区域,并将它们检索为关系数据。
Oracle Database Release 12.1 中引入的 JSON_TABLE 函数支持创建 JSON 内容的内联关系视图。JSON_TABLE 运算符使用一组 JSON 路径表达式将 JSON 文档中的内容映射到视图中的列中。将 JSON 文档的内容显示为列后,SQL 的所有功能都可以应用于 JSON 文档的内容。NESTED 子句允许您将嵌套 JSON 对象或 JSON 数组中的 JSON 值与来自父对象或数组的 JSON 值一起展平到一行中的各个列中。您可以递归使用此子句将多层嵌套对象或数组中的数据投影到一行中。此路径表达式与 JSON_TABLE 函数中指定的 SQL/JSON 行路径表达式相关。
sql
column COUNTRY format a8
column REGION format a20
col title for a36
col name for a24
set lines 120
set pages 9999
SELECT jt.countryName Country,
convert(jt.adminName1,'WE8ISO8859P1','AL32UTF8') Region,
convert(jt.toponymName,'WE8ISO8859P1','AL32UTF8') Title,
convert(jt.name,'WE8ISO8859P1','AL32UTF8') Name, jt.adminCode1, jt.adminCode2 FROM MYJSON,
JSON_TABLE(DOC, '$' COLUMNS
(NESTED PATH '$.geonames[*]'
COLUMNS (countryName VARCHAR2(80) PATH '$.countryName',
adminName1 VARCHAR2(80) PATH '$.adminName1',
toponymName VARCHAR2(120) PATH '$.toponymName',
name VARCHAR2(80) PATH '$.name',
adminCode1 VARCHAR(8) PATH '$.adminCode1',
adminCode2 VARCHAR(8) PATH '$.adminCode2',
fcode VARCHAR2(6) PATH '$.fcode')))
AS jt WHERE (fcode = 'ADM2');
COUNTRY REGION TITLE NAME ADMINCOD ADMINCOD
-------- -------------------- ------------------------------------ ------------------------ -------- --------
Spain Andalusia Almeria Almeria 51 AL
Spain Andalusia Provincia de Cadiz Cadiz 51 CA
Spain Andalusia Province of Cordoba Cordoba 51 CO
Spain Andalusia Provincia de Granada Granada 51 GR
Spain Andalusia Provincia de Huelva Huelva 51 H
Spain Andalusia Provincia de Jaen Jaen 51 J
Spain Andalusia Provincia de Malaga Malaga 51 MA
Spain Andalusia Provincia de Sevilla Seville 51 SE
Spain Aragon Provincia de Huesca Huesca 52 HU
Spain Aragon Provincia de Zaragoza Saragossa 52 Z
Spain Aragon Provincia de Teruel Teruel 52 TE
Spain Asturias Province of Asturias Asturias 34 O
Spain Balearic Islands Illes Balears Balearic Islands 07 PM
Spain Basque Country Araba / Alava Araba / Alava 59 VI
Spain Basque Country Bizkaia Biscay 59 BI
Spain Basque Country Gipuzkoa Gipuzkoa 59 SS
Spain Canary Islands Provincia de Las Palmas Las Palmas 53 GC
Spain Canary Islands Provincia de Santa Cruz de Tenerife Santa Cruz de Tenerife 53 TF
Spain Cantabria Provincia de Cantabria Cantabria 39 S
Spain Castille and Leon Provincia de Avila Avila 55 AV
Spain Castille and Leon Provincia de Burgos Burgos 55 BU
Spain Castille and Leon Provincia de Leon Leon 55 LE
Spain Castille and Leon Provincia de Palencia Palencia 55 P
Spain Castille and Leon Provincia de Salamanca Salamanca 55 SA
Spain Castille and Leon Provincia de Segovia Segovia 55 SG
Spain Castille and Leon Provincia de Soria Soria 55 SO
Spain Castille and Leon Provincia de Valladolid Valladolid 55 VA
Spain Castille and Leon Provincia de Zamora Zamora 55 ZA
Spain Castille-La Mancha Provincia de Albacete Albacete 54 AB
Spain Castille-La Mancha Provincia de Ciudad Real Ciudad Real 54 CR
Spain Castille-La Mancha Provincia de Cuenca Cuenca 54 CU
Spain Castille-La Mancha Provincia de Guadalajara Guadalajara 54 GU
Spain Castille-La Mancha Province of Toledo Toledo 54 TO
Spain Catalonia Provincia de Barcelona Barcelona 56 B
Spain Catalonia Provincia de Girona Girona 56 GI
Spain Catalonia Provincia de Lleida Lleida 56 L
Spain Catalonia Provincia de Tarragona Tarragona 56 T
Spain Ceuta Ceuta Ceuta CE CE
Spain Extremadura Provincia de Badajoz Badajoz 57 BA
Spain Extremadura Provincia de Caceres Caceres 57 CC
Spain Galicia Provincia da Coru?a A Coru?a 58 C
Spain Galicia Provincia de Lugo Lugo 58 LU
Spain Galicia Provincia de Ourense Ourense 58 OR
Spain Galicia Provincia de Pontevedra Pontevedra 58 PO
Spain La Rioja Provincia de La Rioja La Rioja 27 LO
Spain Madrid Provincia de Madrid Madrid 29 M
Spain Melilla Melilla Melilla ML ME
Spain Murcia Murcia Murcia 31 MU
Spain Navarre Provincia de Navarra Navarre 32 NA
Spain Valencia Provincia de Alicante Alicante 60 A
Spain Valencia Provincia de Castello Castellon 60 CS
Spain Valencia Provincia de Valencia Valencia 60 V
52 rows selected.
任务 3:以 JSON 格式检索城堡信息
为了从 GeoNames Web 服务中检索有关城堡的信息,我们必须创建一个新函数。此函数的输入是 ISO 国家代码、地区代码和子区域代码。输出是包含该子区域内所有城堡的 JSON 文档。
注意:记得替换 GeoNames_username为你的用户名。
sql
create or replace function get_castles (country in VARCHAR2, adminCode1 in VARCHAR2, adminCode2 in VARCHAR2) return clob
is
t_http_req utl_http.req;
t_http_resp utl_http.resp;
t_response_text clob;
begin
t_http_req:= utl_http.begin_request('http://api.geonames.org/searchJSON?formatted=true' || '&' || 'featureCode=CSTL' || '&' || 'country=' || country || '&' || 'adminCode1=' || adminCode1 || '&' || 'adminCode2=' || adminCode2 || '&' || 'username=GeoNames_username' || '&' || 'style=full', 'GET', 'HTTP/1.1');
t_http_resp:= utl_http.get_response(t_http_req);
UTL_HTTP.read_text(t_http_resp, t_response_text);
UTL_HTTP.end_response(t_http_resp);
return t_response_text;
end;
/
测试 get_castles 函数,使用瓦伦西亚地区 (adminCode1: 60) 和阿利坎特省子区域 (adminCode2: A) 作为输入。
sql
set long 100000
select get_castles('ES', 60, 'A') castles_document from dual;
CASTLES_DOCUMENT
--------------------------------------------------------------------------------
{
"totalResultsCount": 12,
"geonames": [
{
"timezone": {
"gmtOffset": 1,
"timeZoneId": "Europe/Madrid",
"dstOffset": 2
},
"asciiName": "Castillo de Santa Barbara",
"astergdem": 138,
"countryId": "2510769",
"fcl": "S",
"srtm3": 121,
...
循环使用此函数从所有子区域中检索城堡,如以下示例所示,将 JSON 文档存储在同一张表中。
sql
column SUB_REGION format a20
column REGION format a12
declare
cursor c1 is
SELECT jt.adminCode1, jt.adminCode2 FROM MYJSON,
JSON_TABLE(DOC, '$' COLUMNS
(NESTED PATH '$.geonames[*]'
COLUMNS (adminCode1 VARCHAR(8) PATH '$.adminCode1',
adminCode2 VARCHAR(8) PATH '$.adminCode2',
fcode VARCHAR2(6) PATH '$.fcode')))
AS jt WHERE (fcode = 'ADM2');
begin
FOR SubRegion in c1
LOOP
insert into MYJSON (doc) values (get_castles('ES', SubRegion.adminCode1, SubRegion.adminCode2));
END LOOP;
commit;
end;
/
PL/SQL procedure successfully completed.
此时,数据库中有足够的 JSON 文档,以及开发提供有关西班牙中世纪城堡信息的应用程序的所有信息。
sql
col country for a10
SELECT jt.countryName Country,
convert(jt.adminName1,'WE8ISO8859P1','AL32UTF8') Region,
convert(jt.adminName2,'WE8ISO8859P1','AL32UTF8') Sub_Region,
jt.fcode, convert(jt.toponymName,'WE8ISO8859P1','AL32UTF8') Title,
convert(jt.name,'WE8ISO8859P1','AL32UTF8') Name FROM MYJSON,
JSON_TABLE(DOC, '$' COLUMNS
(NESTED PATH '$.geonames[*]'
COLUMNS (countryName VARCHAR2(80) PATH '$.countryName',
adminName1 VARCHAR2(80) PATH '$.adminName1',
adminName2 VARCHAR2(80) PATH '$.adminName2',
toponymName VARCHAR2(120) PATH '$.toponymName',
name VARCHAR2(80) PATH '$.name',
adminCode1 VARCHAR(8) PATH '$.adminCode1',
fcode VARCHAR2(6) PATH '$.fcode')))
AS jt WHERE (fcode = 'CSTL');
此查询应返回 269 行。
sql
COUNTRY REGION SUB_REGION FCODE TITLE NAME
---------- ------------ -------------------- ------ -------------------------- --------------------------
Spain Navarre Navarre CSTL Castello de Javier Castello de Javier
Spain Valencia Alicante CSTL Castillo de Santa Barbara Castillo de Santa Barbara
Spain Valencia Alicante CSTL Castillo de Cocentaina Castillo de Cocentaina
Spain Valencia Alicante CSTL Atalaya Castle Atalaya Castle
Spain Valencia Alicante CSTL Castillo de San Fernando Castillo de San Fernando
Spain Valencia Alicante CSTL Castillo de Busot Castillo de Busot
Spain Valencia Alicante CSTL Castillo de Santa Pola Castillo de Santa Pola
...
269 rows selected.
任务 4:JSON_DATAGUIDE - 发现有关 JSON 文档结构和内容的信息
下面显示了 JSON_DATAGUIDE,这是一个分析一个或多个 JSON 值并提供架构的函数 - 数据的结构摘要、字段名称、它们的嵌套方式及其数据类型。
JSON 数据指南信息可以作为 JSON 搜索索引基础结构的一部分永久保存,并且此信息会在添加新的 JSON 内容时自动更新。当您创建 JSON 搜索索引时,默认情况下就是这种情况:数据指南信息是索引基础结构的一部分。
您可以使用数据指南:
- 作为开发涉及数据挖掘、商业智能或其他 JSON 文档分析的应用程序的基础。
- 作为向用户提供有关所请求 JSON 信息(包括搜索)的帮助的基础。
- 在将新的 JSON 文档添加到文档集之前对其进行检查或操作(例如:验证、类型检查或排除某些字段)。
我们将使用城堡示例来说明 JSON 数据指南
我们想要获取此 JSON 数据的数据指南
sql
set long 100000
select get_castles('ES', 60, 'A') castles_document from dual;
CASTLES_DOCUMENT
--------------------------------------------------------------------------------
{
"totalResultsCount": 12,
"geonames": [
{
"timezone": {
"gmtOffset": 1,
"timeZoneId": "Europe/Madrid",
"dstOffset": 2
},
"asciiName": "Castillo de Santa Barbara",
"astergdem": 138,
"countryId": "2510769",
"fcl": "S",
"srtm3": 121,
"score": 63.745391845703125,
"adminId2": "2521976",
"adminId3": "6355390",
"countryCode": "ES",
"adminCodes2": {"ISO3166_2": "A"},
"adminCodes1": {"ISO3166_2": "VC"},
"adminId1": "2593113",
"lat": "38.34916",
"fcode": "CSTL",
"continentCode": "EU",
"elevation": 167,
"adminCode2": "A",
"adminCode3": "03014",
"adminCode1": "60",
"lng": "-0.47802",
"geonameId": 6255097,
"toponymName": "Castillo de Santa B?!rbara",
"population": 0,
"adminName5": "",
"adminName4": "",
"adminName3": "Alicante",
"alternateNames": [{
"name": "https://en.wikipedia.org/wiki/Santa_B%C3%A1rbara_Castle",
"lang": "link"
}],
"adminName2": "Alicante",
"name": "Castillo de Santa B?!rbara",
"fclName": "spot, building, farm",
"countryName": "Spain",
"fcodeName": "castle",
"adminName1": "Valencia"
},
...
创建没有 IS JSON 检查约束的表:
sql
create table castles (castle_info clob);
将 JSON 数据插入到此新表中:
sql
insert into castles
select get_castles('ES', 60, 'A') castles_document from dual;
commit;
我们可以使用 IS JSON 函数来检查 CLOB 值是否为 JSON。使用 IS JSON,我们可以过滤语法正确的 JSON 文本值:
sql
select 1 from castles where castle_info IS JSON;
1
----------
1
计算城堡信息的 JSON 数据指南。JSON 数据指南是一个模式文档,列出了所有字段名称、其对象层次结构和数据类型。我们使用 dbms_json.pretty 漂亮地打印数据指南以提高可读性。
sql
select json_dataguide(get_castles('ES', 60, 'A'), dbms_json.FORMAT_HIERARCHICAL, dbms_json.pretty ) castles_schema from dual;
CASTLES_SCHEMA
--------------------------------------------------------------------------------
{
"type" : "object",
"properties" :
{
"geonames" :
{
"type" : "array",
"o:length" : 16384,
"o:preferred_column_name" : "geonames",
"items" :
{
"properties" :
{
"fcl" :
{
"type" : "string",
"o:length" : 1,
"o:preferred_column_name" : "fcl"
},
"lat" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "lat"
},
"lng" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "lng"
},
"name" :
{
"type" : "string",
"o:length" : 32,
"o:preferred_column_name" : "name"
},
"fcode" :
{
"type" : "string",
"o:length" : 4,
"o:preferred_column_name" : "fcode"
},
"score" :
{
"type" : "number",
"o:length" : 32,
"o:preferred_column_name" : "score"
},
"srtm3" :
{
"type" : "number",
"o:length" : 4,
"o:preferred_column_name" : "srtm3"
},
"fclName" :
{
"type" : "string",
"o:length" : 32,
"o:preferred_column_name" : "fclName"
},
"adminId1" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "adminId1"
},
"adminId2" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "adminId2"
},
"adminId3" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "adminId3"
},
"timezone" :
{
"type" : "object",
"o:length" : 64,
"o:preferred_column_name" : "timezone",
"properties" :
{
"dstOffset" :
{
"type" : "number",
"o:length" : 1,
"o:preferred_column_name" : "dstOffset"
},
"gmtOffset" :
{
"type" : "number",
"o:length" : 1,
"o:preferred_column_name" : "gmtOffset"
},
"timeZoneId" :
{
"type" : "string",
"o:length" : 16,
"o:preferred_column_name" : "timeZoneId"
}
}
},
"asciiName" :
{
"type" : "string",
"o:length" : 32,
"o:preferred_column_name" : "asciiName"
},
"astergdem" :
{
"type" : "number",
"o:length" : 4,
"o:preferred_column_name" : "astergdem"
},
"countryId" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "countryId"
},
"elevation" :
{
"type" : "number",
"o:length" : 4,
"o:preferred_column_name" : "elevation"
},
"fcodeName" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "fcodeName"
},
"geonameId" :
{
"type" : "number",
"o:length" : 8,
"o:preferred_column_name" : "geonameId"
},
"adminCode1" :
{
"type" : "string",
"o:length" : 2,
"o:preferred_column_name" : "adminCode1"
},
"adminCode2" :
{
"type" : "string",
"o:length" : 1,
"o:preferred_column_name" : "adminCode2"
},
"adminCode3" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "adminCode3"
},
"adminName1" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "adminName1"
},
"adminName2" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "adminName2"
},
"adminName3" :
{
"type" : "string",
"o:length" : 16,
"o:preferred_column_name" : "adminName3"
},
"adminName4" :
{
"type" : "string",
"o:length" : 1,
"o:preferred_column_name" : "adminName4"
},
"adminName5" :
{
"type" : "string",
"o:length" : 1,
"o:preferred_column_name" : "adminName5"
},
"population" :
{
"type" : "number",
"o:length" : 1,
"o:preferred_column_name" : "population"
},
"adminCodes1" :
{
"type" : "object",
"o:length" : 32,
"o:preferred_column_name" : "adminCodes1",
"properties" :
{
"ISO3166_2" :
{
"type" : "string",
"o:length" : 2,
"o:preferred_column_name" : "ISO3166_2"
}
}
},
"adminCodes2" :
{
"type" : "object",
"o:length" : 32,
"o:preferred_column_name" : "adminCodes2",
"properties" :
{
"ISO3166_2" :
{
"type" : "string",
"o:length" : 1,
"o:preferred_column_name" : "ISO3166_2"
}
}
},
"countryCode" :
{
"type" : "string",
"o:length" : 2,
"o:preferred_column_name" : "countryCode"
},
"countryName" :
{
"type" : "string",
"o:length" : 8,
"o:preferred_column_name" : "countryName"
},
"toponymName" :
{
"type" : "string",
"o:length" : 32,
"o:preferred_column_name" : "toponymName"
},
"continentCode" :
{
"type" : "string",
"o:length" : 2,
"o:preferred_column_name" : "continentCode"
},
"alternateNames" :
{
"type" : "array",
"o:length" : 128,
"o:preferred_column_name" : "alternateNames",
"items" :
{
"properties" :
{
"lang" :
{
"type" : "string",
"o:length" : 4,
"o:preferred_column_name" : "lang"
},
"name" :
{
"type" : "string",
"o:length" : 64,
"o:preferred_column_name" : "name"
}
}
}
}
}
}
},
"totalResultsCount" :
{
"type" : "number",
"o:length" : 2,
"o:preferred_column_name" : "totalResultsCount"
}
}
}
使用此架构的子集创建视图:
sql
DECLARE
dg clob;
BEGIN
dg := '{
"type": "object",
"properties": {
"geonames": {
"type": "array",
"o:length": 16384,
"o:preferred_column_name": "geonames",
"items": {
"properties": {
"lat": {
"type": "string",
"o:length": 8,
"o:preferred_column_name": "lat"
},
"lng": {
"type": "string",
"o:length": 8,
"o:preferred_column_name": "lng"
},
"name": {
"type": "string",
"o:length": 32,
"o:preferred_column_name": "name"
},
"timezone": {
"type": "object",
"o:length": 64,
"o:preferred_column_name": "timezone",
"properties": {
"dstOffset": {
"type": "number",
"o:length": 1,
"o:preferred_column_name": "dstOffset"
},
"gmtOffset": {
"type": "number",
"o:length": 1,
"o:preferred_column_name": "gmtOffset"
}
}
},
"countryName": {
"type": "string",
"o:length": 8,
"o:preferred_column_name": "countryName"
}
}
}
}
}
}
';
dbms_json.create_view('castle_view', 'castles', 'castle_info', dg);
END;
/
PL/SQL procedure successfully completed.
检查新视图:
sql
SQL> desc castle_view;
Name Null? Type
----------------------------------------------------------------- -------- --------------------------------------------
CASTLE_INFO CLOB
lat VARCHAR2(8)
lng VARCHAR2(8)
name VARCHAR2(32)
dstOffset NUMBER
gmtOffset NUMBER
countryName VARCHAR2(8)
SQL> select count(1) from castle_view;
COUNT(1)
----------
12
select "name", "lat", "lng" from castle_view order by "name";
name lat lng
-------------------------- -------- --------
Atalaya Castle 38.63168 -0.86118
Castell de Biar 38.63112 -0.76542
Castell de D??nia 38.84287 0.10762
Castillo de Busot 38.48546 -0.41744
Castillo de Cocentaina 38.7459 -0.44711
Castillo de Petrer 38.4844 -0.76772
Castillo de Salvatierra 38.63533 -0.85417
Castillo de San Fernando 38.35097 -0.4914
Castillo de Santa B?!rbara 38.34916 -0.47802
Castillo de Santa Pola 38.19218 -0.55464
Castillo de Sax 38.54032 -0.81731
El Castillo del Rio 38.34166 -0.72435
12 rows selected.
任务 5:查询 JSON 数据的语法简化
在 Oracle Database 19c 中,使用 SQL 查询 JSON 文档的简易性得到了一些改进。在从关系数据动态生成 JSON 文档方面也做出了其他改进。
这是从关系表生成 JSON 文档的基本示例,用于 Web 服务。通过这些 Web 服务,您可以集成企业内的异构应用程序,或通过互联网向第三方公开业务功能。所有这些都可以直接从数据库完成,无需安装和配置任何其他组件。让我们考虑 OE 模式中的 ORDERS 表。
sql
column ORDER_DATE format a28
desc ORDERS
select * from ORDERS;
ORDER_ID ORDER_DATE ORDER_MO CUSTOMER_ID ORDER_STATUS ORDER_TOTAL SALES_REP_ID PROMOTION_ID
---------- ---------------------------- -------- ----------- ------------ ----------- ------------ ------------
...
2452 06-OCT-07 07.59.43.462632 PM direct 149 5 12589 159
2453 04-OCT-07 08.53.34.362632 PM direct 116 0 129 153
2456 07-NOV-06 08.53.25.989889 PM direct 117 0 3878.4 163
2457 31-OCT-07 10.22.16.162632 PM direct 118 5 21586.2 159
105 rows selected.
SQL/JSON 函数 JSON_OBJECT 从关系 (SQL) 数据构造 JSON 对象。在 19c 之前,在关系表上使用此函数生成 JSON 时,必须为每一列指定一个显式字段名称-值对。
sql
select JSON_OBJECT (
key 'OrderID' value to_char(o.order_id) format JSON,
key 'OrderDate' value to_char(o.order_date) format JSON ) "Orders"
from ORDERS o;
Orders
--------------------------------------------------------------------------------------------
...
{"OrderID":2367,"OrderDate":27-JUN-08 08.53.32.335522 PM}
{"OrderID":2354,"OrderDate":14-JUL-08 05.18.23.234567 PM}
{"OrderID":2447,"OrderDate":27-JUL-08 07.59.10.223344 AM}
{"OrderID":2441,"OrderDate":01-AUG-08 10.22.48.734526 AM}
105 rows selected.
任务 6:Oracle 19C 中的 JSON 查询改进
在 Oracle 19c 中,函数 JSON_OBJECT 可以生成 JSON 对象,该对象仅接收关系列名称作为参数,该名称可能以表名称或别名开头,或以视图名称后跟一个点开头。例如 TABLE.COLUMN,或者只是 COLUMN。
sql
select JSON_OBJECT(order_id, order_date) from ORDERS;
JSON_OBJECT(ORDER_ID,ORDER_DATE)
------------------------------------------------------------------------------------------------------------------------
...
{"order_id":2368,"order_date":"2008-06-26T21:19:43.190089Z"}
{"order_id":2370,"order_date":"2008-06-26T23:22:11.647398Z"}
{"order_id":2367,"order_date":"2008-06-27T20:53:32.335522Z"}
{"order_id":2354,"order_date":"2008-07-14T17:18:23.234567Z"}
{"order_id":2447,"order_date":"2008-07-27T07:59:10.223344Z"}
{"order_id":2441,"order_date":"2008-08-01T10:22:48.734526Z"}
105 rows selected.
19c 中对使用通配符生成 JSON 文档进行了另一项改进。在这种情况下,参数可以是表名或别名,也可以是视图名称,后跟一个点和一个星号通配符 (.*),或者只是一个星号通配符,如以下示例所示。
sql
SELECT JSON_OBJECT(*) FROM ORDERS;
...
{"ORDER_ID":2456,"ORDER_DATE":"2006-11-07T20:53:25.989889Z","ORDER_MODE":"direct","CUSTOMER_ID":117,"ORDER_STATUS":0,"OR
DER_TOTAL":3878.4,"SALES_REP_ID":163,"PROMOTION_ID":null}
{"ORDER_ID":2457,"ORDER_DATE":"2007-10-31T22:22:16.162632Z","ORDER_MODE":"direct","CUSTOMER_ID":118,"ORDER_STATUS":5,"OR
DER_TOTAL":21586.2,"SALES_REP_ID":159,"PROMOTION_ID":null}
105 rows selected.
任务 7:使用自定义类型和通配符
在某些情况下,存在例外情况,对于具有某些自定义数据类型的列的表,不接受通配符,例如我们的表 CUSTOMERS。
sql
SQL> desc CUSTOMERS
Name Null? Type
----------------------------------------------------------------- -------- --------------------------------------------
CUSTOMER_ID NOT NULL NUMBER(6)
CUST_FIRST_NAME NOT NULL VARCHAR2(20)
CUST_LAST_NAME NOT NULL VARCHAR2(20)
CUST_ADDRESS CUST_ADDRESS_TYP
PHONE_NUMBERS PHONE_LIST_TYP
NLS_LANGUAGE VARCHAR2(3)
NLS_TERRITORY VARCHAR2(30)
CREDIT_LIMIT NUMBER(9,2)
CUST_EMAIL VARCHAR2(40)
ACCOUNT_MGR_ID NUMBER(6)
CUST_GEO_LOCATION MDSYS.SDO_GEOMETRY
DATE_OF_BIRTH DATE
MARITAL_STATUS VARCHAR2(20)
GENDER VARCHAR2(1)
INCOME_LEVEL VARCHAR2(20)
在普通 SQL 查询中允许使用星号通配符。
sql
select * from CUSTOMERS;
...
980 Daniel Loren
CUST_ADDRESS_TYP('1667 2010 St', '61311', 'Batavia', 'IL', 'IN')
PHONE_LIST_TYP('+91 80 012 3837')
hi INDIA 200 Daniel.Loren@REDSTART.EXAMPLE.COM 148
17-SEP-70 single M F: 110,000 - 129,999
319 rows selected.
但是如果我们尝试将星号通配符与 JSON_OBJECT 函数一起使用,我们会收到错误。
sql
SQL> select JSON_OBJECT(*) from CUSTOMERS;
select JSON_OBJECT(*) from CUSTOMERS
*
ERROR at line 1:
ORA-40579: star expansion is not allowed
有一个解决方案。此问题的解决方法是在原始表上创建一个视图。此视图将编译自定义数据类型并将结果用作标准数据类型。
sql
SQL> CREATE OR REPLACE VIEW view_cust AS SELECT * FROM customers;
View created.
SQL> SELECT json_object(*) FROM view_cust;
...
{"CUSTOMER_ID":980,"CUST_FIRST_NAME":"Daniel","CUST_LAST_NAME":"Loren","CUST_ADDRESS":{"STREET_ADDRESS":"1667 2010 St","
POSTAL_CODE":"61311","CITY":"Batavia","STATE_PROVINCE":"IL","COUNTRY_ID":"IN"},"PHONE_NUMBERS":["+91 80 012 3837"],"NLS_
LANGUAGE":"hi","NLS_TERRITORY":"INDIA","CREDIT_LIMIT":200,"CUST_EMAIL":"Daniel.Loren@REDSTART.EXAMPLE.COM","ACCOUNT_MGR_
ID":148,"CUST_GEO_LOCATION":null,"DATE_OF_BIRTH":"1970-09-17T00:00:00","MARITAL_STATUS":"single","GENDER":"M","INCOME_LE
VEL":"F: 110,000 - 129,999"}
319 rows selected.
总之,您可以传递用户定义的 SQL 对象类型的单个实例,而不是传递用于定义单个 JSON 对象成员的 SQL 表达式。这将生成一个 JSON 对象,其字段名称取自对象属性名称,其字段值取自对象属性值(JSON 生成以递归方式应用于该对象)。或者使用星号 (*) 通配符作为快捷方式,明确指定给定表或视图的所有列以生成对象成员。生成的对象字段名称是大写的列名称。您可以将通配符与表、视图或表别名一起使用。
任务 8:更新 JSON 文档
您现在可以使用新的 SQL 函数 JSON_MERGEPATCH 以声明方式更新 JSON 文档。您可以使用单个语句将一个或多个更改应用于多个文档。此功能提高了 JSON 更新操作的灵活性。
动态更新选定的 JSON 文档
您可以在 SELECT 列表中使用 JSON_MERGEPATCH 来修改选定的文档。修改后的文档可以返回或进一步处理。
从 JSON 文档中检索特定值
例如,我们可以检索包含西班牙国家描述的整个 JSON 文档。
sql
SQL> select DOC from MYJSON j where j.doc.geonames.geonameId = '2510769';
DOC
--------------------------------------------------------------------------------
{"geonames": [{
"continent": "EU",
"capital": "Madrid",
"languages": "es-ES,ca,gl,eu,oc",
"geonameId": 2510769,
"south": 36.0001044260548,
"isoAlpha3": "ESP",
"north": 43.7913565913767,
"fipsCode": "SP",
"population": "46723749",
"east": 4.32778473043961,
"isoNumeric": "724",
"areaInSqKm": "504782.0",
"countryCode": "ES",
"west": -9.30151567231899,
"countryName": "Spain",
"postalCodeFormat": "#####",
"continentName": "Europe",
"currencyCode": "EUR"
}]}
JSON Merge Patch 的作用有点像 UNIX 补丁实用程序 --- 你需要提供:
- 要修补的源文档和
- 指定要进行的更改的补丁文档,它会返回更新(修补)的源文档副本。
这是一个非常简单的示例,更改两个属性的 JSON 文档中的一个属性。
sql
SQL> SELECT json_mergepatch('{"CountryName":"Spain", "Capital":"Madrid"}', '{"Capital":"Toledo"}' RETURNING CLOB PRETTY) Medieval FROM dual;
MEDIEVAL
--------------------------------------------------------------------------------
{
"CountryName" : "Spain",
"Capital" : "Toledo"
}
但是,您不能使用它来添加、删除或更改数组元素(除非明确替换整个数组)。例如,我们从 GeoNames 收到的文档都是数组。
西班牙的国家/地区描述有一个字段 geonames,该字段具有数组值。此数组有一个元素 (geonames[0]),它是一个包含 17 个字段的对象,从"continent"到"currencyCode":
sql
SQL> SELECT j.doc.geonames[0] FROM MYJSON j where j.doc.geonames.geonameId = '2510769';
GEONAMES
------------------------------------------------------------------------------------------------------------------------
{"continent":"EU","capital":"Madrid","languages":"es-ES,ca,gl,eu,oc","geonameId":2510769,"south":36.0001044260548,"isoAl
pha3":"ESP","north":43.7913565913767,"fipsCode":"SP","population":"46723749","east":4.32778473043961,"isoNumeric":"724",
"areaInSqKm":"504782.0","countryCode":"ES","west":-9.30151567231899,"countryName":"Spain","postalCodeFormat":"#####","co
ntinentName":"Europe","currencyCode":"EUR"}
从 JSON 文档更新特定值
记下该文档中的 capital 属性 --- "capital":"Madrid"。
动态更改该属性 --- "capital":"Toledo"。我们可以以漂亮的格式打印相同的结果,只是为了更加突出它。
sql
SQL> SELECT json_mergepatch(j.doc.geonames[0], '{"capital":"Toledo"}' RETURNING CLOB PRETTY) Medieval
FROM myjson j where j.doc.geonames.geonameId = '2510769';
MEDIEVAL
--------------------------------------------------------------------------------
{
"continent" : "EU",
"capital" : "Toledo",
"languages" : "es-ES,ca,gl,eu,oc",
"geonameId" : 2510769,
"south" : 36.0001044260548,
"isoAlpha3" : "ESP",
"north" : 43.7913565913767,
"fipsCode" : "SP",
"population" : "46723749",
"east" : 4.32778473043961,
"isoNumeric" : "724",
"areaInSqKm" : "504782.0",
"countryCode" : "ES",
"west" : -9.30151567231899,
"countryName" : "Spain",
"postalCodeFormat" : "#####",
"continentName" : "Europe",
"currencyCode" : "EUR"
}
更改该 JSON 文档中的两个属性。请记住,点符号查询的返回值始终是字符串,我们可以使用字符串。例如,我们可以添加它的第一部分(在元素 geonames[0] 之前)和最后一部分,以将此单个元素转换回数组,并以漂亮的格式打印结果数组。
sql
SQL> SELECT json_mergepatch(j.doc, '{"geonames": [' || json_mergepatch(j.doc.geonames[0], '{"capital":"Toledo", "countryName" : "Medieval Spain"}') || ']}' RETURNING CLOB PRETTY) Medieval
FROM myjson j where j.doc.geonames.geonameId = '2510769';
MEDIEVAL
--------------------------------------------------------------------------------
{
"geonames" :
[
{
"continent" : "EU",
"capital" : "Toledo",
"languages" : "es-ES,ca,gl,eu,oc",
"geonameId" : 2510769,
"south" : 36.0001044260548,
"isoAlpha3" : "ESP",
"north" : 43.7913565913767,
"fipsCode" : "SP",
"population" : "46723749",
"east" : 4.32778473043961,
"isoNumeric" : "724",
"areaInSqKm" : "504782.0",
"countryCode" : "ES",
"west" : -9.30151567231899,
"countryName" : "Medieval Spain",
"postalCodeFormat" : "#####",
"continentName" : "Europe",
"currencyCode" : "EUR"
}
]
}
此外,我们可以将修改后的元素(包含更新的值)作为附加 JSON 文档添加到第一个包含原始值的元素中。例如,我们可以保留原始数组元素,并添加包含新值的新元素。
sql
SQL> SELECT json_mergepatch(j.doc, '{"geonames": [' || j.doc.geonames[0] || ',' || json_mergepatch(j.doc.geonames[0], '{"capital":"Toledo", "countryName" : "Medieval Spain"}') || ']}' RETURNING CLOB PRETTY) Medieval
FROM myjson j where j.doc.geonames.geonameId = '2510769';
MEDIEVAL
--------------------------------------------------------------------------------
{
"geonames" :
[
{
"continent" : "EU",
"capital" : "Madrid",
"languages" : "es-ES,ca,gl,eu,oc",
"geonameId" : 2510769,
"south" : 36.0001044260548,
"isoAlpha3" : "ESP",
"north" : 43.7913565913767,
"fipsCode" : "SP",
"population" : "46723749",
"east" : 4.32778473043961,
"isoNumeric" : "724",
"areaInSqKm" : "504782.0",
"countryCode" : "ES",
"west" : -9.30151567231899,
"countryName" : "Spain",
"postalCodeFormat" : "#####",
"continentName" : "Europe",
"currencyCode" : "EUR"
},
{
"continent" : "EU",
"capital" : "Toledo",
"languages" : "es-ES,ca,gl,eu,oc",
"geonameId" : 2510769,
"south" : 36.0001044260548,
"isoAlpha3" : "ESP",
"north" : 43.7913565913767,
"fipsCode" : "SP",
"population" : "46723749",
"east" : 4.32778473043961,
"isoNumeric" : "724",
"areaInSqKm" : "504782.0",
"countryCode" : "ES",
"west" : -9.30151567231899,
"countryName" : "Medieval Spain",
"postalCodeFormat" : "#####",
"continentName" : "Europe",
"currencyCode" : "EUR"
}
]
}
使用选定的当前值更新 JSON 文档
同样,我们可以使用带有 JSON_MERGEPATCH 函数的更改后的数组来插入或更新存储在数据库中的 JSON 文档。
sql
INSERT INTO MYJSON (doc) SELECT json_mergepatch(j.doc, '{"geonames": [' || j.doc.geonames[0] || ',' || json_mergepatch(j.doc.geonames[0], '{"capital":"Toledo", "countryName" : "Medieval Spain"}') || ']}' RETURNING CLOB PRETTY) Medieval
FROM myjson j where j.doc.geonames.geonameId = '2510769';
1 row created.
使用 JSON Merge Patch 更新 JSON 列
您可以在 UPDATE 语句中使用 JSON_MERGEPATCH 来更新 JSON 列中的文档。我们将使用表中的第一个 JSON 文档,即有关该 Oracle Workshop 的文档。
sql
SQL> select DOC from MYJSON where ID = 1;
DOC
--------------------------------------------------------------------------------
{
"workshopName": "Database 19c New Features for Developers",
"audienceType": "Partners Technical Staff",
"location": {
"company": "Oracle",
"office": "Customer Visiting Center",
"region": "EMEA"
}
}
这是一个简单的 JSON 文档,包含三个字段。第三个字段也是一个包含三个字段的集合。
更新 JSON 文档中的特定元素
我们可以使用普通的 UPDATE 语句和 JSON_MERGEPATCH 函数来更新第二个字段。运行相同的 SELECT 语句时,我们注意到文档不再是漂亮的打印。
sql
SQL> update MYJSON set DOC = json_mergepatch(DOC, '{"audienceType": "Developers and DBAs"}') where ID = 1;
1 row updated.
SQL> select DOC from MYJSON where ID = 1;
DOC
--------------------------------------------------------------------------------
{"workshopName":"Database 19c New Features for Developers","audienceType":"Devel
opers and DBAs","location":{"company":"Oracle","office":"Customer Visiting Cente
r","region":"EMEA"}}
您可以将 PRETTY 子句添加到 UPDATE 语句中,这样从我们的表返回文档时就会更加清晰。
sql
update MYJSON set DOC = json_mergepatch(DOC, '{"audienceType": "Developers and DBAs"}' RETURNING CLOB PRETTY) where ID = 1;
1 row updated.
SQL> select DOC from MYJSON where ID = 1;
DOC
--------------------------------------------------------------------------------
{
"workshopName" : "Database 19c New Features for Developers",
"audienceType" : "Developers and DBAs",
"location" :
{
"company" : "Oracle",
"office" : "Customer Visiting Center",
"region" : "EMEA"
}
}
这个看起来好多了。如果您想将更改保留在数据库中,请记住提交更改。
sql
commit;
更新 Oracle 数据库内的 JSON 文档就是这么简单。
任务 9:JSON 物化视图支持
物化视图查询重写已得到增强,因此具有 JSON_EXISTS、JSON_VALUE 和其他函数的查询可以利用基于包含 JSON_TABLE 函数的查询创建的物化视图。
当表中的 JSON 文档包含数组时,此功能特别有用。这种类型的物化视图为访问这些 JSON 数组中的数据提供了快速的性能。
具有快速刷新查询重写的物化视图
作为 Oracle 19c 中的一项性能增强,如果您在 json_table 上创建语句刷新物化视图并且应用了一些其他条件,那么与定义视图的查询匹配的查询可以重写为物化视图表访问。您可以使用此功能,而不必创建多个功能索引。
创建物化视图
使用 ON STATEMENT 子句创建一个物化视图,帮助我们检索有关中世纪城堡的信息。ENABLE QUERY REWRITE 子句不是强制性的,即使未指定,性能增强也会适用。请注意,每个列都明确指定了错误和空值处理。
sql
CREATE MATERIALIZED VIEW json_castles_mv
REFRESH FAST ON STATEMENT
ENABLE QUERY REWRITE
AS
SELECT j.id jsonID, jt.geonameId ID, jt.countryName Country,
convert(jt.adminName1,'WE8ISO8859P1','AL32UTF8') Region,
convert(jt.adminName2,'WE8ISO8859P1','AL32UTF8') Sub_Region,
jt.fcode, convert(jt.toponymName,'WE8ISO8859P1','AL32UTF8') Title,
convert(jt.name,'WE8ISO8859P1','AL32UTF8') Name FROM MYJSON j,
JSON_TABLE(DOC, '$' COLUMNS
(NESTED PATH '$.geonames[*]'
COLUMNS (countryName VARCHAR2(80) PATH '$.countryName' ERROR ON ERROR NULL ON EMPTY,
adminName1 VARCHAR2(80) PATH '$.adminName1' ERROR ON ERROR NULL ON EMPTY,
adminName2 VARCHAR2(80) PATH '$.adminName2' ERROR ON ERROR NULL ON EMPTY,
toponymName VARCHAR2(120) PATH '$.toponymName' ERROR ON ERROR NULL ON EMPTY,
name VARCHAR2(80) PATH '$.name' ERROR ON ERROR NULL ON EMPTY,
adminCode1 VARCHAR(8) PATH '$.adminCode1' ERROR ON ERROR NULL ON EMPTY,
fcode VARCHAR2(6) PATH '$.fcode' ERROR ON ERROR NULL ON EMPTY,
geonameId VARCHAR2(10) PATH '$.geonameId' ERROR ON ERROR NULL ON EMPTY)))
AS jt;
Materialized view created.
使用以下查询测试物化视图。(可选)在运行此查询时使用 set Timing on,以及在从 GeoNames Web 服务检索所有必需的 JSON 文档后用于检索有关城堡的信息的查询,然后比较结果。差异可能看起来微不足道,因为只有 269 座城堡,但想象一下,我们在一个应用程序中可以有数百万行,并且有数千个并发用户。
sql
set timing on
SELECT Country, Region, Sub_Region, Title, Name FROM json_castles_mv WHERE fcode = 'CSTL';
...
Spain Castille and Leon Castillo De Los Templarios Castillo De Los Templari
Leon os
Spain Castille and Leon Torreon de La Vecilla Torreon de La Vecilla
Leon
269 rows selected.
Elapsed: 00:00:00.01
性能提升
使用查询重写和物化视图通常可以实现显著的性能提升,在本练习中,我们将研究如何在不更改应用程序的情况下实现这些性能提升。
检查查询重写机制
我们将使用以下 SELECT 语句作为示例。在数据库上运行此查询。它返回每个子区域中的第一座城堡,因此它应该列出 45 座城堡。
sql
column CODE format a4
SELECT JSON_VALUE(doc, '$.geonames[0].fcode') Code,
JSON_VALUE(doc, '$.geonames[0].countryName') Country,
JSON_VALUE(doc, '$.geonames[0].adminName1') Region,
JSON_VALUE(doc, '$.geonames[0].adminName2') Sub_Region,
JSON_VALUE(doc, '$.geonames[0].toponymName') Title,
JSON_VALUE(doc, '$.geonames[0].name') Name
from MYJSON
where JSON_VALUE(doc, '$.geonames[0].fcode') = 'CSTL'
order by Region, Sub_Region;
...
CSTL Spain Valencia Castellon Castell de Pen?-scola Castell de Pen?-scola
CSTL Spain Valencia Valencia Castillo de Bu??ol Castillo de Bu??ol
45 rows selected.
Elapsed: 00:00:00.01
刷新共享池,从内存中刷新缓存的执行计划和 SQL 查询。
sql
SQL> ALTER SYSTEM FLUSH SHARED_POOL;
System altered.
Elapsed: 00:00:00.51
显示 Oracle 优化器为此语句选择的执行计划。
sql
EXPLAIN PLAN for
SELECT JSON_VALUE(doc, '$.geonames[0].fcode') Code,
JSON_VALUE(doc, '$.geonames[0].countryName') Country,
JSON_VALUE(doc, '$.geonames[0].adminName1') Region,
JSON_VALUE(doc, '$.geonames[0].adminName2') Sub_Region,
JSON_VALUE(doc, '$.geonames[0].toponymName') Title,
JSON_VALUE(doc, '$.geonames[0].name') Name
from MYJSON
where JSON_VALUE(doc, '$.geonames[0].fcode') = 'CSTL'
order by Region, Sub_Region;
Explained.
Elapsed: 00:00:00.05
DBMS_XPLAN 包提供了一种简单的方法,可以以几种预定义的格式显示 EXPLAIN PLAN 命令的输出。默认情况下,表函数 DISPLAY 会格式化并显示计划表的内容。我们可以看到点符号调用被重写为 JSON_TABLE 调用,因为我们可以在计划中看到 JSONTABLE EVALUATION 步骤 (6),并且我们可以看到数据已从 JSON_CASTLES_MV 物化视图 (4) 返回。
sql
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3162132558
---------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5146 | 9M| | 3915 (1)| 00:00:01 |
| 1 | SORT ORDER BY | | 5146 | 9M| 13M| 3915 (1)| 00:00:01 |
| 2 | NESTED LOOPS | | 5146 | 9M| | 1726 (1)| 00:00:01 |
|* 3 | HASH JOIN RIGHT SEMI | | 63 | 124K| | 7 (0)| 00:00:01 |
|* 4 | MAT_VIEW ACCESS FULL| JSON_CASTLES_MV | 113 | 904 | | 4 (0)| 00:00:01 |
| 5 | TABLE ACCESS FULL | MYJSON | 75 | 147K| | 3 (0)| 00:00:01 |
|* 6 | JSONTABLE EVALUATION | | | | | | |
---------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("SYS_JMV_1"."JSONID"="MYJSON"."ID")
4 - filter("SYS_JMV_1"."FCODE"='CSTL')
6 - filter("P"."C_06$"='CSTL')
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
24 rows selected.
Elapsed: 00:00:00.13
如果查询太简单,则可能不会进行查询重写,在这种情况下它将没有资格进行重写以使用物化视图。
任务 10:JSON 对象映射
此功能支持 JSON 数据与用户定义的 SQL 对象类型和集合之间的映射。您可以使用 SQL/JSON 函数 JSON_VALUE 将 JSON 数据转换为 SQL 对象类型的实例。相反,您可以使用 SQL/JSON 函数 JSON_OBJECT 或 JSON_ARRAY 从 SQL 对象类型的实例生成 JSON 数据。
创建您自己的 JSON 结构
我们将从第二个用例开始,使用 SQL/JSON 函数 JSON_OBJECT 生成 JSON 数据。您将使用 JSON_CASTLES_MV 物化视图中的关系数据来生成具有自定义结构的 JSON 文档。
使用自定义 JSON 结构插入 Castles 数据
使用此语句生成表示物化视图关系记录的 JSON 文档版本的数据。
sql
SELECT JSON_OBJECT(
'CastleID' : id,
'Country' : country,
'Region' : region,
'Sub_Region' : sub_region,
'Name' : '"' || title || '"'
FORMAT JSON)
FROM json_castles_mv WHERE fcode = 'CSTL';
...
{"CastleID":"12022513","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Avila","Name":"Castillo de Manqueosp
ese"}
{"CastleID":"10296204","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castle of Miranda de
Ebro"}
{"CastleID":"6697975","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castillo de Castrojer
iz"}
{"CastleID":"10295353","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Castillo De Los Templa
rios"}
{"CastleID":"11185994","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Torreon de La Vecilla"
}
269 rows selected.
Elapsed: 00:00:00.01
某些客户端驱动程序(例如 SQL Developer)可能会尝试扫描查询文本并识别绑定变量,然后再将查询发送到数据库。在某些情况下,JSON_OBJECT 中作为名称-值分隔符的冒号可能会被误解为引入绑定变量。您可以使用关键字 VALUE 作为分隔符来避免此问题('Country ' VALUE country),或者您可以简单地将对的值部分括在括号中:'Country':(country)。以下是可以在 SQL Developer 中成功执行的相同 SELECT 语句。
sql
SELECT JSON_OBJECT(
key 'CastleID' value id,
key 'Country' value country,
key 'Region' value region,
key 'Sub_Region' value sub_region,
key 'Name' value '"' || title || '"'
FORMAT JSON)
FROM json_castles_mv WHERE fcode = 'CSTL';
...
{"CastleID":"10296204","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castle of Miranda de
Ebro"}
{"CastleID":"6697975","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castillo de Castrojer
iz"}
{"CastleID":"10295353","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Castillo De Los Templa
rios"}
{"CastleID":"11185994","Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Torreon de La Vecilla"
}
269 rows selected.
Elapsed: 00:00:00.01
这些从关系数据生成的 JSON 文档具有完全个性化的结构,可以插入到我们的表中。
sql
INSERT INTO MYJSON (doc)
SELECT JSON_OBJECT(
'CastleID' : id,
'Country' : country,
'Region' : region,
'Sub_Region' : sub_region,
'Name' : '"' || title || '"'
FORMAT JSON)
FROM json_castles_mv WHERE fcode = 'CSTL';
269 rows created.
Elapsed: 00:00:01.58
SQL> COMMIT;
Commit complete.
运行以下选择来验证插入的文档,并观察这些是单独的 JSON 对象,描述每个中世纪城堡,来自我们数据库中的 269 个条目。
sql
SELECT j.id, JSON_SERIALIZE(j.doc PRETTY) FROM myjson j WHERE j.doc."CastleID" is not null;
...
343
{
"CastleID" : "10295353",
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Leon",
"Name" : "Castillo De Los Templarios"
}
344
{
"CastleID" : "11185994",
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Leon",
"Name" : "Torreon de La Vecilla"
}
269 rows selected.
Elapsed: 00:00:00.04
观察这些 JSON 文档中的结构和值。请注意,我们的游客推荐应用程序可以更轻松地为最终用户列出这些城堡。
JSON 到用户定义对象类型实例
相反,您可以将 JSON 文档转换为用户定义的对象类型。
创建新类型并检索数据
我们可以创建可能与表中的 JSON 数据不匹配的对象类型。一个具有所有属性,另一个具有比数据库中可用的 JSON 数据更少的属性。创建一个具有 JSON 文档的所有属性的新类型。
sql
CREATE TYPE t_castle AS OBJECT (
"CastleID" NUMBER(10),
"Country" VARCHAR2(10),
"Region" VARCHAR2(120),
"Sub_Region" VARCHAR2(120),
"Name" VARCHAR2(120)
);
/
Type created.
现在使用 SQL/JSON 函数 JSON_VALUE 将 JSON 数据转换为 SQL 对象类型的实例。只需一个查询即可完成此操作。
sql
SELECT JSON_VALUE(j.doc, '$' RETURNING t_castle) AS castle FROM myjson j WHERE j.doc."CastleID" is not null;
...
T_CASTLE(10296204, 'Spain', 'Castille and Leon', 'Burgos', 'Castle of Miranda de Ebro')
T_CASTLE(6697975, 'Spain', 'Castille and Leon', 'Burgos', 'Castillo de Castrojeriz')
T_CASTLE(10295353, 'Spain', 'Castille and Leon', 'Leon', 'Castillo De Los Templarios')
T_CASTLE(11185994, 'Spain', 'Castille and Leon', 'Leon', 'Torreon de La Vecilla')
269 rows selected.
Elapsed: 00:00:00.02
在 Oracle Database 19c 中,除了我们已经测试过的可选 ERROR 子句之外,函数 JSON_VALUE 还接受可选的 RETURNING 子句。在这种情况下,JSON_VALUE 函数在 RETURNING 子句中使用用户定义的对象类型,并根据源 JSON 文档中的数据从查询返回实例化的对象类型。
对相同数据使用多种类型
创建另一种与文档中的 JSON 数据不匹配的对象类型。此对象类型提供 JSON 结构的简化版本,仅使用两个属性。假设我们的应用程序中有一个所有中世纪城堡的列表,我们只想使用 ID 作为参考来显示名称。在这种情况下,我们不想将内存用于我们不使用的属性,更简单的对象类型可以完成这项工作。
sql
CREATE TYPE t_castle_short AS OBJECT (
"CastleID" NUMBER(10),
"Name" VARCHAR2(120)
);
/
Type created.
查看此查询的结果,并与前一个查询的结果进行比较,并检查差异。
sql
SELECT JSON_VALUE(j.doc, '$' RETURNING t_castle_short) AS castle FROM myjson j WHERE j.doc."CastleID" is not null;
...
T_CASTLE_SHORT(10296204, 'Castle of Miranda de Ebro')
T_CASTLE_SHORT(6697975, 'Castillo de Castrojeriz')
T_CASTLE_SHORT(10295353, 'Castillo De Los Templarios')
T_CASTLE_SHORT(11185994, 'Torreon de La Vecilla')
269 rows selected.
这些自定义对象类型可用于直接从数据库层优化我们的应用程序。
创建具有自定义类型列的表
自定义对象类型也可用作表列。创建此新表,其中包含一个自定义类型的列。
sql
SQL> CREATE TABLE mycastles ( castle t_castle );
Table created.
在新表中插入有关我们所有 269 座中世纪城堡的信息。
sql
INSERT INTO mycastles
SELECT JSON_VALUE(j.doc, '$' RETURNING t_castle) AS castle
FROM myjson j WHERE j.doc."CastleID" is not null;
269 rows created.
COMMIT;
从新表中选择全部 269 条记录,作为用户定义的 SQL 对象返回。
sql
SELECT * FROM mycastles;
...
T_CASTLE(10296204, 'Spain', 'Castille and Leon', 'Burgos', 'Castle of Miranda de Ebro')
T_CASTLE(6697975, 'Spain', 'Castille and Leon', 'Burgos', 'Castillo de Castrojeriz')
T_CASTLE(10295353, 'Spain', 'Castille and Leon', 'Leon', 'Castillo De Los Templarios')
T_CASTLE(11185994, 'Spain', 'Castille and Leon', 'Leon', 'Torreon de La Vecilla')
269 rows selected.
现在我们在新表中只有城堡,以及我们的应用程序中所需的属性。
用户定义对象类型实例转换为 JSON
将用户定义的 SQL 对象类型实例转换为 JSON 文档同样容易。
将 JSON 转换为自定义类型
使用 JSON_OBJECT 函数,我们可以从新表中检索使用用户定义的对象类型存储的此数据的 JSON 表示形式。
sql
SELECT JSON_OBJECT(castle) AS castle FROM mycastles;
...
{"CastleID":10296204,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castle of Miranda de E
bro"}
{"CastleID":6697975,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castillo de Castrojeriz
"}
{"CastleID":10295353,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Castillo De Los Templari
os"}
{"CastleID":11185994,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Torreon de La Vecilla"}
269 rows selected.
在这种情况下,我们的应用程序使用 JSON 格式,您可以从 SELECT 语句动态进行此转换。
使用漂亮的格式
漂亮的格式可以帮助您更好地理解输出。
sql
SELECT JSON_SERIALIZE( JSON_OBJECT(castle) PRETTY) AS castle FROM mycastles;
...
{
"CastleID" : 10296204,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Burgos",
"Name" : "Castle of Miranda de Ebro"
}
{
"CastleID" : 6697975,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Burgos",
"Name" : "Castillo de Castrojeriz"
}
{
"CastleID" : 10295353,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Leon",
"Name" : "Castillo De Los Templarios"
}
{
"CastleID" : 11185994,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Leon",
"Name" : "Torreon de La Vecilla"
}
269 rows selected.
这是一个非常简单的例子,并不是完全必要的,但想象一下你有一个包含数百个属性的 JSON 文档。
将自定义格式值分组到数组中
JSON_ARRAY 函数还会将用户定义的对象类型实例转换为 JSON。在下面的示例中,我们为每一行创建一个 JSON 数组,其中包含用于参考的城堡 ID 号,以及从用户定义的对象类型中检索到的城堡行的 JSON 表示形式。
sql
SELECT JSON_ARRAY(c.castle."CastleID", castle) AS castle FROM mycastles c;
...
[10296204,{"CastleID":10296204,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castle of Mi
randa de Ebro"}]
[6697975,{"CastleID":6697975,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Burgos","Name":"Castillo de Ca
strojeriz"}]
[10295353,{"CastleID":10295353,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Castillo De Lo
s Templarios"}]
[11185994,{"CastleID":11185994,"Country":"Spain","Region":"Castille and Leon","Sub_Region":"Leon","Name":"Torreon de La
Vecilla"}]
269 rows selected.
输出是一个包含两个记录的集合,具有有效的 JSON 结构。
对数组使用漂亮的格式
和以前一样,漂亮的格式对于理解输出很有用。
sql
SELECT JSON_SERIALIZE( JSON_ARRAY(c.castle."CastleID", castle) PRETTY) AS castle FROM mycastles c;
...
[
10296204,
{
"CastleID" : 10296204,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Burgos",
"Name" : "Castle of Miranda de Ebro"
}
]
[
6697975,
{
"CastleID" : 6697975,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Burgos",
"Name" : "Castillo de Castrojeriz"
}
]
[
10295353,
{
"CastleID" : 10295353,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Leon",
"Name" : "Castillo De Los Templarios"
}
]
[
11185994,
{
"CastleID" : 11185994,
"Country" : "Spain",
"Region" : "Castille and Leon",
"Sub_Region" : "Leon",
"Name" : "Torreon de La Vecilla"
}
]
269 rows selected.
该实验全部结束!总共做了3回,才把笔记做完。要理解这些概念,光做实验是不够的,还需要看JSON开发者指南。