当我在子查询中进行选择时,我得到了我认为不必要的交叉连接,这会损害性能。如果这有什么区别的话,我正在使用 Postgres。
我的目标是生成以下查询
select a1.first_name from author a1
where a1.last_name = ?
and (a1.id in
(select distinct b.author_id
from book b
where (b.published_on between ? and ?)
group by b.author_id
having count(b.author_id) >= 2))
但是我明白了
select a1.first_name from author a1
where a1.last_name = ?
and (a1.id in
(select distinct b.author_id
from book b
cross join author a2 where b.author_id = a2.id -- <<< I don't want this cross join!
and (b.published_on between ? and ?)
group by b.author_id
having count(b.author_id) >= 2))
代码
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> cq = cb.createQuery(Author.class);
Root<Author> authorRoot = cq.from(Author.class);
Subquery<Long> countSubquery = cq.subquery(Long.class);
Root<Book> bookRoot = countSubquery.from(Book.class);
Expression<Long> count = cb.count(bookRoot.get(Book_.author));
countSubquery.select(bookRoot.get(Book_.AUTHOR))
.distinct(true)
.where(cb.between(bookRoot.get(Book_.publishedOn),
LocalDate.of(2021, MARCH, 1),
LocalDate.of(2021, MARCH, 31)))
.groupBy(bookRoot.get(Book_.author))
.having(cb.greaterThanOrEqualTo(count, 2L));
cq.where(
cb.equal(authorRoot.get(Author_.lastName), "Smith"),
cb.in(authorRoot.get(Author_.ID)).value(countSubquery));
cq.select(authorRoot.get(Author_.FIRST_NAME));
TypedQuery<String> query = entityManager.createQuery(cq);
return query.getResultList();
实际上,我是从用户驱动的查询生成器生成查询,此代码重新创建了我遇到的确切问题。
使用查询生成器时,用户最终可能会在子查询中进行多次选择,因此我需要它尽可能地执行。
我不明白为什么我需要任何联接/交叉联接才能让我的查询工作。
实体
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Book> books;
}
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
private LocalDate publishedOn;
}
此表达式:
bookRoot.get(Book_.author)
表示您隐式地将 Author
加入到 Book
。
要摆脱额外的连接,您必须使用本机查询,或再次将
Book.author_id
映射为简单列:
@Column(name = "author_id", insertable = false, updatable = false)
private Long authorId;
并使用
Book_.authorId
代替。
您的问题只需使用
join
而不是 get
中的 bookRoot.get(Book_.author)
即可解决。您在没有显式连接的情况下引用 Author
(关系属性,而不是基本属性)。这将使持久性提供者生成 cross join
而不是 inner join
。