java.sql.SQLSyntaxErrorException: No such column: id
When table already contains the column id
I'm trying to have the following native SQL query that runs with expected results from within MariaDB SQL from a Repository class:
SELECT name
FROM table1 t1
WHERE t1.table2_id IN (
SELECT id
FROM table2 t2
WHERE t2.column1_number = 1 AND
t2.id IS NOT NULL
)
ORDER BY t1.name
The entity classes are as follows:
#Entity
#Table(name = "table1")
public class Table1 {
#Column(nullable = false)
private String name;
#Column(nullable = false)
private Integer number;
#ManyToOne
#JoinColumn(name = "table2_id")
private Table2 table2;
// Getters and Setters
}
#Entity
#Table(name="table2")
public class Table2 {
private Integer id;
private Integer column1_number;
#Override
public Integer getId() {
return id;
}
#Override
public void setId(Integer id) {
this.id = id;
}
// Getters and Setters
}
The Repository is:
#Repository
public interface Table1Repository extends JpaRepository<Table1, Integer> {
#Query(value = "SELECT name FROM table1 t1 WHERE t1.table2_id IN (SELECT id FROM table2 t2 WHERE t2.column1_number = ?1 AND t2.id IS NOT NULL) ORDER BY t1.name", nativeQuery = true)
List<Table1> findByNumberOrderByName(Integer number);
}
Any ideas as to why I am getting this error at run time? This error does not occur at compile time.
#Query(value = "SELECT name FROM table1 t1 WHERE t1.table2_id IN (SELECT id FROM table2 t2 WHERE t2.column1_number = ?1 AND t2.id IS NOT NULL) ORDER BY t1.name", nativeQuery = true)
List<Table1> findByNumberOrderByName(Integer number);
should have been List<String> and not List<Table1>
and so the solution was:
#Query(value = "SELECT name FROM table1 t1 WHERE t1.table2_id IN (SELECT id FROM table2 t2 WHERE t2.column1_number = ?1 AND t2.id IS NOT NULL) ORDER BY t1.name", nativeQuery = true)
List<String> findByNumberOrderByName(Integer number);
You are trying to search in table2 using id, but you didn't choose the correct mapping column from table1 which should be table2_id.id , so the query should be something like this
SELECT name FROM table1 t1 WHERE t1.table2_id.id IN (SELECT id FROM table2 t2 WHERE t2.column1_number = 1 AND t2.id IS NOT NULL) ORDER BY t1.name
Also note you're using native query, that means querying the db directly, so make sure you're using the same correct name in your database tables.
Try to add Id element to table1 and annotated both tables Id fields with #Id annotation like below.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
Related
I am having trouble to converting the following postgresql query (with a join and a group by) to JPA criteria API for a Spring Boot, JPA, Hibernate application:
select u.id, u.full_name, count(*) project_applications_count from users u
join project_applications pa on pa.created_by = u.id
group by u.id, u.full_name
having count(*) >= 1 and count(*) <= 5
The tables look like this:
create table project_applications (
id serial primary key,
...
city_id integer not null references cities (id),
created_by integer not null references users (id)
);
create table users (
id serial primary key,
...
full_name varchar(100) not null
);
And the entities look like this:
#Entity
#Table(name = "project_applications")
public class ProjectApplication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "created_by")
private User createdBy;
...
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "full_name")
private String fullName;
...
}
I tried searching online for a solution but every exemple I found was using either a join or group by, but not both.
Using #akortex's idea with projections, I think something like this should work:
public class UserSummary {
private Long id;
private String fullName;
private Long count;
public UserSummary() {
}
public UserSummary(Long id, String fullName, Long count) {
this.id = id;
this.fullName = fullName;
this.count = count;
}
... (getters and setters)
}
public List<UserSummary> getSummaries(Integer minProjectAppsCount, Integer maxProjectAppsCount) {
CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
CriteriaQuery<UserSummary> query = cb.createQuery(UserSummary.class);
Root<ProjectApplication> projectApp = query.from(ProjectApplication.class);
Join<ProjectApplication, User> userJoin = projectApp.join("createdBy", JoinType.INNER);
query.multiselect(userJoin.get("id"), userJoin.get("fullName"), cb.count(projectApp))
.groupBy(userJoin.get("id"), userJoin.get("fullName"));
List<Predicate> predicates = new ArrayList<>();
if (minProjectAppsCount != null ) {
Predicate p = cb.ge(cb.count(projectApp), minProjectAppsCount);
predicates.add(p);
}
if (maxProjectAppsCount != null ) {
Predicate p = cb.le(cb.count(projectApp), maxProjectAppsCount);
predicates.add(p);
}
query.having(predicates.toArray(new Predicate[0]));
return _entityManager.createQuery(query).getResultList();
}
You could potentially look into projections in order to achieve what you want.
For example consider the following projection and repository:
#Data
#AllArgsConstructor
public class ProjectApplicationSummary {
private Long id;
private String fullName;
private Long count;
}
And:
#Repository
public interface ProjectApplicationRepository extends JpaRepository<ProjectApplication, Long> {
#Query(
"""
SELECT new com.example.springdemo.entities.ProjectApplicationSummary(u.id, u.fullName, count(pa))
FROM User u, ProjectApplication pa
GROUP BY u.id, u.fullName
"""
)
List<ProjectApplicationSummary> getSummaries();
}
You will most likely need to tweak the query a bit (which revolves experimenting with JPQL) but other than that, the basic idea is there.
I'm not sure in my solution, but it should be similar. I took an idea from here. Maybe it helps you to resolve your problem.
public static Specification<User> getUsers() {
return Specification.where((root, query, criteriaBuilder) -> {
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<ProjectApplication> subRoot = subQuery.from(ProjectApplication.class);
subQuery
.select(criteriaBuilder.count(subRoot))
.where(criteriaBuilder.equal(root.get("id"), subRoot.get("createdBy").get("id")));
query
.multiselect(criteriaBuilder.construct(root.get("id"), root.get("fullName")))
.groupBy(root.get("id"), root.get("fullName"))
.having(criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(subQuery.getSelection(), 1L),
criteriaBuilder.lessThanOrEqualTo(subQuery.getSelection(), 5L)));
return query.getRestriction();
});
}
I'm developing an application using Spring Boot and Spring Data JPA.
The following are the relevant entities:
#Entity
public class Certification {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "certification_type_id")
private CertificationType certificationType;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "certification", cascade = CascadeType.ALL)
private Set<CertificationStatus> certificationStatuses;
}
#Entity
public class CertificationType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
private Code code;
private String name;
private String description;
...
public enum Code {
...
}
}
#Entity
public class CertificationStatus {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "certification_id")
private Certification certification;
#ManyToOne
#JoinColumn(name = "certification_status_type_id")
private CertificationStatusType certificationStatusType;
private String remarks;
}
#Entity
public class CertificationStatusType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
private Code code;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "certificationStatusType", cascade = CascadeType.ALL)
private Set<CertificationStatus> certificationStatuses;
public enum Code {
...
}
}
In my service, I have a method, CertificationService.findAll(). The idea is to retrieve certification records whose latest certification_status is based on MAX(id), GROUP BY certification_id and WHERE certification_status_type_id = ?. This method has five (5) parameters:
Integer certificationTypeId,
Integer certificationStatusTypeId,
LocalDate dateFrom,
LocalDate dateTo,
String client
The combination could be zero (0) to five (5). Thus, I chose to apply JPA Criteria. So far, I have coded the following:
#Override
public List<CertificationDto> findAll(
Integer certificationTypeId,
Integer certificationStatusTypeId,
LocalDate dateFrom,
LocalDate dateTo,
String client) {
var certifications = this.certificationRepository.findAll((root, criteriaQuery, criteriaBuilder) -> {
var predicates = new ArrayList<Predicate>();
// This is working
if (certificationTypeId != null && certificationTypeId > 0) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.join(Certification_.certificationType, JoinType.INNER), certificationTypeId)));
}
// To do
if (certificationStatusTypeId != null && certificationStatusTypeId > 0) {
/*
* I have no idea how to apply my SQL statement:
* SELECT * FROM certification_status WHERE id IN (SELECT MAX(id) FROM certification_status GROUP BY certification_id) and certification_status_type_id = ?;
*/
// Not sure what is the direction of the ff codes
var certificationStatuses = root.join(Certification_.certificationStatuses);
predicates.add(criteriaBuilder.and(criteriaBuilder.max(certificationStatuses.get(CertificationStatus_.id))));
// It says Cannot resolve method 'and(javax.persistence.criteria.Expression<N>)'
// Not also sure how to pass in parameter certificationStatusTypeId
}
// This is working
if (dateFrom != null) {
var offsetDateTimeFrom = OffsetDateTime.of(dateFrom, LocalTime.MIDNIGHT, ZoneOffset.ofHours(8));
var dateTimeFrom = Date.from(offsetDateTimeFrom.toInstant());
var offsetDateTimeTo = OffsetDateTime.of(Objects.requireNonNullElse(dateTo, dateFrom), LocalTime.MAX, ZoneOffset.ofHours(8));
var dateTimeTo = Date.from(offsetDateTimeTo.toInstant());
predicates.add(criteriaBuilder.and(criteriaBuilder.between(
root.get(Certification_.createdAt),
criteriaBuilder.literal(dateTimeFrom),
criteriaBuilder.literal(dateTimeTo)))
);
}
// This is working
if (client != null && !client.isBlank()) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(criteriaBuilder.lower(root.join(Certification_.client).get(Client_.name)), "%" + client.toLowerCase() + "%")));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
});
return certifications.stream()
.map(this.certificationMapper::fromEntityToDto)
.collect(Collectors.toUnmodifiableList());
}
My criteria for certificationTypeId, dateFrom, dateTo, and client are working. For the certificationStatusTypeId, I want to create criteria based on the following SQL statement:
SELECT * FROM certification_status WHERE id IN (SELECT MAX(id) FROM certification_status GROUP BY certification_id) and certification_status_type_id = ?;
This statement is working in the database. Not sure though if this is the best composition of query to select the record based on MAX(id), GROUP BY certification_id and WHERE certification_status_type_id = ?.
Appreciate any help. Thank you.
**Update
This is now my code:
if (certificationStatusTypeId != null && certificationStatusTypeId > 0) {
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<CertificationStatus> subRoot = subQuery.from(CertificationStatus.class);
subQuery.select(criteriaBuilder.max(subRoot.get(CertificationStatus_.id)))
.groupBy(subRoot.get(CertificationStatus_.certification).get(Certification_.id));
predicates.add(criteriaBuilder.and(criteriaBuilder.in(subRoot.get(CertificationStatus_.id)).value(subQuery)));
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(subRoot.get(CertificationStatus_.certificationStatusType).get(CertificationStatusType_.id), certificationStatusTypeId)));
}
But it throws the following error:
org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.id' [select generatedAlias0 from entity.Certification as generatedAlias0 where ( generatedAlias1.id in (select max(generatedAlias1.id) from entity.CertificationStatus as generatedAlias1 group by generatedAlias1.certification.id) ) and ( generatedAlias1.certificationStatusType.id=2 )]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.id' [select generatedAlias0 from entity.Certification as generatedAlias0 where ( generatedAlias1.id in (select max(generatedAlias1.id) from entity.CertificationStatus as generatedAlias1 group by generatedAlias1.certification.id) ) and ( generatedAlias1.certificationStatusType.id=2 )]
Looking at the generated JPQL, it seems it looks similar to my SQL statement. I just don't understand why it's Invalid path.
**Update
My apologies! My SQL statement is actually similar to the following:
select
// props of Certification
from certification c
inner join certification_type ct on ct.id = c.certification_type_id
inner join client cl on cl.certification_id = c.id
inner join certification_status cs on cs.certification_id = c.id
inner join certification_status_type cst on cst.id = cs.certification_status_type_id
where cs.id in (select max(cs2.id) form certification_status cs2 group by cs2.certification_id) and cs.certification_status_type_id = ?;
Update
This is solved. Thanks to Thorben Janssen for being patient looking into my issue.
This is the final portion of my codes:
if (certificationStatusTypeId != null && certificationStatusTypeId > 0) {
var certificationStatuses = root.join(Certification_.certificationStatuses);
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<CertificationStatus> subRoot = subQuery.from(CertificationStatus.class);
subQuery.select(criteriaBuilder.max(subRoot.get(CertificationStatus_.id)))
.groupBy(subRoot.get(CertificationStatus_.certification).get(Certification_.id));
predicates.add(criteriaBuilder.and(certificationStatuses.get(CertificationStatus_.id).in(subQuery)));
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(certificationStatuses.get(CertificationStatus_.certificationStatusType).get(CertificationStatusType_.id), certificationStatusTypeId)));
}
The following code snippet creates a CriteriaQuery that's identical to your SQL statement. Because you're using Spring Data JPA, you can ignore the first block and the last line. Spring Data JPA provides them for you.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CertificationStatus> criteriaQuery = cb.createQuery(CertificationStatus.class);
Root<CertificationStatus> root = criteriaQuery.from(CertificationStatus.class);
criteriaQuery.select(root);
Subquery<Long> sub = criteriaQuery.subquery(Long.class);
Root<CertificationStatus> subRoot = sub.from(CertificationStatus.class);
sub.select(cb.max(subRoot.get("id")));
sub.groupBy(subRoot.get("certification").get("id"));
criteriaQuery.where(root.get("id").in(sub));
em.createQuery(criteriaQuery).getResultList();
You can create a subquery by calling the subquery method on your CriteriaQuery. This returns a Subquery interface which you can use in the same way as the CriteriaQuery interface.
After you defined your Subquery, you can define the IN clause and include it in the WHERE clause of your query. This is the part that always looks a little strange. Instead of using the CriteriaBuilder to define the Expression, you get the entity attribute and call the in method on it with a reference to your Subquery.
In the final step, you use the CriteriaQuery to instantiate a TypedQuery and execute it. After you activated the logging of SQL statements, you can see that Hibernate executes the following SQL statement:
select
certificat0_.id as id1_4_,
certificat0_.certification_id as certific3_4_,
certificat0_.certification_status_type_id as certific4_4_,
certificat0_.remarks as remarks2_4_
from
CertificationStatus certificat0_
where
certificat0_.id in (
select
max(certificat1_.id)
from
CertificationStatus certificat1_
group by
certificat1_.certification_id
)
**Update
It looks like you're referencing the wrong table when defining the IN clause. Please try to replace
predicates.add(criteriaBuilder.and(criteriaBuilder.in(subRoot.get(CertificationStatus_.id)).value(subQuery)));
with
predicates.add(criteriaBuilder.and(certificationStatuses.get(CertificationStatus_.id).in(subQuery)));
Is there a way to select multiple columns in a subquery of hql?
I want to convert the following mysql query to hql
select u.name, sub.cnt from user u
inner join (select user_id, count(user_id) cnt from user_log group by user_id order by cnt desc limit 5) sub
on u.id = sub.user_id;
But in hql, I know that can use subquery only in where clause
I'm not aware of a pure Hibernate solution. I talk about that in my Blog post.
But there is a FluentJPA solution that you may consider:
FluentQuery query = FluentJPA.SQL((User u) -> {
UserIdCount sub = subQuery((UserLog log) -> {
int count = alias(COUNT(log.getUserId()), UserIdCount::getCount);
SELECT(log.getUserId(), count);
FROM(log);
ORDER(BY(count).DESC());
LIMIT(5);
});
SELECT(u.getName(), sub.getCount());
FROM(u).JOIN(sub).ON(u.getId() == sub.getUserId());
});
return query.createQuery(em, UserNameCount.class).getSingleResult();
which produces the following SQL:
SELECT t0.name, q0.count
FROM USER t0 INNER JOIN (SELECT t1.user_id, COUNT(t1.user_id) AS count
FROM USER_LOG t1
ORDER BY COUNT(t1.user_id) DESC
LIMIT 5 ) q0 ON (t0.id = q0.user_id)
Entities I used (declared with lombok):
#Entity
#Getter
#Table(name = "USER")
class User {
private Long id;
private String name;
}
#Entity
#Getter
#Table(name = "USER_LOG")
class UserLog {
private Long userId;
}
#Tuple
#Getter
class UserIdCount {
private Long userId;
private int count;
}
#Tuple
#Data
class UserNameCount {
private int count;
private String name;
}
Can you help me converting this SQL statement into a CriteriaBuilder statement? The problem I have is with the INNER JOIN statement.
SELECT th.id, th.date, th.exercise_id
FROM traininghistory th
INNER JOIN (
SELECT exercise_id, MAX(date) as maxdate
FROM traininghistory
group by exercise_id
) AS tm on tm.exercise_id = th.exercise_id AND th.date = tm.maxdate
WHERE th.accountid = :accountId
#Entity
public class TrainingHistory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#NotNull
public Long id;
public Long accountId;
#ManyToOne
public Exercise exercise;
public Date dateDone = new Date();
public WellBeing wellBeing;
public int weight;
public int repetitions;
public int duration;
}
Found a solution by reformulating the query without the INNER JOIN. The following SQL query achieves the same result as the SQL query in the question, but was translatable for me into Criteria API.
FROM traininghistory th
WHERE th.datedone in (
SELECT MAX(tm.datedone)
FROM traininghistory tm
GROUP BY tm.exercise_id
)
AND th.accountid = :userId
So using that as a basis the statement using Criteria API is as follows:
// define query
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery<TrainingHistory> query = cb.createQuery(TrainingHistory.class);
Root<TrainingHistory> root = query.from(TrainingHistory.class);
query.select(root);
// define subquery
Subquery<Integer> subquery = query.subquery(Integer.class);
Root<TrainingHistory> rootSubquery = subquery.from(TrainingHistory.class);
Expression<Integer> max = cb.max(rootSubquery.get(TrainingHistory_.DATE_DONE));
subquery.select(max);
subquery.groupBy(rootSubquery.get(TrainingHistory_.exercise));
// compose whole query
query.where(
cb.and(
cb.in(root.get(TrainingHistory_.DATE_DONE)).value(subquery),
cb.equal(root.get(TrainingHistory_.ACCOUNT_ID), userId)
)
);
return this.entityManager.createQuery(query).getResultList();
I have the following tables with the following structure
Table A {
id <-- Primary key
someColumn
}
Table B {
id <-- Primary key
someColumn
idOfA <-- Foreign key mapping to Table A
}
Entity classes look like below
#Entity
#Table(name = "A")
public class A implements Serializable {
private static final long serialVersionUID = -78448557049178402L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToMany(mappedBy = "a")
private List<B> bs = new ArrayList<>();
}
#Entity
#Table(name = "B")
public class B implements Serializable {
private static final long serialVersionUID = -659500557015441771L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToOne
#JoinColumn(name = "a_id", nullable = false)
private A a;
}
Using JPA2, I want to select records from table A which do not have a reference in Table B.
The Expected native postgres query is
select * from A a
where a.id not in
(select b.idOfA from B b);
What I have so far managed to do is
public List<A> getANotInB() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// Select From Table B
CriteriaQuery<B> criteriaQueryB = criteriaBuilder
.createQuery(B.class);
Root<B> rootB = criteriaQueryB.from(B.class);
criteriaQueryB.select(rootB);
// Select records from Table A
CriteriaQuery<A> criteriaQueryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = criteriaQueryA.from(A.class);
criteriaQueryA.select(A);
// Create predicate
Predicate predicate = rootAttemptA.in(criteriaQueryB.getSelection());
criteriaQueryA.where(criteriaBuilder.not(predicate));
// Create query
TypedQuery<A> query = entityManager.createQuery(criteriaQueryA);
List<A> as= query.getResultList();
System.out.println(as);
return as;
}
I know the code above is incorrect and I have got a lot of basics wong.
Kindly help
Note: I Want to use JPA2 Criteria Query
Try this
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// Select distinct aid from B
CriteriaQuery<B> bQuery = cb.createQuery(B.class);
Root<B> bRoot = bQuery.from(B.class);
bQuery.select(bRoot.get("a").get("id")).distinct(true);
// Select * from A where aid not in ()
CriteriaQuery<A> aQuery = cb.createQuery(A.class);
Root<A> aRoot = aQuery.from(A.class);
aQuery.select(aRoot).where(cb.not(aRoot.get("id").in(bQuery)));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Basically, you will construct part of the query and glue them together.
More information here:
JPA Criteria
I was able to get it done using subquery() as below. Posting it so that it can help others
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// select a from A a
CriteriaQuery<A> queryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = queryA.from(A.class);
queryA.select(rootA);
// Select distinct aId from B
CriteriaQuery<B> subQueryB = queryA.subquery(B.class);
Root<B> rootB = subQueryB.from(B.class);
bQuery.select(rootB.get("a")).distinct(true);
queryA.where(criteriaBuilder.not(criteriaBuilder.in(rootA.get("id").value(subQueryB))));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Thanks #Mạnh for showing the way