[英]Create a custom Aggregate Function with jOOQ
我正在针对PostgreSQL数据库使用jOOQ 。
我想将jsonb_object_agg(name, value)
用于LEFT OUTER JOIN
的结果集。
在加入作为一个OUTER
一个,有时name
聚集功能的部分仅仅是null
:那是行不通的。 然后我会去:
COALESCE(
json_object_agg(table.name, table.value) FILTER (WHERE table.name IS NOT NULL),
'{}'
)::json
到目前为止,我用来调用jsonb_object_agg
的代码(不完全是,但归结为)如下:
public static Field<?> jsonbObjectAgg(final Field<?> key, final Select<?> select) {
return DSL.field("jsonb_object_agg({0}, ({1}))::jsonb", JSON_TYPE, key, select);
}
...其中JSON_TYPE
是:
private static final DataType<JsonNode> JSON_TYPE = SQLDataType.VARCHAR.asConvertedDataType(/* a custom Converter */);
我很想利用jOOQ的AggregateFilterStep
接口,特别是能够使用它的AggregateFilterStep#filterWhere(Condition... conditions)
。
但是, implements AggregateFilterStep
的org.jooq.impl.Function
类(间接通过AgregateFunction
和ArrayAggOrderByStep
)在其package
可见性方面受到限制,因此我不能盲目地回收DSL#ArrayAggOrderByStep
的实现:
public static <T> ArrayAggOrderByStep<T[]> arrayAgg(Field<T> field) {
return new org.jooq.impl.Function<T[]>(Term.ARRAY_AGG, field.getDataType().getArrayDataType(), nullSafe(field));
}
我最接近合理的是......构建我自己的coalesceAggregation
函数,专门合并聚合字段:
// Can't quite use AggregateFunction there
// v v
public static <T> Field<T> coalesceAggregation(final Field<T> agg, final Condition coalesceWhen, @NonNull final T coalesceTo) {
return DSL.coalesce(DSL.field("{0} FILTER (WHERE {1})", agg.getType(), agg, coalesceWhen), coalesceTo);
}
public static <T> Field<T> coalesceAggregation(final Field<T> agg, @NonNull final T coalesceTo) {
return coalesceAggregation(agg, agg.isNotNull(), coalesceTo);
}
...但我然后遇到了问题,我T
类型为JsonNode
,其中DSL#coalesce
似乎CAST
我coalesceTo
至varchar
。
或者,你知道:
DSL.field("COALESCE(jsonb_object_agg({0}, ({1})) FILTER (WHERE {0} IS NOT NULL), '{}')::jsonb", JSON_TYPE, key, select)
但这将是最后的手段:感觉就像我离让用户将他们想要的任何 SQL 注入我的数据库只差一步了🙄
jOOQ有没有办法“正确”实现自己的聚合函数,作为实际的org.jooq.AgregateFunction
?
我想尽可能避免让jooq-codegen
生成它(不是我不喜欢它——这只是我们的管道太可怕了)。
现在JSON_OBJECTAGG
原生支持JSON_OBJECTAGG
聚合函数:
DSL.jsonObjectAgg(TABLE.NAME, TABLE.VALUE).filterWhere(TABLE.NAME.isNotNull());
在 jOOQ 3.14.8 中添加了对FILTER
子句的支持。
如果 jOOQ 没有实现特定的聚合函数,您现在可以指定DSL.aggregate()
来使用自定义聚合函数。
DSL.aggregate("json_object_agg", SQLDataType.JSON, TABLE.NAME, TABLE.VALUE)
.filterWhere(TABLE.NAME.isNotNull());
这是通过https://github.com/jOOQ/jOOQ/issues/1729实现的
jOOQ DSL
API 中缺少一个功能,即创建纯 SQL 聚合函数。 这还不可用的原因(从 jOOQ 3.11 开始)是因为有很多微妙的内部结构来指定支持所有供应商特定选项的供应商不可知聚合函数,包括:
FILTER (WHERE ...)
子句(如您在问题中提到的),必须使用CASE
进行模拟OVER (...)
子句将聚合函数转换为窗口函数WITHIN GROUP (ORDER BY ...)
子句支持有序集合聚合函数DISTINCT
子句,如果支持在您的特定情况下,简单的解决方法是一直使用纯 SQL 模板,正如您在问题中提到的:
DSL.field("COALESCE(jsonb_object_agg({0}, ({1})) FILTER (WHERE {0} IS NOT NULL), '{}')::jsonb", JSON_TYPE, key, select)
或者你做你之前提到的事情。 关于这种担忧:
...但随后我遇到了 T 类型为 JsonNode 的问题,其中 DSL#coalesce 似乎将我的 coalesceTo 转换为 varchar。
这可能是因为你使用agg.getType()
返回Class<?>
而不是agg.getDataType()
返回DataType<?>
。
但这将是最后的手段:感觉就像我离让用户将他们想要的任何 SQL 注入我的数据库仅一步之遥
我不确定为什么这是一个问题。 您仍然可以自己控制普通 SQL API 的使用,并且用户将无法将任意内容注入key
和select
因为您也控制这些元素。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.