使用协议版本3向我的Postgres db(9.2.3)发出以下hibernate hql:
select count(*) , obj1.type , (obj1.creationTime + :p1 )
from fr.xxx.WorkflowPojo obj1
group by obj1.type , (obj1.creationTime + :p1 )
order by (obj1.creationTime + :p1 ) asc
获得:ERROR: column "workflow.creation_time" must appear in the GROUP BY clause or be used in an aggregate function
虽然我对协议版本2没有例外
在pgAdmin中,以下请求是可以的,所以我猜这不是PgSql问题:
PREPARE test2(varchar) as
select count(*) ,
workflow.optype, (workflow.creation_time + $1::integer )
from workflow
group by workflow.optype, (workflow.creation_time + $1::integer )
order by (workflow.creation_time + $1::integer ) asc
如果我想使用PostgreSQL协议版本3,任何人都知道如何解决这个问题?
编辑:谢谢Craig的帮助。这是我从postgreSQL的角度来看的:
VERSION3:
STATEMENT: select count(*) as col_0_0_, workflowdd0_.optype as col_1_0_, workflowdd0_.creation_time+$1 as col_2_0_
from workflow_ddc workflowdd0_
group by workflowdd0_.optype , workflowdd0_.creation_time+$2
order by workflowdd0_.creation_time+$3
版本2:
LOG: statement: select count(*) as col_0_0_, workflowdd0_.optype as col_1_0_, workflowdd0_.creation_time+3600 as col_2_0_
from workflow_ddc workflowdd0_
group by workflowdd0_.optype , workflowdd0_.creation_time+3600
order by workflowdd0_.creation_time+3600 asc
从休眠的角度来看:
Hibernate: select count(*) as col_0_0_, workflowdd0_.optype as col_1_0_, workflowdd0_.creation_time+? as col_2_0_
from workflow_ddc workflowdd0_
group by workflowdd0_.optype , workflowdd0_.creation_time+?
order by workflowdd0_.creation_time+? asc
使用版本3服务器端预备语句,它将$ 1 $ 2 $ 3 $ 4替换为$ 1 ... 使用vesion 2,它取代了字符串客户端。
也许这是一个jdbc驱动程序错误?它应该在任何地方保持1美元......
由于 克里斯托夫
答案 0 :(得分:2)
这似乎是一个JDBC API限制,结合PostgreSQL对GROUP BY
子句相当严格。
关键的区别在于您的手动PgAdmin测试使用单个参数并在查询中使用它两次。相比之下,Hibernate查询将值作为两个单独的参数传递两次。在PREPARE
时,PostgreSQL无法证明$1
总是等于$2
,即使实际上你知道它们会,但PostgreSQL拒绝计划查询。
演示设置:
CREATE TABLE somedemo( x integer, y integer );
INSERT INTO somedemo(x,y) SELECT a,a from generate_series(1,15) a;
演示1,文本替换,工作正常:
SELECT x, (y+1) FROM somedemo GROUP BY x, y+1;
演示2,单个参数,工作正常,因为Pg可以证明一个地方的(y+$1)
在另一个地方始终等于(y+$1)
:
PREPARE preptest1(integer) AS select x, (y+$1) from somedemo GROUP BY x, y+$1;
EXECUTE preptest1(1);
演示3,两个参数。失败,因为Pg在(y+$1)
时无法证明(y+$2)
等于PREPARE
:
regress=> PREPARE preptest2(integer,integer) AS SELECT x, (y+$1) FROM somedemo GROUP BY x, y+$2;
ERROR: column "somedemo.y" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: PREPARE preptest2(integer,integer) AS SELECT x, (y+$1) FROM ...
^
当您强制执行协议级别2时,这会起作用,因为JDBC驱动程序会替换服务器端的参数。
在Python + psycopg2或其他具有更复杂数据库驱动程序的语言中,我会使用命名或位置参数来处理它:
$ python
Python 2.7.3 (default, Aug 9 2012, 17:23:57)
>>> import psycopg2
>>> conn = psycopg2.connect('')
>>> curs = conn.cursor()
>>> curs.execute("SELECT x, (y+%(parm)s) FROM somedemo GROUP BY x, y+%(parm)s", { 'parm': 1 })
>>> curs.fetchall()
[(15, 16), (3, 4), (12, 13), (14, 15), (10, 11), (11, 12), (8, 9), (5, 6), (13, 14), (1, 2), (2, 3), (4, 5), (7, 8), (9, 10), (6, 7)]
>>>
不幸的是,看起来JDBC只支持CallableStatement
中的命名参数;我们再一次看到Java遗留下来的痛苦让我们感到痛苦。
要处理此服务器端,PostgreSQL必须延迟规划这些语句,直到获得参数,然后将其作为常规的即席查询执行。尽管在准备好的声明重新规划的引入下已经奠定了一些基础,但目前还没有人支持这样做。
目前尚不清楚我们如何在JDBC驱动程序端透明地处理它。即使我们延迟发送准备好的语句直到我们得到第一组参数,我们也不会知道“$ 1”总是等于“$ 2”(并且可以组合),因为它们对于第一次执行是相同的。 ..
Hibernate无法解决这个问题;它知道:p1
在所有三个地方都是相同的参数,但它无法通过JDBC位置参数接口的限制告诉PostgreSQL。它可以将所有参数替换为查询文本,但这几乎总是错误的做法,这是一个相当不寻常的角落案例。
我看到的唯一可靠修复是PgJDBC使用命名或序数参数扩展JDBC,例如?:p1
或?:1
。然后可以扩展Hibernate的PostgreSQL方言以支持它们。为避免兼容性问题,需要设置连接参数以启用扩展参数语法。这一切看起来都非常痛苦,所以我宁愿等到JDBC规范添加真正的命名参数支持(即:不要屏住呼吸,你的孙子们可能活着看到它发生)或者只是解决问题。
我怀疑最好的选择是使用子查询生成具有生成值的虚拟表,然后在外部查询中按其分组。执行此操作的SQL将如下所示:
SELECT x, y_plus FROM (
SELECT x, (y+?) FROM somedemo
) temptable(x,y_plus)
GROUP BY x, y_plus;
此措辞只需要一个参数参考。将其翻译成HQL留给读者练习;-)。
PostgreSQL的查询优化器通常会将其转换为与简单字符串替换形式一样高效的计划,如下所示:
regress=> PREPARE preptest5(integer) AS SELECT x, y_plus FROM (SELECT x, (y+$1) FROM somedemo) temptable(x,y_plus) GROUP BY x, y_plus;
regress=> explain EXECUTE preptest5(1);
QUERY PLAN
---------------------------------------------------------------
HashAggregate (cost=1.26..1.45 rows=15 width=8)
-> Seq Scan on somedemo (cost=0.00..1.19 rows=15 width=8)
(2 rows)
regress=> explain SELECT x, y+1 FROM somedemo GROUP BY x, y+1;
QUERY PLAN
---------------------------------------------------------------
HashAggregate (cost=1.26..1.45 rows=15 width=8)
-> Seq Scan on somedemo (cost=0.00..1.19 rows=15 width=8)
(2 rows)
对于非性能关键的ad-hoc或不经常使用的函数,您可以通过编写在CTE VALUES子句中传递参数一次的本机查询来解决此问题,例如:
PREPARE preptest3(integer) AS
WITH params(a) AS (VALUES($1))
SELECT x, (y+a) FROM somedemo CROSS JOIN params GROUP BY x, y+a;
EXECUTE preptest3(1);
毋庸置疑,这是笨拙的,可能无法表现得特别出色,但它适用于您必须在许多不同的上下文中引用参数的情况。
如果你不能使用早先从HQL中提出的子查询表方法,那么hacky CTE的一个更好的替代方法是将查询包装在SQL函数中并从JDBC调用函数,例如:
-- Define this in your database schema or run it on app startup:
CREATE OR REPLACE FUNCTION test4(integer) RETURNS TABLE (x integer, y integer) AS $$
SELECT x, (y+$1) FROM somedemo GROUP BY x, y+$1;
$$ LANGUAGE sql;
-- then in JDBC prepare a simple "SELECT * FROM test4(?)", resulting in:
PREPARE preptest4(integer) AS SELECT * FROM test4($1);
EXECUTE preptest4(1);
答案 1 :(得分:0)
为了记录,最新版本的postgresql放弃了对协议V2的支持。可以使用参数preferQueryMode来强制jdbc驱动程序发送值" inline"而不是命名参数