MySql 5.7对json_table()函数的一次变通替代

摘要:
一天,一个变态的客户说他将转向云平台,但云平台没有Oracle,只有MySql和其他开源产品。在将数据库构建脚本从oracle转换为mysql的过程中,最令人烦恼的问题是如何实现表()函数的json_替换。Oracle版本脚本在许多地方使用json_value()函数在一些地方使用json_table()当转换为MySql时,json_Value()可以被json_Extract()替换,但json_Table()…众所周知,MySql函数不能返回表类型的变量,因此没有相应的函数,也没有相应的想法。

一、前言

目前项目使用的数据库是Oracle 12c,选择该版本的的主要原因之一是支持json。某日,某变态客户说要转向云平台,而云平台却没有Oracle只有MySql及其它开源产品。在把建库脚本(包括表、视图、索引、触发器、存储过程等等)由oracle版转为mysql版的过程中,最烦人的一个难关是如何实现json_table()函数的替代。

Oracle版脚本在多处使用了json_value()函数,少数几处使用了json_table()。转为MySql版时,json_value()可以替换为json_extract(),但json_table()……众所周知,MySql的函数不能返回表类型的变量,因此根本没有对应函数,也没提供对应的思路。曾经通过谷歌搜索到一段很复杂的示例sql代码,可当时的确看不明白,也就谈不上改造,而现在连谷歌也上不去,只能另寻出路。

二、Oracle脚本

去除不必要的内容,与json_table()相关的Oracle版脚本如下:

-- Table
create table PERSON_INFO 
(
   ID          NUMBER(15)           not null ,
   ADDR_INFO   VARCHAR2(1000) CONSTRAINT ADDR_INFO_JSON CHECK (ADDR_INFO IS JSON),
   PRIMARY KEY ( ID )
);

-- View
CREATE OR REPLACE VIEW PERSON_ADDR_VIEW
AS
 SELECT
   PI.ID ID,
   TEMP_TAB.ADDRESS_CODE,
   TEMP_TAB.ADDRESS_DETAIL,
   TEMP_TAB.ADDRESS_TYPE,
   TEMP_TAB.ADDRESS_ZIP_CODE
 FROM PERSON_INFO PI, 
  JSON_TABLE(PI.ADDR_INFO, '$[*]' COLUMNS (ADDRESS_CODE VARCHAR2 PATH '$.AddressCode', ADDRESS_DETAIL VARCHAR2 PATH '$.AddressDetail',
             ADDRESS_TYPE VARCHAR2 PATH '$.AddressType', ADDRESS_ZIP_CODE VARCHAR2 PATH '$.AddressZipCode')) TEMP_TAB;

-- Function
CREATE OR REPLACE 
FUNCTION GET_ADDR_NAME_BY_TYPE (P_ADDR_INFO IN VARCHAR2, P_ADDR_TYPE IN NUMBER)
RETURN VARCHAR2 AS 
RET_ADDR_NAME VARCHAR2(200);
BEGIN
  IF P_ADDR_INFO IS NULL THEN RETURN '0' ;END IF;
  SELECT T.ADDRESS_NAME INTO RET_ADDR_NAME 
FROM JSON_TABLE(P_ADDR_INFO, '$[*]' COLUMNS (ADDRESS_NAME VARCHAR2 PATH '$.AddressDetail', ADDRESS_TYPE VARCHAR2 PATH '$.AddressType')) T
WHERE T.ADDRESS_TYPE = P_ADDR_TYPE; RETURN RET_ADDR_NAME; END HS_GET_ADDR_NAME_BY_TYPE; /

json字段的一个示例:

[{"AddressType":1, "AdrressCode":"Code 1", "AdreessDetail":"aaaa", "AddressZipCode":"100010"},
 {"AddressType":2, "AdrressCode":"Code 2", "AdreessDetail":"bbbb", "AddressZipCode":"200020"},
 {"AddressType":5, "AdrressCode":"Code 1", "AdreessDetail":"xxxx", "AddressZipCode":"500050"}
]

三、MySql脚本

最简单的是改造表,直接将字段类型改为JSON即可:

-- Table
create table PERSON_INFO 
(
   ID          DECIMAL(15)           not null ,
   ADDR_INFO   JSON,
   PRIMARY KEY ( ID )
);

难度较大的是改造函数(存储过程类似,限制更少),经一系列尝试后,用循环取值+比较的方法替代方法成功:

