由简单的mod运算符在几种数据库中的支持引发的思考


背景

最近在一个比较复杂的Cloud环境中,需要同时使用动态的HANA与PostgreSQL数据源,并且支持MultiTenant。这就不可避免的遇到SQL语法方面的通常与专用的一些小困惑。JPA/ObjectQuery功能再强大,有时候也只是尽量的使用Dialect进行翻译。

下边就是遇到的一个典型的错误:

select t1.Id, t1.InstanceId, t1.ApplicationId, t1.Component, t1.CorrelationId, t1.FileId, t1.FileName, t1.LineNumber, t1.Location, t1.MessageCode, t1.MessageText, t1.RootContextId, t1.TransactionId, t1.Severity, t1.SourceName, t1.UniqueId, t1.Username, t1.TrialPlan, t1.TimeHours, t1.Timestamp, t1.Created
from ClientLogs t1
where (((t1.Severity%?)=?) and (t1.InstanceId=?)) limit 1000 offset 0;

com.sap.xscript.data.SQLDatabaseException: prepareStatement: select t1.\"Id\", t1.\"InstanceId\", t1.\"ApplicationId\", t1.\"Component\", t1.\"CorrelationId\", t1.\"FileId\", t1.\"FileName\", t1.\"LineNumber\", t1.\"Location\", t1.\"MessageCode\", t1.\"MessageText\", t1.\"RootContextId\", t1.\"TransactionId\", t1.\"Severity\", t1.\"SourceName\", t1.\"UniqueId\", t1.\"Username\", t1.\"TrialPlan\", t1.\"TimeHours\", t1.\"Timestamp\", t1.\"Created\"\nfrom \"ClientLogs\" t1\nwhere (((t1.\"Severity\"%?)=?) and (t1.\"InstanceId\"=?)) limit 1000 offset 0; Caused by: com.sap.xscript.data.RuntimeSQLException; Caused by: com.sap.db.jdbc.exceptions.JDBCDriverException: SAP DBTech JDBC: [257]: sql syntax error: incorrect syntax near \"%\": line 3 col 23 (at pos 392)"

分析

上边出现的问题,其实是比较容易判断和重现的。因为从驱动看:com.sap.db.jdbc.exceptions.JDBCDriverException 明显来自HANA数据库,然后也告诉我们(t1.Severity%?)=?)这个附近的语法出现错误。

1、我们在HANA数据库上直接测试下边的SQL:

SELECT 5 % 2 FROM dummy;
-- 
SQL Error [257] [HY000]: SAP DBTech JDBC: [257] (at 10): sql syntax error: incorrect syntax near "%": line 1 col 10 (at pos 10)

SELECT 5 MOD 2 FROM dummy;
--
SQL Error [257] [HY000]: SAP DBTech JDBC: [257] (at 14): sql syntax error: incorrect syntax near "2": line 1 col 14 (at pos 14)

