My JPQL query with Outer Join returns empty list while using GroupBy, OrderBy and Sum Function - jpa

This is my Product entity:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotBlank
private String name;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Category category;
...
}
And this is the productOrder junction table for the relation product-order:
#Entity
public class ProductOrder {
#EmbeddedId
private ProductOrderId pk;
#Min(value = 1)
private int quantity;
...
}
And this is the embeddedId class:
#Embeddable
public class ProductOrderId {
#ManyToOne
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JoinColumn(name = "orderr_id")
private Order order;
...
}
I have this JPQL Query works perfectly which finds all the products from a category, sorting them based on their total sold quantity. Since it is a outer join, i get also the ones which haven't been sold yet:
#Query(value = "select p, sum(po.quantity) as total_quantity " +
"from ProductOrder po " +
"right join po.pk.product p where p.category = (?1) " +
"group by p.id, p.name " +
"order by total_quantity desc nulls last")
Page<Object[]> findBestSellerProductsByCategory(Category category, Pageable pageable);
The problem is, when there is not any sold product in a category, i get an empty page. So in order to use this query, my category has to have at least 1 sold product, then i get also the others which haven't been sold yet. What is the problem here?
Since i make a "select from junctionTable (ProductOrder)", when there is not any element from the category in this table, i get an empty list as i understand. But i could not make it the other way around "select from product", since product has no reference of productOrder table, i cannot make a join relationship in that case..
INFO: When i remove the groupBy, it works. I get my products from a category even if none of them is sold yet.
#Query(value = "select p " +
"from ProductOrder po " +
"right join po.pk.product p where p.category = (?1)")
Page<Object[]> findProductsByCategory(Category category, Pageable pageable);

Related

Query in Spring Boot JPA - #OneToMany List relation

I've got entity like this:
#Entity
#Table(name = "formula")
public class Formula {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "formula_id")
private Long formulaId;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "time")
private int time;
#OneToMany(mappedBy = "formula",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Product> productList = new ArrayList<>();
And another Entity:
#Entity
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long productId;
#Column(name = "product_name")
private String productName;
#Column(name = "amount")
private Double amount;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "formula_id")
private Formula formula;
I want to ask Query to DB which help me get every type of data (by key word). I've got all except List of <Product>. It look like this:
public interface FormulaRepository extends JpaRepository<Formula, Long> {
#Query("SELECT f FROM Formula f WHERE " + "CONCAT(f.name, f.description,
f.time)" + "LIKE %?1%")
List<Formula> findFormulaBy(String word);
How can add productList to Query and acomplished my searching? Is there any possibility to do this in findFormulaBy(String word); method?
Change query to include LEFT JOIN FETCH to eagerly fetch productList. Also include DISTINCT to prevent duplicate Formula objects in List
#Query("SELECT DISTINCT f FROM Formula f " +
"LEFT JOIN FETCH f.productList " +
"WHERE " + "CONCAT(f.name, f.description,f.time)" + "LIKE %?1%")
List<Formula> findFormulaBy(String word);
SQL generated by Hibernate
2022-09-10 10:16:38.287 DEBUG --- [ main] org.hibernate.SQL :
select
distinct formula0_.formula_id as formula_1_9_0_,
productlis1_.product_id as product_1_12_1_,
formula0_.description as descript2_9_0_,
formula0_.name as name3_9_0_,
formula0_.time as time4_9_0_,
productlis1_.amount as amount2_12_1_,
productlis1_.formula_id as formula_4_12_1_,
productlis1_.product_name as product_3_12_1_,
productlis1_.formula_id as formula_4_12_0__,
productlis1_.product_id as product_1_12_0__
from
formula formula0_
left outer join
products productlis1_
on formula0_.formula_id=productlis1_.formula_id
where
(
formula0_.name||formula0_.description||formula0_.time
) like ?
I see in your comment you have added f.productList list to the CONCAT function which is why you are getting a SQL error. If you want to search product fields in CONCAT function you will need to give p.productList an alias and reference the fields in this way
#Query("SELECT DISTINCT f FROM Formula f " +
"LEFT JOIN FETCH f.productList p " +
"WHERE " + "CONCAT(f.name, f.description,f.time,p.productName)" + "LIKE %?1%")
List<Formula> findFormulaBy(String word);
This seems a strange way to search formulae and products and you will be better off adding a second parameter to your SQL
#Query("SELECT DISTINCT f FROM Formula f " +
"LEFT JOIN FETCH f.productList p " +
"WHERE " + "CONCAT(f.name, f.description,f.time)" + "LIKE %?1% " +
"AND p.productName = ?2 ")
List<Formula> findFormulaBy(String word, String productName);

