Oracle LiveLabs实验:Database 19c - JSON

本文为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 许可。您可以自由地:

  • 共享 --- 以任何媒介或格式复制和重新分发材料;
  • 改编 --- 出于任何目的(甚至商业目的)重新混合、转换和基于材料进行创作。
  1. 单击 GeoNames 网站右上角的"登录"链接,然后创建一个新帐户。

    注意:创建 GeoNames 帐户后,您将收到一封电子邮件以激活帐户。

  2. 在帐户页面 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开发者指南。

相关推荐
betazhou1 小时前
oracle goldengate from mongodb to oracle的实时同步
数据库·mongodb·oracle
m0_748239471 小时前
PostgreSQL在Linux环境下的常用命令总结
linux·postgresql·oracle
前端小尘5 小时前
多语言插件i18n Ally的使用
javascript·vscode·typescript·json
m0_7482365810 小时前
SQL-Server链接服务器访问Oracle数据
服务器·sql·oracle
2301_7951685815 小时前
javascript基础从小白到高手系列一十二:JSON
javascript·udp·json
你疯了抱抱我16 小时前
Oracle知识点1:如何修改密码并登录sys/system账号
数据库·oracle
静水楼台x18 小时前
Java中json的一点理解
java·后端·json
少年攻城狮21 小时前
Oracle系列---【Oracle中密码的策略如何设置】
数据库·oracle
小蒜学长21 小时前
疾病防控综合系统设计与实现(代码+数据库+LW)
前端·数据库·vue.js·spring boot·后端·oracle