可以实现 SQL 移植的技术

不同类型数据库的函数语法或多或少有些差异,要想让功能相同的SQL查询语句在不同类型的数据库中都能被顺利执行,就要把各数据库都有的那些差异化函数语法进行翻译,即SQL移植。本文将探讨几种SQL移植的技术并加以对比。

 

ORM技术

使用ORM技术能够将程序员书写的查询转换成不同数据库的SQL,相当于有一定的移植能力。但 ORM 技术只适合应对 OLTP 场景下的简单 SQL,难以实现 OLAP 场景下较复杂 SQL 的移植。

 

HibernateMybatis是两种最常见的ORM技术。Mybatis被称为半自动ORM,最主要的一个原因是需要程序员自己编写原生态SQL,也就几乎没有可移植性,即使借助Mybatis-plusMybatis-ext这样的第三方增强扩展包来改善输入方式,其可移植性也远比不上HibernateHibernate则可以使用HQL根据配置文件中的数据库方言自动生成对应的SQL,对于一些简单的情况(Hibernate中直接支持),具有一定的可移植性,例如:

 

HQL

SELECT

            client,

            YEAR(orderDate),

            sum(amount),

            count(orderId)

FROM

            OrdersEntity

GROUP BY

            client,

            YEAR(orderDate)

HAVING

            sum(amount)>2000

 

MySQL

SELECT

            ordersenti0_.Client AS col_0_0_,

            YEAR(ordersenti0_.OrderDate) AS col_1_0_,

            sum(ordersenti0_.Amount) AS col_2_0_,

            count(ordersenti0_.OrderID) AS col_3_0_

FROM

            orders ordersenti0_

GROUP BY

            ordersenti0_.Client,

            YEAR(ordersenti0_.OrderDate)

HAVING

            sum(ordersenti0_.Amount)>2000

 

Oracle

SELECT

            ordersenti0_.Client AS col_0_0_,

            EXTRACT(YEAR FROM ordersenti0_.OrderDate) AS col_1_0_,

            sum(ordersenti0_.Amount) AS col_2_0_,

            count(ordersenti0_.OrderID) AS col_3_0_

FROM

            system.orders ordersenti0_

GROUP BY

            ordersenti0_.Client,

            EXTRACT(YEAR FROM ordersenti0_.OrderDate)

HAVING

            sum(ordersenti0_.Amount)>2000

 

HQL生成的SQL中,对应MySQL时用了YEAR(d)函数,对应Oracle时用了EXTRACT(YEAR FROM d)函数。可以实现SQL的移植效果。

 

但是更复杂一些的情况,Hibernate也会失去可移植性。

 

例如使用HQL本身不直接支持的函数时,需要先根据当前使用的数据库注册自定义函数后再用HQL生成SQL,以MySQL为例:

 

注册自定义函数关键代码:

registerFunction("udf_dateadd", new SQLFunctionTemplate(DateType.INSTANCE,"date_add(?1,INTERVAL ?2 DAY)"));

 

HQL

SELECT

            udf_dateadd (orderDate,3)

FROM

            OrdersEntity

 

MySQL