How to use JPQL join on unidirectional one to many relationship

Here are my entities:
#Entity
public class Author {
#Id
private Long id;
//...
}
and
#Entity
public class Book {
#Id
private Long id;
#ManyToOne(optional = true)
#JoinColumn(name = COLUMN_AUTHOR_ID, referencedColumnName = "id")
private Author author;
//...
}
I don't want to declare the Set<Book> books field in the Author entity to avoid unnecessary data fetch. I already know about fetch = FetchType.LAZY, but I have some cases in which even the lazy fetching is triggered.
Here is my question: How can I use JPQL join to retrieve the relationship in a custom query?
Look at ??books?? in the below query:
entityManager.createQuery("SELECT new " + AuthorWithBooks.class.getName() +
"(a.id, ..., group_concat(b.name)) FROM Author a LEFT JOIN a.??books?? b GROUP BY a.id", AuthorWithBooks.class);
You should consider that there might be some Author with no book and I want to include them in my query! So I can not start my join from Book.
Thanks
Simply switch the from and join clause:
entityManager.createQuery("SELECT new " + AuthorWithBooks.class.getName() +
"(a.id, ..., group_concat(b.name)) FROM Book b " +
"RIGHT JOIN b.author a GROUP BY a.id", AuthorWithBooks.class);

How to use string_agg with PostgreSQL in #Query annotation without nativeQuery flag?

I need to remove the nativeQuery flag from the #Query annotation.
The table structure may change in the future, and code without nativeQuery will make it easier to maintain later.
I have a class Parent that is linked to class Child with the #ManyToMany annotation.
Class Child has a field pseudonym that is a value of the String type.
The result of the query needs to be sorted by the String value from class Child, which I have to sort and then concatenate into one String value.
The query without the nativeQuery flag in the #Query annotation works if I do not add an additional sort in the string_agg function:
order by string_agg(c.pseudonym, ',')
If I add additional required sorting as below, an exception occurs
order by string_agg(c.pseudonym, ',' order by c.pseudonym)
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found 'order' near line 1, column ...
#Entity
#Getter
#Setter
#Table(name = "parent")
public class Parent {
#Id
private Long id;
private String name;
#ManyToMany
#JoinTable(
name = "parent_child_link",
joinColumns = {#JoinColumn(name = "parent_id")},
inverseJoinColumns = {#JoinColumn(name = "child_id")}
)
#OrderBy("pseudonym ASC")
private List<Child> childs = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name = "child")
public class Child {
#Id
private Long id;
private String pseudonym;
#ManyToMany(mappedBy = "childs")
private Set<Parent> parents = new HashSet<>();
}
public interface ParentRepository extends JpaRepository<Parent, Long> {
#Query(nativeQuery = true, value =
"select p.*" +
" from parent p" +
" left join parent_child_link link on p.id = link.parent_id" +
" left join child c on link.child_id = c.id" +
" where p.name = :name" +
" group by (p.id)" +
" order by string_agg(c.pseudonym, ',' order by c.pseudonym)")
Page<Parent> find(#Param("name") String name, Pageable pageable);
}
Please try with nested query:
select p.*
from (
select p, string_agg(c.pseudonym, ',' order by c.pseudonym) ord
from parent p
left join parent_child_link link on p.id = link.parent_id
left join child c on link.child_id = c.id
where p.name = :name
group by (p.id)
) inn(p, ord)
order by ord
or:
select p.*
from(
select p, c.pseudonym
from parent p
left join parent_child_link link on p.id = link.parent_id
left join child c on link.child_id = c.id
where p.name = :name
order by p, pseudonym
) inn(p, pseudonym)
group by p.id
order by string_agg(pseudonym, ',')

How to return a count column not exists in table by JPA

I want find a way to get extra column that count my records and return it in 1 mapping entity with extra filed.
I tried #transient on field but it will not return value when query.
Then I remove #transient but get an exception when save.
Also I tried #Formula but received null pointer exception.
Here's my repository code:
#Query(value = "select id,account,session_id,create_time,count from query_history a join " +
"(select session_id sessionId,max(create_time) createTime,count(*) count from query_history group by session_id) b " +
"on a.session_id = b.sessionId and a.create_time = b.createTime where account = ?1 order by create_time desc",
countQuery = "select count(distinct(session_id)) from query_history where account = ?1",
nativeQuery = true)
Page<QueryHistory> findByNtAndGroupBySessionAndAction(String account, Pageable pageable);
entity code:
#Entity
#Table(name = "query_history")
#Data
public class QueryHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String account;
#Column
private Long sessionId;
#Column
private long createTime;
#Transient
private Integer count;
}
Sorry about my English and thanks a lot for any advice.
I solved the problem by projections spring-data-projections, in fact I tried this before but in my sql:
select id,account,session_id,create_time,count
which should be:
select id,account,session_id sessionId,create_time createTime,count
PS:
projection interface:
public interface QueryHistoryWithCountProjection {
Long getId();
String getAccount();
Long getSessionId();
long getCreateTime();
Integer getCount();
}

