【问题标题】:QueryDsl aggregate by day of week as mapQueryDsl 按星期几聚合为地图
【发布时间】:2020-12-11 14:57:46
【问题描述】:

我想按星期几按类型获取预订数量,例如:

| DoW   | Type   | Count |
|-----  |--------|-------|
| 1     | Store  | 3     |
| 1     | Remove | 2     |

结果应该是Map<Integer, Map<String, Long>>。我已经尝试过一些变体:

LocalDateTime monday = LocalDate.now().with(DayOfWeek.MONDAY).atStartOfDay();
LocalDateTime nextMonday = monday.plusDays(7);

var dayOfWeek = booking.bookingDate.dayOfWeek().subtract(1);

new JPAQuery<>(entityManager)
                .select(dayOfWeek, booking.bookingType, booking.bookingType.count())
                .from(booking)
                .where(booking.bookingDate.between(monday, nextMonday))
                .groupBy(booking.bookingDate, booking.bookingType)
                .transform(groupBy(dayOfWeek)
                        .as(map(booking.bookingType, booking.bookingType.count())));

但它并没有给我正确的计数(所有 1)。我考虑过将dayofweek 放在 groupby 子句中,bc。我担心按 LocalDateTime 分组,“时间”部分会搞砸,但这不起作用。

这个 postgresql 查询按预期工作:

select
    extract(DOW from b.book_date)-1 as dow,
    b.dtype as type,
    count(b.dtype) as cnt 
from
    booking b 
where
    b.book_date between '2020-05-11 0:0:0.000000' and '2020-05-18 0:0:0.000000'
group by
    b.book_date,
    b.dtype

编辑:

在 groupBy 子句中添加“dayOfWeek”时,如下所示:

.groupBy(dayOfWeek, buchung.buchungType)

然后我得到一个异常:

javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
[...]
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Feld 

"BOOKING0_.BOOKING_DATE" muss in der GROUP BY Liste sein
Column "BOOKING0_.BOOKING_DATE" must be in the GROUP BY list; SQL statement:
/* select dayofweek(booking.gebuchtDate) - ?1, booking.bookingType, count(booking.bookingType)
from Buchung booking
where booking.bookingDate between ?2 and ?3
group by dayofweek(booking.gebuchtDate) - ?1, booking.bookingType */ select dayofweek(booking0_.booking_date)-? as col_0_0_, booking0_.dtype as col_1_0_, count(booking0_.dtype) as col_2_0_ from booking booking0_ where booking0_.booking_date between ? and ? group by dayofweek(booking0_.booking_date)-? , booking0_.dtype [90016-200]

编辑 2:

Jan-Willem Gmelig Meyling 在下面的回答解决了这个问题,但我将星期几从 Sun-Sat 转换为 Mon-Sun 的逻辑当然是垃圾。让我们看看:

Postgresql 将返回星期日 (0) 到星期六 (6)(这似乎是 ISO SQL 方式),而 java.time 从星期一 (1) 到星期日 (7) 返回 ISO-8601 标准格式。所以基本上一切都匹配,除了星期天。
更糟糕的是,我了解到您可以在 MS SQL 上更改一周的开始日期。

因为我不想为这个头痛而烦恼,所以我已将其更改为 Day of Year 并且我正在使用 Java 进行转换。由于结果集最多有 7 个条目,这还不错:

    var dayOfYear = booking.bookingDate.dayOfYear();
    
    var res = new JPAQuery<>(entityManager)
            .from(booking)
            .where(booking.bookingDate.between(monday, nextMonday))
            .groupBy(dayOfYear, booking.bookingType)
            .transform(groupBy(dayOfYear)
                    .as(map(booking.bookingType, booking.bookingType.count())));

    return res.entrySet().stream()
            .collect(toMap(
                    e -> Year.now().atDay(e.getKey()).getDayOfWeek().getValue(),
                    Map.Entry::getValue
            ));

【问题讨论】:

  • 您必须按dayOfWeek 分组,因为按booking.bookingDate 分组实际上是按年分组。根据经验:您需要按投影中的任何非聚合表达式(在本例中为工厂表达式的转换)进行分组。当您按 dayOfWeek 而不是 booking.bookingDate 分组时,究竟是什么仍然无法正常工作(非常重要的是不要同时按 FWIW 分组)。
  • 我已经修改了我的问题
  • “1”是参数化的,因此 group by 表达式在查询执行计划时与 select 表达式不“相等”。你必须使用一个常数来表示 1。即.subtact(Expressions.numberTemplate("1"))。此外,我相信 QueryDSL 为 DayOfWeek 添加了 1 以使其与 Java 的星期几一致,而不是与 ANSI SQL 中定义的 DayOfWeek 一致。您可以考虑使用 JPQLTemplates 的自定义实例,该实例为 DAY_OF_WEEK 运算符定义不同的标准模板(并在那里呈现常量)。这可能是您应用程序中更安全的默认设置。
  • 或者,当使用 Hibernate 5.4.10 或更新版本时,您还可以使用“按别名分组”:在您的选择表达式中使用 dayOfWeek.as("dayOfWeekAlias"),然后在您的分组子句中使用 Expressions.numberPath("dayOfWeekAlias"): ) 希望对您有所帮助!

标签: postgresql hibernate jpa spring-data-jpa querydsl


【解决方案1】:

“1”是参数化的,因此 group by 表达式在查询执行计划时与 select 表达式不“相等”。你必须使用一个常数来表示 1。即.subtact(Expressions.numberTemplate(Integer.class, "1")).

此外,我相信 QueryDSL 将 1 添加到 DayOfWeek 以使其与 Java 的星期而不是 ANSI SQL 中定义的 DayOfWeek 保持一致。您可以考虑使用 JPQLTemplates 的自定义实例,该实例为 DAY_OF_WEEK 运算符定义不同的标准模板(并在那里呈现常量)。这可能是您的应用程序中更安全的默认设置。

或者,当使用 Hibernate 5.4.10 或更高版本时,您还可以使用“按别名分组”:在您的选择表达式中使用dayOfWeek.as("dayOfWeekAlias"),然后在您的分组子句中使用Expressions.numberPath(Integer.class, "dayOfWeekAlias")

new JPAQuery<>(entityManager)
        .from(booking)
        .where(booking.bookingDate.between(monday, nextMonday))
        .groupBy(Expressions.numberPath(Integer.class, "dayOfWeekAlias"), booking.bookingType)
        .transform(groupBy(dayOfWeek).as("dayOfWeekAlias")
            .as(map(booking.bookingType, booking.bookingType.count())));

【讨论】:

  • Ty,很好的观察力。仅供参考,需要类型 arg:Expressions.numberTemplate(Integer.class, "1")。我之前测试过它,不,一周的第一天不等于 Java 的。我不熟悉JPQLTemplates 。我正在使用 v5.4.20,但您的建议不起作用。它试图将“dayOfWeekAlias”作为属性访问,但没有生成别名。
  • 选择被转换覆盖。此处不需要选择。您是否在 .select 或转换部分中使用了“.as(...)”调用?
  • 有趣。所以我删除了select 并将dayOfWeek.as("dow1") 放在groupBy 子句中,然后是.transform(groupBy(Expressions.numberPath(Integer.class, "dow1")),但我得到了QuerySyntaxException。我也刚刚意识到我的星期翻译是垃圾......
  • 必须反过来:group by 可以在 select 子句中引用别名,后者在您的情况下由您的变换投影定义。快到了!
  • 好了!有效。如果你没问题,我会在问答中填写一些细节。
猜你喜欢
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-04
  • 2022-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多