SELECT MOD(52FROM dummy;
--
|MOD(5,2)|
|--------|
|1       |

mod表达式有三种形式:

  • a % b  最常见

  • a mod b 也比较多

  • mod (a, b)  最不常见

看来这三种形式,HANA选择了大家最不常用的那一种表达式mod(a, b)进行支持。也难怪上边报错了。

2、再看看PostgreSQL中的情况:

mydb=# select 5 % 2 as mod_a, mod(52) as mod_c;
 mod_a | mod_c
-------+-------
     1 |     1
(1 row)

mydb=# select 5 mod 2 as mod_b;
ERROR:  syntax error at or near "2"
LINE 1select 5 mod 2 as mod_b;
                     ^

它对第2种表达式表示不太支持:a mod b

3、试试MySQL中的支持

select 5 % 2 as moda, 5 mod 2 as modb,  mod(52as modc;
1    1   1

MySQL对这三种表达式全都是支持的。最宽松语法支持,看来不假。

4、看看新秀DuckDB中的情况

select 5 % 2 as moda, mod(52as modc;
┌───────┬───────┐
│ moda  │ modc  │
│ int32 │ int32 │
├───────┼───────┤
│     1 │     1 │
└───────┴───────┘
select 5 mod 2 as modb;
Error: Parser Error: syntax error at or near "2"
LINE 1select 5 mod 2 as modb;
                     ^

与PostgreSQL类似,它对第2种表达式表示不太支持:a mod b。

5、看看SQLite:

SQLite version 3.38.5 2022-05-06 15:25:27
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> select 5 % 2 as moda;
1
sqlite> select mod(52as modc;
Parse error: no such function: mod
  select mod(52as modc;
         ^--- error here
sqlite> select 5 mod 2 as modb;
Parse error: near "2": syntax error
  select 5 mod 2 as modb;
               ^--- error here
sqlite>

它只支持第1种表达式形式:a % b

6、Sybase ASE中的支持:

1select 5 % 2 as moda
2> go
 moda
 -----------
           1

(1 row affected)
1>  select 5 % 2 as moda, 5 mod 2 as modb
2> go
Msg 102, Level 15, State 181:
Server 'SEANRH3', Line 1:
Incorrect syntax near '2'.
1select mod(5,2as modc
2> go
Msg 14216, Level 16, State 1:
Server 'SEANRH3', Line 1:
Function 'mod' not found. If this is a SQLJ function or SQL function, use
sp_help to check whether the object exists (sp_help may produce a large amount
of output).

它支持的也是第一种语法 a % b

7、Oracle中的支持

select 5 % 2 as moda from dual;
ORA-00911: invalid character

select 5 mod 2 as modb from dual;
ORA-00923: FROM keyword not found where expected

select  mod(52as modc from dual;
1

Oracle跟HANA一样,只支持第3种语法 : mod (a, b)

8、DB2中的支持

select 5 % 2 as moda from sysibm.sysdummy1;
1
select  mod(52as modc from sysibm.sysdummy1;
1
select 5 mod 2 as modb from sysibm.sysdummy1;
[IBM][CLI Driver][DB2/LINUXX8664] SQL0104N  An unexpected token "2" was found following "select mod ".  Expected tokens may include:  "<space>".  SQLSTATE=42601 SQLCODE=-104

https://dbfiddle.uk/AsWQirD7)

DB2支持第1种和第3种语法 ,对于第2种语法则不支持。

至此结论基本已出。

HANA, Oracle在这块似乎有些执拗了。当然如果我们去查询SQL标准:https://ronsavage.github.io/SQL/sql-2003-2.bnf.html#modulus%20expression  还是在2003里头出现的。

<modulus expression>    ::=   MOD <left paren> <numeric value expression dividend> <comma> <numeric value expression divisor> <right paren>

mod (a, b) 似乎是正儿八经的按标准来做的,只是用户不怎么买帐。因为a % b 或者 a mod b更像数据表达式。

从ObjectQuery的角度出发,它似乎用的是第3种形式:mod(a, b),然后各个数据库再利方言之类的进行映射。

详见:https://www.objectdb.com/java/jpa/query/jpql/arithmetic#the_mod_function

进一步思考

这种语法上的偏差,有数据库移植带来很多困扰。数据的移植,基本上只要用心去做,也都能做到,但是SQL存储过程、函数以及特殊语法上的转换,就需要倍加小心。尤其是一些偏门函数。一个小小的mod操作符尚且如此,我们还能对migration掉以轻心吗?

SQL online运行试验环境

关于各种数据库的SQL运行环境,我们可以直接利用网上的online控制台,没有必要每个数据库都搭建一遍。

推荐常用的有:

[1]  https://dbfiddle.uk/:   https://dbfiddle.uk/  (支持非常多的DBMS)

[2] https://www.db-fiddle.com/: https://www.db-fiddle.com/

[3] 国产的恩墨墨天轮:  https://www.modb.pro/sqlrun/

参考:

[1] https://ronsavage.github.io/SQL/: https://ronsavage.github.io/SQL/

[2] mod function:  https://www.objectdb.com/java/jpa/query/jpql/arithmetic#the_mod_function

[3]  https://dbfiddle.uk/:   https://dbfiddle.uk/  (支持非常多的DBMS)

[4] https://www.db-fiddle.com/: https://www.db-fiddle.com/

[5] 国产的恩墨墨天轮:  https://www.modb.pro/sqlrun/



请使用浏览器的分享功能分享到微信等