假设存在这么一张表,记录全国各地大学的名称,以及所处的行政区划

CREATE TABLE 大学信息表
(
id varchar DEFAULT uuid_generate_v4() PRIMARY KEY,
v_name varchar,
v_所处行政区划代码 varchar
);

其中,行政区划是国家标准的,也就是你身份证开头的6位,能够精确到区、县的。两位一个级别,前六位,可以笼统的概括成,省及、市级、县级,当然还有直辖市、特别行政区什么的,先不必深究,暂且简化这个逻辑就好了。
现在问题来了,如果想统计各省份大学的数量,是不是该用这条SQL

select left(v_所处行政区划代码, 2), count(*)
from 大学信息表
group by left(v_所处行政区划代码, 2);

对需求敏感的小伙伴肯定看出来了,上面的2在设计接口的时候应该设计成参数,万一要统计各城市的大学数量,直接传4不就好了。
所以在Java中,我们大约会写这么一段代码

PreparedStatement pstmt = conn.prepareStatement("""
select left(v_所处行政区划代码, ?), count(*) as num
from 大学信息表
group by left(v_所处行政区划代码, ?);
""");

pstmt.setInt(1, 2);
pstmt.setInt(2, 2);

就是用两处?占位,然后传入相同的参数。可惜事与愿违,这段SQL一定是报错的,会有提示

column "大学信息表.v_所处行政区划代码" must appear in the GROUP BY clause or be used in an aggregate function

简单的说,数据库不认为select中的left(v_所处行政区划代码, ?)group by中的left(v_所处行政区划代码, ?)是一个东西,所以认为我们在查询了一个没有group by也没有聚合函数的数据,属于SQL语法错误。
下面尝试分析原因:

  1. 原始SQLPostgreSQL中独立执行,完全没问题,所以肯定不是PostgreSQL的问题
  2. Pythonpsycopg2)实现了同样的逻辑,然而没有报错,所以还得从JDBC+PostgreSQL上找原因
  3. 去数据库服务器看日志,发现问题,当JDBC请求的时候,后台日志是
    STATEMENT:
    select left(v_所处行政区划代码, $1), count(*) as num
    from 大学信息表
    group by left(v_所处行政区划代码, $2)
    DETAIL: parameters: $1 = '2', $2 = '2'
    而当Python请求时,后台日志是
    select left(v_所处行政区划代码, 2), count(*) as num
    from 大学信息表
    group by left(v_所处行政区划代码, 2)
    是不是有一种见了鬼的感觉,

结论

在使用JDBC操作PostgreSQL时,JAVA中的prepareStatement会精准换算成PostgreSQL中的PREPARE,而在PREPARE的时候,参数还没给出,所以PostgreSQL会认为selectleft(v_所处行政区划代码, $1)并未参与group by,因而PREPARE报错,导致最终SQL执行失败。
而在psycopg2+PostgreSQL的环境中,并没有像JDBC那样充分利用PostgreSQLPREPARE特性,而是在程序侧就换算好了SQL语句,所以反而不会报错。
显然,是JDBC的设计更细致,但是却给自己挖了个坑