将大型 JSON 文件存储到 Oracle 数据库中

Posted

技术标签:

【中文标题】将大型 JSON 文件存储到 Oracle 数据库中【英文标题】:Store big JSON files into Oracle DB 【发布时间】:2014-11-26 05:47:26 【问题描述】:

我正在使用以下脚本从 MongoDB 读取数据作为 JSON 文件。

DECLARE
  l_param_list     VARCHAR2(512);
 
  l_http_request   UTL_HTTP.req;
  l_http_response  UTL_HTTP.resp;
  l_response_text  CLOB;
  --l_response_text  VARCHAR2(32767);

l_list json_list;
A_id           VARCHAR2(100);
Photo          VARCHAR2(32767);
A_Name         VARCHAR2(100);
Remarks        VARCHAR2(100);
Status         VARCHAR2(100);
UserId         VARCHAR2(100);
A_Date         VARCHAR2(100);
A_Time         VARCHAR2(100);
MSG_status     VARCHAR2(100);
Oracle_Flag    VARCHAR2(100);
acl            VARCHAR2(100);


BEGIN

  -- service's input parameters

  -- preparing Request...
  l_http_request := UTL_HTTP.begin_request('https://api.appery.io/rest/1/db/collections/Photos?where=%7B%22Oracle_Flag%22%3A%22Y%22%7D'
                                          , 'GET'
                                          , 'HTTP/1.1');
 
  -- ...set header's attributes
  UTL_HTTP.set_header(l_http_request, 'X-Appery-Database-Id', '53f2dac5e4b02cca64021dbe');
  --UTL_HTTP.set_header(l_http_request, 'Content-Length', LENGTH(l_param_list));

  -- ...set input parameters
 -- UTL_HTTP.write_text(l_http_request, l_param_list);

  -- get Response and obtain received value
  l_http_response := UTL_HTTP.get_response(l_http_request);

  UTL_HTTP.read_text(l_http_response, l_response_text);

  DBMS_OUTPUT.put_line(l_response_text);
 l_list := json_list(l_response_text);

FOR i IN 1..l_list.count
LOOP
A_id        := json_ext.get_string(json(l_list.get(i)),'_id');
Photo       := json_ext.get_string(json(l_list.get(i)),'Photo');
A_Name      := json_ext.get_string(json(l_list.get(i)),'Name');
Remarks     := json_ext.get_string(json(l_list.get(i)),'Remarks');
Status      := json_ext.get_string(json(l_list.get(i)),'Status');
UserId      := json_ext.get_string(json(l_list.get(i)),'UserId');
A_Date      := json_ext.get_string(json(l_list.get(i)),'Date');
A_Time      := json_ext.get_string(json(l_list.get(i)),'Time');
MSG_status  := json_ext.get_string(json(l_list.get(i)),'MSG_status');
Oracle_Flag := json_ext.get_string(json(l_list.get(i)),'Oracle_Flag');
acl         := json_ext.get_string(json(l_list.get(i)),'acl');


insert into Appery_Photos values(
  A_id,
  Photo,
  A_Name,
  Remarks,
  Status,
  UserId,
  A_Date,
  A_Time,
  MSG_status ,
  Oracle_Flag,
  acl
);
  end loop;

-- finalizing
  UTL_HTTP.end_response(l_http_response);

EXCEPTION 
  WHEN UTL_HTTP.end_of_body 
    THEN UTL_HTTP.end_response(l_http_response);  
END;
/

该脚本适用于小型 JSON 文件。但是,当文件包含 base64 文件(以 base64 格式表示的照片)时,脚本失败并给出错误(未找到字符串结尾)。

显然,错误是由于没有复制整个文件,因此 JSON 解析器无法找到字符串“]”或“”的结尾。

我尝试使用最大大小为 32767 的 CLOB 和 VARCHAR2,但这还不够。

我想解码 base64 文件,但问题是我需要先读取文件才能解码该字段。

任何建议将不胜感激。

结果

两个答案都提供了读取大型 JSON 文件 (>32KB) 的解决方案,我使用了 @Jeffrey Kemp 之一。然而,作为下一个问题,其中一个 json_values/fields 本身 >32KB,json_ext.get_string 只返回一个 VARCHAR2,这意味着它被限制为最大 32767 个字节。因此,对于字段 Photo,其值为 >32KB,我使用了 json_ext.get_json_valuedbms_lob.createtemporary。稍微整理一下得到的相关脚本如下:

DECLARE
  l_val            json_value;
  l_param_list     VARCHAR2(512);
  l_http_request   UTL_HTTP.req;
  l_http_response  UTL_HTTP.resp;
  l_response_text  CLOB;
  --l_response_text  VARCHAR2(32767);

l_list json_list;
A_id           VARCHAR2(100);
Photo          VARCHAR2(32767);
A_Name         VARCHAR2(100);
Remarks        VARCHAR2(100);
Status         VARCHAR2(100);
UserId         VARCHAR2(100);
A_Date         VARCHAR2(100);
A_Time         VARCHAR2(100);
MSG_status     VARCHAR2(100);
Oracle_Flag    VARCHAR2(100);
acl            VARCHAR2(100);


