使用 jOOQ 创建自定义聚合函数

Posted

技术标签:

【中文标题】使用 jOOQ 创建自定义聚合函数【英文标题】:Create a custom Aggregate Function with jOOQ 【发布时间】:2019-03-25 19:43:40 【问题描述】:

上下文

我正在针对 PostgreSQL 数据库使用 jOOQ。 我想在LEFT OUTER JOIN 的结果集上使用jsonb_object_agg(name, value)

问题

连接是一个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 */);

不完整的解决方案

我很想利用 jOOQAggregateFilterStep 界面,尤其是能够使用它的 AggregateFilterStep#filterWhere(Condition... conditions)

但是,implements AggregateFilterStep(间接通过AgregateFunctionArrayAggOrderByStep)的org.jooq.impl.Function 类对其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 我的coalesceTovarchar

或者,你知道:

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 生成它(并不是我不喜欢它——只是我们的管道很糟糕)。

【问题讨论】:

【参考方案1】:

从 jOOQ 3.14.0 开始

现在 jOOQ 原生支持 JSON_OBJECTAGG aggregate function:

DSL.jsonObjectAgg(TABLE.NAME, TABLE.VALUE).filterWhere(TABLE.NAME.isNotNull());

Support for the FILTER clause was added in jOOQ 3.14.8.

从 jOOQ 3.14.8 和 3.15.0 开始

如果 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实现的

Pre jOOQ 3.14.0

jOOQ DSL API 中缺少一个功能,即创建 plain SQL aggregate functions。这还不可用的原因(从 jOOQ 3.11 开始)是因为指定一个支持所有特定于供应商的选项的与供应商无关的聚合函数有很多微妙的内部机制,包括:

FILTER (WHERE ...) 子句(正如您在问题中提到的),必须使用 CASE 模拟 OVER (...) 子句将聚合函数转换为窗口函数 WITHIN GROUP (ORDER BY ...) 子句支持有序集合聚合函数 DISTINCT 子句,在支持的情况下 其他供应商特定的聚合函数扩展

在您的具体情况下,简单的解决方法是一直使用plain SQL templating,正如您在问题中提到的那样:

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。

这可能是因为您使用了返回Class&lt;?&gt;agg.getType() 而不是返回DataType&lt;?&gt;agg.getDataType()

但这将是最后的手段:感觉就像我离让用户将他们想要的任何 SQL 注入我的数据库仅一步之遥

我不确定为什么这是一个问题。您仍然可以自己控制普通 SQL API 的使用,并且用户将无法将任意内容注入 keyselect,因为您也可以控制这些元素。

【讨论】:

谢谢卢卡斯!我确实认为以与供应商无关的方式处理这个问题很有挑战性。我现在不在,但我会在 GitHub 上寻找有关此问题的讨论并尝试做出贡献!最后一点有点像个笑话:我只是想开玩笑地说“我不想自己写 SQL”,但我知道在这种情况下它完全没问题。感谢您的回答,以及 jOOQ ?

以上是关于使用 jOOQ 创建自定义聚合函数的主要内容,如果未能解决你的问题,请参考以下文章

SqlServer如何用Sql语句自定义聚合函数

60种特征工程操作:使用自定义聚合函数

使用 plsql 的用户定义的自定义聚合函数

python自定义聚合函数,merge与transform的区别

Reporting Services 使用啥类型的对象将多行传递给自定义聚合函数?

加速自定义聚合函数