1

I have a postgres database with two tables: Film and Actor. Each Film can have multiple Actors and each actor can work in multiple films(i.e MANY to MANY relationship),

I want return a list of actor’s names for each film from the film table. Also actors First name should be present in ascending order in the list

I am able to write native query for that like below

SELECT
    f.title,
    STRING_AGG (
    a.first_name,
        ','
       ORDER BY
        a.first_name,
    ) actor
FROM
    film f
INNER JOIN film_actor fa USING (film_id)
INNER JOIN actor a USING (actor_id)
GROUP BY
    f.title;

Below are my sample entities

@Entity
class Film{

@Id
int film_id;

String title;

@ManyToMany(targetEntity = Actor.class,)
    @JoinTable(name = "film_actor"
            joinColumns = { @JoinColumn(name = "film_id") }, inverseJoinColumns = { @JoinColumn(name = "actor_id") })
List<Actors> actors

}


@Entity
class Actors{

@Id
int actor_id;

String first_name;

String last_name;


}

I tried to write the query Criteria Builder like below

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Object> criteriaQuery = criteriaBuilder.createQuery();

        Root<Film> filmRoot = criteriaQuery.from(Film.class);
        Join<Film, Actor> filmActorJoin = filmRoot.join("actors", JoinType.LEFT);

        Expression<String> filmActorExpression = criteriaBuilder.function("string_agg", String.class,
                filmActorJoin.get("first_name"), criteriaBuilder.literal(", ") 
                
        );
        
        criteriaQuery.groupBy(filmRoot.get("film_id")).orderBy(criteriaBuilder.asc(filmActorExpression));
        

But above criteria query is not aggregating the strings of first_name in ASC order . Can anyone help me on this

Codinggeek
  • 111
  • 1
  • 8

2 Answers2

2

In Hibernate 6, the HibernateCriteriaBuilder interface declares the listagg() function. Of course, you'll need to typecast to get it, i.e. (HibernateCriteriaBuilder) entityManager.getCriteriaBuilder().

https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/query/criteria/HibernateCriteriaBuilder.html#listagg(org.hibernate.query.criteria.JpaOrder,jakarta.persistence.criteria.Expression,jakarta.persistence.criteria.Expression)

Note that you can find a list of the supported aggregate functions here:

https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html#hql-aggregate-functions-orderedset

Gavin King
  • 3,182
  • 1
  • 13
  • 11
  • is there any chance to find it available in JPA spec sooner or later, or m.b. it is better to start `spring-data-hiberbane` or `spring-data-reliable-jpa` project? btw, vote up for efforts. – Andrey B. Panfilov Apr 30 '23 at 06:25
  • Thanks @Gavin King for the answer but in my project I am unable to upgrade the hibernate-core orm libs version to 6.2. due to some breaking changes in new version Currently I am using 5.6.11 Final version of it – Codinggeek Apr 30 '23 at 08:38
1

If you are able to define new functions in DB, I would think about something like:

PostgreSQL part:

CREATE OR REPLACE FUNCTION sort_asc(ar text[]) returns text[]
AS
$$
SELECT array((SELECT unnest(ar) ORDER BY 1))
$$
    LANGUAGE SQL
    STABLE;

Hibernate part:

cb.function("array_to_string", Object.class,
  cb.function("sort_asc", Object.class,
    cb.function("array_agg", Object.class,
      filmActorJoin.get("first_name")
    )
  ),
  cb.literal(", ")
).as(String.class)

Another "option" (from my perspective that looks more like trick), is to register sql function in Hibernate, in that case the solution would look like:

public class StringAggFunction extends SQLFunctionTemplate {

    public static final String FUNCTION_NAME = "string_agg";

    public StringAggFunction() {
        super(StringType.INSTANCE, "string_agg(?1, ?2 order by ?3)");
    }

}
cb.function("string_agg", Object.class,
        filmActorJoin.get("first_name"),
        cb.literal(", "),
        filmActorJoin.get("first_name")
).as(String.class)
Andrey B. Panfilov
  • 4,324
  • 2
  • 12
  • 18
  • Actually, I am unable to define new functions in db. I am looking to achieve the same behaviour via Criterial API Only – Codinggeek Apr 29 '23 at 08:58
  • @Codinggeek that is unfortunate, pls check another option. – Andrey B. Panfilov Apr 29 '23 at 10:15
  • Thanks @Andrey for the solution .. I am Wondering how to pass asc or desc in below code after param (?3) ``` public StringAggFunction() { super(StringType.INSTANCE, "string_agg(?1, ?2 order by ?3)"); } ``` I tried like this ``` public StringAggFunction() { super(StringType.INSTANCE, "string_agg(?1, ?2 order by ?3 ?4)"); } ``` but its not working – Codinggeek May 02 '23 at 07:43
  • @Codinggeek the problem is in your case asc/desc are not a query parameters but are part of SQL-expression, in case of column name HBN resolves value in runtime and passes it to template, but in case of "literals" HBN passes them as bind variables and that does not work, however, you may define two SQL functions: `string_agg_asc` and `string_agg_desc` with different templates: `string_agg(?1, ?2 order by ?3 asc)` and `string_agg(?1, ?2 order by ?3 desc)` – Andrey B. Panfilov May 02 '23 at 07:55
  • Thanks @Andrey. Really appreciate for your prompt response :) – Codinggeek May 02 '23 at 09:03