背景
最近在一个比较复杂的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(5, 2) FROM 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(5, 2) 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 1: select 5 mod 2 as mod_b;
^
它对第2种表达式表示不太支持:a mod b
3、试试MySQL中的支持
select 5 % 2 as moda, 5 mod 2 as modb, mod(5, 2) as modc;
1 1 1
MySQL对这三种表达式全都是支持的。最宽松语法支持,看来不假。
4、看看新秀DuckDB中的情况
D select 5 % 2 as moda, mod(5, 2) as modc;
┌───────┬───────┐
│ moda │ modc │
│ int32 │ int32 │
├───────┼───────┤
│ 1 │ 1 │
└───────┴───────┘
D select 5 mod 2 as modb;
Error: Parser Error: syntax error at or near "2"
LINE 1: select 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(5, 2) as modc;
Parse error: no such function: mod
select mod(5, 2) as 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中的支持:
1> select 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'.
1> select mod(5,2) as 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(5, 2) as modc from dual;
1
Oracle跟HANA一样,只支持第3种语法 : mod (a, b)
8、DB2中的支持
select 5 % 2 as moda from sysibm.sysdummy1;
1
select mod(5, 2) as 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/