-- Function
DELIMITER /

DROP FUNCTION IF EXISTS GET_ADDR_NAME_BY_TYPE/

CREATE FUNCTION GET_ADDR_NAME_BY_TYPE (P_ADDR_INFO VARCHAR(1000), P_ADDR_TYPE DECIMAL)
RETURNS VARCHAR(200)
BEGIN
   DECLARE RET_ADDR_NAME VARCHAR(200);
   DECLARE RET_ADDR_TYPE int;
   DECLARE n int;
   DECLARE i int;
   IF P_ADDR_INFO IS NULL THEN RETURN '0' ;END IF;
   -- SELECT T.ADDRESS_NAME INTO RET_ADDR_NAME FROM JSON_TABLE(P_ADDR_INFO, '$[*]' COLUMNS (ADDRESS_NAME VARCHAR2 PATH '$.AddressDetail', ADDRESS_TYPE VARCHAR2 PATH '$.AddressType')) T  WHERE T.ADDRESS_TYPE = P_ADDR_TYPE;
   SELECT json_length(P_ADDR_INFO) into n;
   set i = 0;
   WHILE i<n DO
      SELECT json_extract(P_ADDR_INFO, concat('$[', i, '].AddressDetail')), 
             json_extract(P_ADDR_INFO, concat('$[', i, '].AddressType')) 
      INTO RET_ADDR_NAME, RET_ADDR_TYPE FROM DUAL;
      
      IF RET_ADDR_TYPE=P_ADDR_TYPE THEN return RET_ADDR_NAME; END if;
      set i = i+1;
   END WHILE;
   RETURN '0';
END;
/

DELIMITER ;

效率低一些,但在多数场合也都适用。

最难缠的是改造视图,MySql不支持表函数,VIEW定义里又不能有附加操作(比如转存到临时表),一开始真实一筹莫展……

后来了解到每个json里的AddressType的取值范围只有六个数,且在内部唯一,终于找到了替代办法:

-- View
CREATE OR REPLACE VIEW person_addr_view
AS
SELECT ID, ADDRESS_TYPE, ADDRESS_CODE, ADDRESS_DETAIL, ADDRESS_ZIP_CODE
FROM 
   (
   SELECT
      PI.ID ID, 
      json_extract(PI.ADDR_INFO, '$[0].AddressType') ADDRESS_TYPE, 
      json_extract(PI.ADDR_INFO, '$[0].AddressCode') ADDRESS_CODE, 
      json_extract(PI.ADDR_INFO, '$[0].AddressDetail') ADDRESS_DETAIL,
      json_extract(PI.ADDR_INFO, '$[0].AddressZipType') ADDRESS_ZIP_CODE
   FROM MIS_PERSON_TEXT_INFO PI
   UNION ALL
   SELECT
      PI.ID ID, 
      json_extract(PI.ADDR_INFO, '$[1].AddressType') ADDRESS_TYPE, 
      json_extract(PI.ADDR_INFO, '$[1].AddressCode') ADDRESS_CODE, 
      json_extract(PI.ADDR_INFO, '$[1].AddressDetail') ADDRESS_DETAIL,
      json_extract(PI.ADDR_INFO, '$[1].AddressZipType') ADDRESS_ZIP_CODE
   FROM MIS_PERSON_TEXT_INFO PI
   UNION ALL
   SELECT
      PI.ID ID, 
      json_extract(PI.ADDR_INFO, '$[2].AddressType') ADDRESS_TYPE, 
      json_extract(PI.ADDR_INFO, '$[2].AddressCode') ADDRESS_CODE, 
      json_extract(PI.ADDR_INFO, '$[2].AddressDetail') ADDRESS_DETAIL,
      json_extract(PI.ADDR_INFO, '$[2].AddressZipType') ADDRESS_ZIP_CODE
   FROM MIS_PERSON_TEXT_INFO PI
   UNION ALL
   SELECT
      PI.ID ID, 
      json_extract(PI.ADDR_INFO, '$[3].AddressType') ADDRESS_TYPE, 
      json_extract(PI.ADDR_INFO, '$[3].AddressCode') ADDRESS_CODE, 
      json_extract(PI.ADDR_INFO, '$[3].AddressDetail') ADDRESS_DETAIL,
      json_extract(PI.ADDR_INFO, '$[3].AddressZipType') ADDRESS_ZIP_CODE
   FROM MIS_PERSON_TEXT_INFO PI
   UNION ALL
   SELECT
      PI.ID ID, 
      json_extract(PI.ADDR_INFO, '$[4].AddressType') ADDRESS_TYPE, 
      json_extract(PI.ADDR_INFO, '$[4].AddressCode') ADDRESS_CODE, 
      json_extract(PI.ADDR_INFO, '$[4].AddressDetail') ADDRESS_DETAIL,
      json_extract(PI.ADDR_INFO, '$[4].AddressZipType') ADDRESS_ZIP_CODE
   FROM MIS_PERSON_TEXT_INFO PI
   UNION ALL
   SELECT
      PI.ID ID, 
      json_extract(PI.ADDR_INFO, '$[5].AddressType') ADDRESS_TYPE, 
      json_extract(PI.ADDR_INFO, '$[5].AddressCode') ADDRESS_CODE, 
      json_extract(PI.ADDR_INFO, '$[5].AddressDetail') ADDRESS_DETAIL,
      json_extract(PI.ADDR_INFO, '$[5].AddressZipType') ADDRESS_ZIP_CODE
   FROM PERSON_INFO PI
   ) union_tab
   WHERE ADDRESS_TYPE IS NOT NULL; 

