如何使用 JPQL、Spring Data Repositories 和 Hibernate 为 TimescaleDB `time_bucket` 函数参数化 Postgresql 间隔

Posted

技术标签:

【中文标题】如何使用 JPQL、Spring Data Repositories 和 Hibernate 为 TimescaleDB `time_bucket` 函数参数化 Postgresql 间隔【英文标题】:How to parameterise Postgresql Interval for TimescaleDB `time_bucket` function with JPQL, Spring Data Repositories and Hibernate 【发布时间】:2021-08-03 17:16:11 【问题描述】:

我在 PostgreSQL 13 上使用带有 TimescaleDB 扩展的 Spring Data JPA(下面带有 Hibernate,JPA 2.1),并希望使用 time_bucket 函数。这需要bucket_width 这是一个INTERVALtime 这是数据的TIMESTAMP 列。

我想把它放在 Spring Data Repository 中,并希望使用 JPQL @Query 将数据提取到一个投影中,该投影表示返回的时间段的聚合计数、平均值等。我不想使用原生查询,因为我想加入其他一些表,并自动填充它们的实体。

我将time_bucket 函数注册到我正在扩展的PostgisPG95Dialect,如下所示:

public class CustomPostgresqlDialect extends PostgisPG95Dialect 

    public CustomPostgresqlDialect() 
        super();
        this.registerFunction("time_bucket", new StandardSQLFunction("time_bucket", new OffsetDateTimeType()));
    

如果bucket_width 是硬编码的,那么所有这些都可以正常工作。但我希望bucket_width 成为查询方法的参数。

以下工作正常:

 @Query("select sys as system, "
                  + "function('time_bucket', '10 mins', vt.ts) as startTime, "
                  + "count(vt) as total, avg(vt.speed) as avgSpeed "
                  + "from Data vt "
                  + "JOIN vt.system sys "
                  + "where sys.sysId = :sysId and "
                  + "function('time_bucket', '10 mins', vt.ts)  between :from and :to "
                  + "group by system, startTime "
                  + "order by startTime")
  List<SummaryAggregate> getSummaryData(
          @Param("sysId") String sysId,
          @Param("from") OffsetDateTime from,
          @Param("to") OffsetDateTime to);

但是当我尝试参数化间隔时,我无法让它工作。我尝试将间隔作为字符串传递,因为这是硬编码版本中的写入方式:

 @Query("select sys as system, "
                  + "function('time_bucket', :grouping, vt.ts) as startTime, "
                  + "count(vt) as total, avg(vt.speed) as avgSpeed "
                  + "from Data vt "
                  + "JOIN vt.system sys "
                  + "where sys.sysId = :sysId and "
                  + "function('time_bucket', :grouping, vt.ts)  between :from and :to "
                  + "group by system, startTime "
                  + "order by startTime")
  List<SummaryAggregate> getSummaryData(
          @Param("sysId") String sysId,
          @Param("from") OffsetDateTime from,
          @Param("to") OffsetDateTime to,
          @Param("grouping") String grouping);

其中grouping 传递了一个类似10 mins 的值。

但为此我收到此错误:

SQL Error: 0, SQLState: 42883
ERROR: function time_bucket(character varying, timestamp with time zone) does not exist
  Hint: No function matches the given name and argument types. You might need to add explicit type casts.
  Position: 61

然后我尝试将其更改为Duration,因为Hibernate translates Duration to PostgreSQL Interval types

 @Query("select sys as system, "
                  + "function('time_bucket', :grouping, vt.ts) as startTime, "
                  + "count(vt) as total, avg(vt.speed) as avgSpeed "
                  + "from Data vt "
                  + "JOIN vt.system sys "
                  + "where sys.sysId = :sysId and "
                  + "function('time_bucket', :grouping, vt.ts)  between :from and :to "
                  + "group by system, startTime "
                  + "order by startTime")
  List<SummaryAggregate> getSummaryData(
          @Param("sysId") String sysId,
          @Param("from") OffsetDateTime from,
          @Param("to") OffsetDateTime to,
          @Param("grouping") Duration grouping);

但我仍然遇到同样的错误,这一次它认为 Duration 是 bigint 而不是 Interval

SQL Error: 0, SQLState: 42883
ERROR: function time_bucket(bigint, timestamp with time zone) does not exist
  Hint: No function matches the given name and argument types. You might need to add explicit type casts.
  Position: 61

有没有办法使用 JPQL 参数化 Interval

【问题讨论】:

【参考方案1】:

有一种方法,但您必须为此目的注册一个自定义函数,因为您不能强制转换为任意 SQL 类型。

public class CastInterval implements SQLFunction 

    @Override
    public boolean hasArguments() 
        return true;
    

    @Override
    public boolean hasParenthesesIfNoArguments() 
        return true;
    

    @Override
    public Type getReturnType(Type firstArgumentType, Mapping mapping) throws QueryException 
        return firstArgumentType;
    

    @Override
    public String render(Type firstArgumentType, List args, SessionFactoryImplementor factory) throws QueryException 
        return "cast(" + args.get(0) + " as interval)";
    

您必须在方言中注册该功能。

因此,如果方言按照指示进行扩展,则可以通过以下方式完成:

 this.registerFunction("castInterval", new CastInterval());

那么你可以这样使用它:function('time_bucket', castInterval(:grouping), vt.ts)

【讨论】:

知道如何在 JPQL function 中使用它吗?我试过"function('time_bucket', castInterval(:grouping), vt.ts),但没有用。 (它给出了一个错误antlr.NoViableAltException: unexpected token: vt,所以我认为这不是有效的语法。) 不确定异常来自哪里,但这是有效的语法。 你是对的......一切正常......谢谢!

以上是关于如何使用 JPQL、Spring Data Repositories 和 Hibernate 为 TimescaleDB `time_bucket` 函数参数化 Postgresql 间隔的主要内容,如果未能解决你的问题,请参考以下文章

无法在 FROM 子句中使用子查询编写 JPQL 查询 - Spring Data Jpa - Hibernate

无法在 FROM 子句中使用子查询编写 JPQL 查询 - Spring Data Jpa - Hibernate

没有 JPQL 查询的 Spring-data-jpa 中的 CURRENT_DATE

spring data jpa总结

Spring data jpa总结

如何在 JPQL(Spring JPA 查询)中使用 JOIN 执行 UPDATE 语句?