JPA CRITERIA QUERY with order by joined columns

How to invoke order by on a joined entity? I am trying to achieve the following with:
select * from person p inner join telephone t on p.id=t.person_id join sim s on s.id=t.sim_id order by s.name DESC
#Entity
public class Person implements Serializable{
#Id
private Long id;
#OneToMany(orphanRemoval = true, mappedBy = "person", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Telephone> telephonesNumber;
#Entity
public class Telephone implements Serializable {
#Id
private String number;
#Id
#ManyToOne()
#JoinColumn(name = "person_id")
private Person person;
#Id
#ManyToOne(cascade = {})
#JoinColumn(name = "sim_id")
private Sim sim;
#Entity
public class Sim implements Serializable {
#Id
private Long id;
#Column(unique = true)
private String name;
I use specification interface, in this example sorting is on the field person.id and it works
public class PersonSpecification implements Specification<Person> {
#Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// there is many different conditions for example
// if(someCondition!=null) {
// predicates.add(builder.like(root.get("someProperty"), someValue));
// }
query.groupBy(root.get("id"));
//there I want to order by Sim.name i dont know how
query.orderBy(builder.asc(root.get("phone")));//this works
return builder.and((predicates.toArray(new Predicate[predicates.size()])));
}
I want to order by Sim.name but i dont know how.
In JPA specification you can use:
query.orderBy(builder.asc(root.join("telephonesNumber").get("sim").get("name")));
to sort by sim name.
For more details:
https://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining.2C_querying_on_a_OneToMany_relationship
If you using JPA Query:
#Query("select s from Person p
join p.telephonesNumber t
join t.sim s order
by t.sim.id desc")
It will produce this:
select * from person p
inner join telephone t on p.id=t.person_id
inner join sim s on t.sim_id=s.id
order by t.sim_id desc
For more details:
https://github.com/abhilekhsingh041992/spring-boot-samples/blob/master/jpa/src/main/java/example/springboot/jpa/repository/PersonRepository.java
another way for that would be using Query method:
List<Telephone> findAllByOrderBySimIdAsc();
Look at this findAllByOrderBySimIdAsc
With the code before, you can get all rows from Telephone ordered by Sim Id.