BEGIN

  -- service's input parameters

  -- preparing Request...
  l_http_request := UTL_HTTP.begin_request('https://api.appery.io/rest/1/db/collections/Photos?where=%7B%22Oracle_Flag%22%3A%22Y%22%7D'
                                          , 'GET'
                                          , 'HTTP/1.1');
 
  -- ...set header's attributes
  UTL_HTTP.set_header(l_http_request, 'X-Appery-Database-Id', '53f2dac5e4b02cca64021dbe');
  --UTL_HTTP.set_header(l_http_request, 'Content-Length', LENGTH(l_param_list));

  -- ...set input parameters
 -- UTL_HTTP.write_text(l_http_request, l_param_list);

  -- get Response and obtain received value
  l_http_response := UTL_HTTP.get_response(l_http_request);
  BEGIN
    LOOP
      UTL_HTTP.read_text(l_http_response, buf);
      l_response_text := l_response_text || buf;
    END LOOP;
  EXCEPTION
  WHEN UTL_HTTP.end_of_body THEN
    NULL;
  END;
  l_list := json_list(l_response_text);


FOR i IN 1..l_list.count
LOOP
A_id        := json_ext.get_string(json(l_list.get(i)),'_id');
l_val := json_ext.get_json_value(json(l_list.get(i)),'Photo');
dbms_lob.createtemporary(Photo, true, 2);
json_value.get_string(l_val, Photo);
A_Name      := json_ext.get_string(json(l_list.get(i)),'Name');
Remarks     := json_ext.get_string(json(l_list.get(i)),'Remarks');
Status      := json_ext.get_string(json(l_list.get(i)),'Status');
UserId      := json_ext.get_string(json(l_list.get(i)),'UserId');
A_Date      := json_ext.get_string(json(l_list.get(i)),'Date');
A_Time      := json_ext.get_string(json(l_list.get(i)),'Time');
MSG_status  := json_ext.get_string(json(l_list.get(i)),'MSG_status');
Oracle_Flag := json_ext.get_string(json(l_list.get(i)),'Oracle_Flag');
acl         := json_ext.get_string(json(l_list.get(i)),'acl');


insert into Appery_Photos values(
  A_id,
  Photo,
  A_Name,
  Remarks,
  Status,
  UserId,
  A_Date,
  A_Time,
  MSG_status ,
  Oracle_Flag,
  acl
);
  end loop;

-- finalizing
  UTL_HTTP.end_response(l_http_response);

EXCEPTION 
  WHEN UTL_HTTP.end_of_body 
    THEN UTL_HTTP.end_response(l_http_response);  
END;
/

【问题讨论】:

【参考方案1】:

CLOB have a size limit of 4G

但这里的限制是UTL_HTTP.read_text 将结果作为VARCHAR2 返回(您在此处有一个隐式 转换)。

要轻松地从网络检索大文本对象,您可能需要HttpUriType.getClob


如果出于某种原因你想坚持使用UTL_HTTP,你将不得不循环读取你的数据块。类似的东西:

BEGIN
  ...
  l_clob           CLOB;
  l_text           VARCHAR2(32767);
BEGIN
  DBMS_LOB.createtemporary(l_clob, FALSE);

  ...
  l_http_request  := UTL_HTTP.begin_request(your_URI);
  l_http_response := UTL_HTTP.get_response(l_http_request);

  -- Loop to read data chunk by chunk up to the end
  BEGIN
    LOOP
      UTL_HTTP.read_text(l_http_response, l_text, 32766);
      DBMS_LOB.writeappend (l_clob, LENGTH(l_text), l_text);
    END LOOP;
  EXCEPTION
    WHEN UTL_HTTP.end_of_body THEN
      UTL_HTTP.end_response(l_http_response);
  END;

查看http://oracle-base.com/articles/misc/retrieving-html-and-binaries-into-tables-over-http.php vor 各种示例

【讨论】:

【参考方案2】:

您的问题在于您致电UTL_HTTP.read_text。您正在传递一个 CLOB,但 read_text 只接受 VARCHAR2,因此它最多可以返回 32k 字节。

您需要在循环中调用它,使用 VARCHAR2 缓冲区,并将结果连接到您的 CLOB,例如:

DECLARE
  buf VARCHAR2(32767);
BEGIN
  LOOP
    UTL_HTTP.read_text(l_http_response, buf);
    l_response_text := l_response_text || buf;
  END LOOP;
EXCEPTION
  WHEN UTL_HTTP.end_of_body THEN
    NULL;
END;

http://docs.oracle.com/cd/E11882_01/appdev.112/e40758/u_http.htm#ARPLS71074

您的第二个问题是json_ext.get_string 只返回一个 VARCHAR2,这意味着它最多限制为 32767 个字节。我浏览了PL/json wiki,您可能需要联系其中一位作者,了解如何使用它来获取 CLOB 值。

【讨论】:

这工作正常,除了“照片”字段。 “照片”字段是 base64 格式的图像 >32KB。当前脚本将所有字段正确存储到数据库中的列中。但是,“照片”值并不完整。我认为问题源于 Photo := json_ext.get_string(json(l_list.get(i)),'Photo'); 行。但是不知道怎么解决。 已更新 - json_ext.get_string 似乎只支持 VARCHAR2。 我在大于 32KB 的字段中添加了以下内容:l_val := json_ext.get_json_value(json(l_list.get(i)),'Photo'); dbms_lob.createtemporary(照片,真,2); json_value.get_string(l_val, Photo); 并且有效。

以上是关于将大型 JSON 文件存储到 Oracle 数据库中的主要内容,如果未能解决你的问题,请参考以下文章

将大型 Twitter JSON 数据 (7GB+) 加载到 Python 中

如何将大型 JSON 数据从服务器存储到 SQLITE Android?

将大型 JSON 文件数据加载到 Angular cli 表中

解析大型 JSON 文件 [重复]

iOS 下载和解析大型 JSON 响应导致 CFData(存储)泄漏

在 Nodejs 中解析大型 JSON 文件