在 oracle 18 中通过 json_table 解析 json

Parse json through json_table in oracle 18

有一个请求例如:

with j (sJson) as (
   select '{
      "ID":"1444284517",
      "ID_ORD":"4255;2187606199",
      "Vals":{
               "CODE":"ONB2B3BB8",
               "DORD":"25.04.2021"
             }
   }'
   from dual
)

select jt.*
from j
cross apply json_table (j.sJson, '$'
   columns
      ID varchar2(32) path '$.ID',
      ID_ORD varchar2(32) path '$.ID_ORD',
        nested path '$.Vals[*]'
              columns (
                 CODE varchar2(9) path '$.CODE',
                 DORD varchar2(30) path '$.DORD',
                 ....
              )) jt    

column中可能有不同的字段。
如何在不指定类型和路径的情况下列出columns中的所有字段?那如何让它动态解析呢?需要放弃CODE varchar2(9) path '$.CODE'
我期望这样的结果:

| ID         | ID_ORD          | CODE      | DORD       |
+------------+-----------------+-----------+------------+
| 1444284517 | 4255;2187606199 | ONB2B3BB8 | 25.04.2021 | 

您可以定义函数:

CREATE FUNCTION get_keys(
  value IN CLOB
) RETURN SYS.ODCIVARCHAR2LIST PIPELINED
IS
  js   JSON_OBJECT_T := JSON_OBJECT_T( value );
  keys JSON_KEY_LIST;
BEGIN
  keys := js.get_keys();
  FOR i in 1 .. keys.COUNT LOOP
    PIPE ROW ( keys(i) );
  END LOOP;
END;
/

CREATE FUNCTION get_value(
  value IN CLOB,
  path  IN VARCHAR2
) RETURN VARCHAR2
IS
  js JSON_OBJECT_T := JSON_OBJECT_T( value );
BEGIN
  RETURN js.get_string( path );
END;
/

然后使用查询:

WITH j (sJson) as (
   select '{
      "ID":"1444284517",
      "ID_ORD":"4255;2187606199",
      "Vals":{
               "CODE":"ONB2B3BB8",
               "DORD":"25.04.2021"
             }
   }'
   from dual
)
SELECT jt.id,
       jt.id_ord,
       k.COLUMN_VALUE AS Key,
       get_value( jt.vals, k.COLUMN_VALUE ) AS value
FROM   j
       CROSS APPLY JSON_TABLE(
         j.sjson,
         '$'
         COLUMNS (
           id     VARCHAR2(20) PATH '$.ID',
           id_ord VARCHAR2(30) PATH '$.ID_ORD',
           vals   VARCHAR2(4000) FORMAT JSON PATH '$.Vals'
         )
       ) jt
       CROSS APPLY get_keys( jt.vals ) k

输出:

ID ID_ORD KEY VALUE
1444284517 4255;2187606199 CODE ONB2B3BB8
1444284517 4255;2187606199 DORD 25.04.2021

(注意:SQL 不支持动态列数,因此您需要提供固定列数,例如 keyvalue 以及将输出作为行而不是列。)

db<>fiddle here

如果您真的想要可变数量的列,那么您将需要对基于列的结果进行动态透视(例如来自@MTO 的回答),或者生成动态 json_table

您可以使用 json_dataguide() 为特定的 Vals 数组生成架构,然后通过它自己的 json_table 和循环来生成 columns 子句名称、类型和路径。

此示例重新生成您的原始查询,除了它使用 JSON 字符串的绑定变量而不是 CTE;然后使用与数据指南相同的字符串打开它。

create or replace function dynamic_parse(sJson clob)
return sys_refcursor as
  sGuide clob;
  sSQL clob;
  rc sys_refcursor;
begin
  -- initial static part of query
  sSQL := q'^select jt.*
