web-dev-qa-db-ja.com

JPAの基準APIを使用して日付間隔でグループ化する

JPAのCriteria APIを使用して、日付間隔でエンティティをグループ化しようとしています。これは、エンティティのクエリにこの方法を使用します。これは、並べ替え、フィルタリング、グループ化、集計など、任意のエンティティの任意のフィールドを要求する可能性があるAPIリクエストを処理するサービスの一部であるためです。日付フィールドによるグループ化を除いて、すべてが正常に機能します。私の基盤となるDBMS i PostgreSQL。

最小限の例を示すために、これが私のエンティティクラスです。

_@Entity
@Table(name = "receipts")
public class DbReceipt {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private Date sellDate;
    // Many other fields
}
_

この例では、「月」間隔のグループ化(したがって、年+月でグループ化)について説明しますが、結局、「年」、「日」、「分」など、任意の間隔でグループ化できるソリューションを探しています。 」.

私が達成しようとしているのは次のクエリですが、Criteria APIを使用しています:

SELECT TO_CHAR(sell_date, 'YYYY-MM') AS alias1 FROM receipts GROUP BY alias1;

そうする私の試みはこれです:

_@Service
public class ReceiptServiceImpl extends ReceiptService {
    @Autowired
    private EntityManager em;

    @Override
    public void test() {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
        Root<?> root = query.from(DbReceipt.class);
        Expression<?> expr = cb.function("to_char", String.class, root.get("sellDate"), cb.literal("YYYY-MM"));

        query.groupBy(expr);
        query.multiselect(expr);

        TypedQuery<Object[]> typedQuery = em.createQuery(query);
        List<Object[]> resultList = typedQuery.getResultList();
    }
}
_

MONTHなどではなく_to_char_関数を使用する理由は、_2019-05_や_2020-05_などのエンティティがグループ化されないようにする必要があるためです。また、例を短くするためにこの例を年と月のみに絞り込んでいますが、目標は任意の日付間隔でグループ化することです。

上記のコードは、エラーが発生する次のクエリ(SQLロギングが有効)を作成します。

_Hibernate: select to_char(dbreceipt0_.sell_date, ?) as col_0_0_ from receipts dbreceipt0_ group by to_char(dbreceipt0_.sell_date, ?)
24-05-2020 12:16:30.071 [http-nio-1234-exec-5] WARN  o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 0, SQLState: 42803
24-05-2020 12:16:30.071 [http-nio-1234-exec-5] ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - ERROR: column "dbreceipt0_.sell_date" must appear in the GROUP BY clause or be used in an aggregate function
  Position: 16
_

これは、式全体が単にエイリアスではなく、クエリの「グループ化」部分に入れられるという事実が原因です。今、私は式にエイリアスを割り当てようとしました(これは_Selection<T>_を返し、groupByは式を受け入れるため、multiselectでのみ使用できます)が、それは方法に影響しませんでしたクエリが実行されます-何も変更されません。

上記のように、Criteria APIを使用して年と月でグループ化するにはどうすればよいですか?多分_to_char_を使用する以外に別の方法がありますか?たぶん、式全体ではなくエイリアスでグループ化するgroupByメソッドにエイリアスを指定する方法はありますか?

4
Jacek Ślimok

PostgreSQLのバグだと思います(エラーはHibernateからではなく、そこから発生します)。私はEclipseLink + Derbyでコードを少し変更したバージョンを試しましたが、完全に動作します。 Derby DBには同等の_TO_CHAR_関数がないため、文字列ではなく数値を使用する必要があることに注意してください。

_Expression<Integer> year = cb.function("YEAR", Integer.class, root.get("sellDate"));
Expression<Integer> month = cb.function("MONTH", Integer.class, root.get("sellDate"));
Expression<Integer> expr = cb.sum(month, cb.prod(12, year));
query.groupBy(expr);
query.multiselect(expr);
_

これにより、次のSQLが返されます。

_SELECT (MONTH(MY_DATE) + (12 * YEAR(MY_DATE))) 
FROM MY_DATE_TABLE 
GROUP BY (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))
_

JPA条件クエリで日付を操作するためのポータブルソリューションはないことに注意してください。同時にクエリするグループの数が多すぎない場合は、より実用的な方法で、Javaで日付を検索し、それらをリテラルとしてクエリビルダーに渡します。

もう1つの回避策は、groupBy(root.get("sellDate"))を使用してクエリを実行し、目的の期間に従って結果をJava)に集約することです。

ポストスクリプト:関係ないと思いますが、クエリの戻り値の型を_Object[]_からObjectに変更しました。

1
perissf