SELECT

            date_add(ordersenti0_.OrderDate,INTERVAL 3 DAYAS col_0_0_

FROM

            orders ordersenti0_

 

Oracle没有date_add函数,如果数据库变为Oracle,又需要再根据Oracle的函数语法,另外再注册新的自定义函数来使用,这就无法自动移植了。

 

还有些HQL无法描述的运算,例如不支持FROM中的子查询:

SELECT

            orderId,

            m

FROM

            (

            SELECT

                        orderId,

                        MONTH(orderDate) m

            FROM

                        OrdersEntity) t1

 

这种情况通常会使用SQLQuery接口来解决,但这时执行的是原生SQL了,也失去移植性。

SQL转换工具

借助某些工具移植SQL,这种办法的优点是,可以直接将现有的源SQL(如Oracle SQL)翻译成目标SQL(如MySQL SQL),即使复杂情况下,如嵌套子查询,也可以完成转换。

 

例如在https://www.sqlines.com/online中:

 

SQL的数据库类型选择MySQL,需要被翻译的SQL为:

SELECT

            O_YEAR ,

            SUM(CASE WHEN NATION = 'CHINA' THEN VOLUME ELSE 0 END) / SUM(VOLUME) ASMKT_SHARE

FROM

            (

            SELECT

                        YEAR (O_ORDERDATE) AS O_YEAR,

                        L_EXTENDEDPRICE * (1 - L_DISCOUNT) AS VOLUME,

                        N2.N_NAME AS NATION

            FROM

                        PART,

                        SUPPLIER,

                        …

 

目标SQL的数据库类型选择Oracle,点击转换按钮,即可将源SQL转为目标数据库可以执行的SQL

SELECT

            O_YEAR ,

            SUM(CASE WHEN NATION = 'CHINA' THEN VOLUME ELSE 0 END) / SUM(VOLUME) ASMKT_SHARE

FROM

            (

            SELECT

                        EXTRACT(YEAR FROM O_ORDERDATE) AS O_YEAR,

                        L_EXTENDEDPRICE * (1 - L_DISCOUNT) AS VOLUME,

                        N2.N_NAME AS NATION

            FROM

                        PART,

                        SUPPLIER,

                        …

 

这种办法的缺点是,这类工具多为独立的小程序,仅支持在命令行或结果文件中输出目标SQL,缺少程序接口以便被各类开发工具集成。

集算器 SPL

SPL设计了一套标准的SQL查询语法,该语法内置大量函数(还在持续追加中),可描述更多常用运算。SPL中有一个sqltranslate函数,可以把这种标准SQL翻译成不同数据库的SQL,实现数据库的迁移。

 

比如这样的标准SQL

SELECT CLIENT, YEAR(ORDERDATE), SUM(AMOUNT)

            , COUNT(ORDERID)

FROM ORDERS

GROUP BY CLIENT, YEAR(ORDERDATE)

HAVING SUM(AMOUNT) > 2000

.sqltranlate("MYSQL")翻译后就得到:

SELECT CLIENT, YEAR(ORDERDATE), SUM(AMOUNT)

            , COUNT(ORDERID)

FROM ORDERS

GROUP BY CLIENT, YEAR(ORDERDATE)

HAVING SUM(AMOUNT) > 2000

 

而如果使用.sqltranslate("ORACLE")将返回:

SELECT CLIENT, EXTRACT(YEAR FROM ORDERDATE), SUM(AMOUNT)

            , COUNT(ORDERID)

FROM ORDERS

GROUP BY CLIENT, EXTRACT(YEAR FROM ORDERDATE)

HAVING SUM(AMOUNT) > 2000

可以看到,标准函数能够正确地根据数据库选择相应的函数。

 

此外,对于上文中提及的HQL本身不支持的:INTERVAL n DAYSPL也可以处理:

 

标准SQL

SELECT

            ADDDAYS(ORDERDATE,3)

FROM

            ORDERS

 

翻译成MySQL SQL

SELECT

            ORDERDATE + INTERVAL 3 DAY

FROM

            ORDERS

 

翻译成Oracle SQL

SELECT

            ORDERDATE + NUMTODSINTERVAL(3,'DAY')

FROM

            ORDERS

 

SPL实现SQL移植,采取的策略是只对标准SQL中的函数进行翻译,不翻译(原样照抄)语句,从而使标准SQL可描述更多的运算。比如,对于下面的子查询无论翻译成哪种数据库SQL都不会变,也都可以正常执行。

 

SELECT

            ORDERID,

            M

FROM

            (

            SELECT

                        ORDERID,

                        MONTH(ORDERDATE) M

            FROM

                        ORDERS) T1

 

SPL很容易被JAVA 集成而使这个移植功能在应用程序中使用,进一步信息可参考:《Java 如何调用 SPL 脚本》。