最后的WHERE条件是防止出现全NULL行,对于'$[n].'里的n,如果大于等于json_length()的值,json_extract()返回NULL。

这性能低得连自己都觉得不好意思,至于适用范围更是有限,换个场景就很难说能适用。但毕竟项目可以使用,不必对前台代码伤筋动骨(一般修改仍不可避免)。

四、备注

  • 以上代码都已通过实测,由于测试环境数据量小,性能数据误差大,这里不给出;
  • MySql 从5.7开始支持json,Oracle 从12c开始支持json;
  • MySql 8(直接跳过6和7)将支持json_table(),以及其它表函数。

免责声明:文章转载自《MySql 5.7对json_table()函数的一次变通替代》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇QT出现没有MySQL驱动,手动编译步骤神经网络(7)---多分类问题下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

QNAP container station安装 docker-mysql

打开container station,即docker,安装MySQL 选择最新的即可 点击创建后,就慢慢等待下载完成即可~ ,下载完成后会自动启动 尝试登陆mysql 密码在启动日志里,所以我们接下去找一下 执行如下命令mysql -u root -p密码复制过来,进入MySQL 到此就安装成功了,之后可进行修改密码和允许远程访问操作...

log4j2

转载自 Blog of 天外的星星: http://www.cnblogs.com/leo-lsw/p/log4j2tutorial.html Log4j 2的好处就不和大家说了,如果你搜了2,说明你对他已经有一定的了解,并且想用它,所以这里直接就上手了。   1. 去官方下载log4j 2,导入jar包,基本上你只需要导入下面两个jar包就可以了(xx是...

Mysql视图

一、视图介绍1.1视图定义:是一个虚拟表,本身不包含数据,行和列的数据来自于自由定义视图查询所引用的列,在引用视图的时候动态生成。和普通的表格使用方式一致。1.2视图的优点:①简化操作:将复杂的sql关联查询和筛选条件进行视图封装,使用较为方便。②安全性:视图可以定制可以查询到的数据(列值),提高了数据的安全性,用于权限控制。③调高了数据查询效率。④实现了...

第一次使用Ubuntu20.04系统-遇坑小记

第一次使用Ubuntu20.04系统-遇坑小记 最近发现Linux系统是程序员前进的必经之路(立志做一名“倔强又装逼的程序员”),所以果断在自己的笔记本上安装了最新的Ubuntu20.04系统,刚开始使用确实不是很习惯(应该还是自己比较菜),尤其是许多命令行的操作方式不是很熟悉、操作系统的一些相关知识也不是很了解,不过用了几天,发现Linux(Ubuntu...

Mysql --数据的增删改

插入数据 INSERT 更新数据 UPDATE 删除数据 DELETE 一、 在mysql管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 1.使用insert实现数据的插入 2.update实现数据的更新 3.使用delete实现数据的删除 4.使用select查询数据以及。 二、插入数据 insert 1. 插入完整数据(顺...

如何在idea中使用Mybatis-generator插件快速生成代码

使用这个插件可以快速生成一些代码,包含 实体类/Mapper接口/*Mapper.xml文件 首先,我们需要搭建一个Maven的项目。 在pom.xml中添加代码 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0...