from json_table (:sJson, '$'
   columns
      ID varchar2(32) path '$.ID',
      ID_ORD varchar2(32) path '$.ID_ORD',
        nested path '$.Vals[*]'
              columns (^';

  select json_dataguide(jt.vals)
  into sGuide
  from json_table (sJson, '$'
    columns
      VALS clob format json path '$.Vals'
  ) jt;

  for r in (
    select jt.*
    from json_table (sGuide format json, '$[*]'
      columns
        indx for ordinality,
        path varchar2(30) path '$."o:path"',
        type varchar2(30) path '$.type',
        length number path '$."o:length"'
    ) jt
  )
  loop
    sSQL := sSQL || case when r.indx > 1 then ',' end
      || chr(10) || '                 '
      || '"' || substr(r.path, 3) || '"'
      -- may need to handle other data type more carefully too
      || ' ' || case when r.type = 'string' then 'varchar2(' || r.length || ')' else r.type end
      || q'^ path '^' || r.path || q'^'^';
  end loop;

  -- final static part of query
  sSQL := sSQL || chr(10) || '              )) jt';
  dbms_output.put_line(sSQL);

  open rc for sSQL using sJson;
  return rc;
end;
/

db<>fiddle 显示了一些步骤、生成的动态 SQL 语句,以及如何使用它打开引用游标。生成的动态语句出来是:

select jt.*
from json_table (:sJson, '$'
   columns
      ID varchar2(32) path '$.ID',
      ID_ORD varchar2(32) path '$.ID_ORD',
        nested path '$.Vals[*]'
              columns (
                 "CODE" varchar2(16) path '$.CODE',
                 "DORD" varchar2(16) path '$.DORD'
              )) jt

它还显示了一个调用函数并打印引用游标内容的虚拟匿名块(因为 db<>fiddle 不支持 select func_returning_ref_cursor from dual,您可以在 SQL 开发人员等)如:

1444284517:4255;2187606199:ONB2B3BB8:25.04.2021

...但这证明了这种方法的一个问题:调用者必须提前知道列的数量和类型,或者它自己必须使用一些动态处理元素。

您可能还想探索更广泛的 JSON Data Guide 功能。

SQL/JSON 函数 json_table 将特定 JSON 数据投影到各种 SQL 数据类型的列。您可以使用它 将 JSON 文档 的部分映射到新的虚拟 table 的行和列中,您也可以将其视为内联视图.

因为您的目标似乎是:通过 json_table 解析 json。我建议阅读 JSON 数据指南功能。here。例如,您可以根据行的结构创建视图,DBMS_JSON 包将为您创建 SQL/JSON。无需编码。您还可以将虚拟列添加到原始 table.

使用您的文档创建 table,

我添加了一个新字段 XARR 以展示 JSON 数据指南功能的强大功能

drop table test_json;

create table test_json
as
with j (sJson ) as (
   select CAST ('{
      "ID":"1444284517",
      "ID_ORD":"4255;2187606199",
      "Vals":{
               "CODE":"ONB2B3BB8",
               "DORD":"25.04.2021",
               "XARR":[{"IDARR":1},{"IDARR":2},{"IDARR":3}]
             }
   }' as VARCHAR2(2000))
   from dual
)
select *
from j;

在您的文档之上创建一个视图,

SELECT json_dataguide(SJSON, DBMS_JSON.FORMAT_HIERARCHICAL, DBMS_JSON.PRETTY)
      FROM TEST_JSON;

drop view MYVIEW;
DECLARE
  dg CLOB;
  BEGIN
    SELECT json_dataguide(SJSON, DBMS_JSON.FORMAT_HIERARCHICAL, DBMS_JSON.PRETTY)
      INTO dg
      FROM TEST_JSON where rownum < 2;
    DBMS_JSON.create_view('MYVIEW',
                          'TEST_JSON',
                          'SJSON',
                          dg);
  END;
/

查询数据

直接使用视图或检索生成的SQL

select text from all_views where view_name =  'MYVIEW';      

TEXT                                                                                                                                                                                                                                                                                                                                                         
-------------------------------------------------------------------
SELECT RT."SJSON",JT."ID",JT."CODE",JT."DORD",JT."ID_ORD",JT."IDARR"
FROM "ADMIN"."TEST_JSON" RT,
JSON_TABLE("SJSON", '$[*]' COLUMNS 
"ID" varchar2(16) path '$.ID',
"CODE" varchar2(16) path '$.Vals.CODE',
"DORD" varchar2(16) path '$.Vals.DORD',
 NESTED PATH '$.Vals.XARR[*]' COLUMNS (
"IDARR" number path '$.IDARR'),
"ID_ORD" varchar2(16) path '$.ID_ORD')JT 

select * from myview;

ID         CODE      DORD       ID_ORD          IDARR 
---------- --------- ---------- --------------- --- 
1444284517 ONB2B3BB8 25.04.2021 4255;2187606199   1 
1444284517 ONB2B3BB8 25.04.2021 4255;2187606199   2 
1444284517 ONB2B3BB8 25.04.2021 4255;2187606199   3