I created the following two tables in a MySQL database with only the required fields.
mysql> desc cars; +
booking_id in the bookings table is the primary key, and fk_car_id is the foreign key that refers to the primary key ( car_id ) of the cars table.
The corresponding JPA criteria query using the IN() subquery is as follows.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<Cars> criteriaQuery = criteriaBuilder.createQuery(Cars.class); Metamodel metamodel = entityManager.getMetamodel(); Root<Cars> root = criteriaQuery.from(metamodel.entity(Cars.class)); Subquery<Long> subquery = criteriaQuery.subquery(Long.class); Root<Bookings> subRoot = subquery.from(metamodel.entity(Bookings.class)); subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId)); List<Predicate> predicates = new ArrayList<Predicate>(); ParameterExpression<Date> fromDate1 = criteriaBuilder.parameter(Date.class); Predicate exp1 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate1); ParameterExpression<Date> toDate1 = criteriaBuilder.parameter(Date.class); Predicate exp2 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate1); Predicate and1 = criteriaBuilder.and(exp1, exp2); ParameterExpression<Date> fromDate2 = criteriaBuilder.parameter(Date.class); Predicate exp3 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate2); ParameterExpression<Date> toDate2 = criteriaBuilder.parameter(Date.class); Predicate exp4 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate2); Predicate and2 = criteriaBuilder.and(exp3, exp4); ParameterExpression<Date> fromDate3 = criteriaBuilder.parameter(Date.class); Predicate exp5 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate3); ParameterExpression<Date> toDate3 = criteriaBuilder.parameter(Date.class); Predicate exp6 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate3); Predicate and3 = criteriaBuilder.and(exp5, exp6); Predicate or = criteriaBuilder.or(and1, and2, and3); predicates.add(or); subquery.where(predicates.toArray(new Predicate[0])); criteriaQuery.where(criteriaBuilder.in(root.get(Cars_.carId)).value(subquery).not()); List<Cars> list = entityManager.createQuery(criteriaQuery) .setParameter(fromDate1, new Date("2015/05/04")) .setParameter(toDate1, new Date("2015/05/04")) .setParameter(fromDate2, new Date("2015/05/06")) .setParameter(toDate2, new Date("2015/05/06")) .setParameter(fromDate3, new Date("2015/05/04")) .setParameter(toDate3, new Date("2015/05/06")) .getResultList();
It produces the following SQL query of your interest (tested on the final version of Hibernate 4.3.6, but in this context there should be no discrepancies in the middle ORM structures).
SELECT cars0_.car_id AS car_id1_7_, cars0_.manufacturer AS manufact2_7_ FROM project.cars cars0_ WHERE cars0_.car_id NOT IN ( SELECT bookings1_.fk_car_id FROM project.bookings bookings1_ WHERE bookings1_.from_date<=? AND bookings1_.to_date>=? OR bookings1_.from_date<=? AND bookings1_.to_date>=? OR bookings1_.from_date>=? AND bookings1_.to_date<=? )
The brackets around conditional expressions in the WHERE above query are technically redundant, which are only necessary for better readability, which Hibernate ignores. Hibernate should not take them into account.
I personally, however, prefer to use the EXISTS statement. Accordingly, the request can be restored as follows.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<Cars> criteriaQuery = criteriaBuilder.createQuery(Cars.class); Metamodel metamodel = entityManager.getMetamodel(); Root<Cars> root = criteriaQuery.from(metamodel.entity(Cars.class)); Subquery<Long> subquery = criteriaQuery.subquery(Long.class); Root<Bookings> subRoot = subquery.from(metamodel.entity(Bookings.class)); subquery.select(criteriaBuilder.literal(1L)); List<Predicate> predicates = new ArrayList<Predicate>(); ParameterExpression<Date> fromDate1 = criteriaBuilder.parameter(Date.class); Predicate exp1 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate1); ParameterExpression<Date> toDate1 = criteriaBuilder.parameter(Date.class); Predicate exp2 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate1); Predicate and1 = criteriaBuilder.and(exp1, exp2); ParameterExpression<Date> fromDate2 = criteriaBuilder.parameter(Date.class); Predicate exp3 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate2); ParameterExpression<Date> toDate2 = criteriaBuilder.parameter(Date.class); Predicate exp4 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate2); Predicate and2 = criteriaBuilder.and(exp3, exp4); ParameterExpression<Date> fromDate3 = criteriaBuilder.parameter(Date.class); Predicate exp5 = criteriaBuilder.greaterThanOrEqualTo(subRoot.get(Bookings_.fromDate), fromDate3); ParameterExpression<Date> toDate3 = criteriaBuilder.parameter(Date.class); Predicate exp6 = criteriaBuilder.lessThanOrEqualTo(subRoot.get(Bookings_.toDate), toDate3); Predicate and3 = criteriaBuilder.and(exp5, exp6); Predicate equal = criteriaBuilder.equal(root, subRoot.get(Bookings_.fkCarId)); Predicate or = criteriaBuilder.or(and1, and2, and3); predicates.add(criteriaBuilder.and(or, equal)); subquery.where(predicates.toArray(new Predicate[0])); criteriaQuery.where(criteriaBuilder.exists(subquery).not()); List<Cars> list = entityManager.createQuery(criteriaQuery) .setParameter(fromDate1, new Date("2015/05/04")) .setParameter(toDate1, new Date("2015/05/04")) .setParameter(fromDate2, new Date("2015/05/06")) .setParameter(toDate2, new Date("2015/05/06")) .setParameter(fromDate3, new Date("2015/05/04")) .setParameter(toDate3, new Date("2015/05/06")) .getResultList();
It produces the following SQL query.
SELECT cars0_.car_id AS car_id1_7_, cars0_.manufacturer AS manufact2_7_ FROM project.cars cars0_ WHERE NOT (EXISTS (SELECT 1 FROM project.bookings bookings1_ WHERE (bookings1_.from_date<=? AND bookings1_.to_date>=? OR bookings1_.from_date<=? AND bookings1_.to_date>=? OR bookings1_.from_date>=? AND bookings1_.to_date<=?) AND cars0_.car_id=bookings1_.fk_car_id))
Returns the same list of results.
Additionally:
Here subquery.select(criteriaBuilder.literal(1L)); Using expressions like criteriaBuilder.literal(1L) in complex subquery statements on EclipseLink, EclipseLink gets confused and throws an exception. Therefore, perhaps this should be considered when writing complex subqueries in EclipseLink. Just select id in this case e.g.
subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId));
as in the first case. Note: in the generation of SQL queries, you will see odd behavior if you run the expression as described above in EclipseLink, although the list of results will be identical.
You can also use joins that are more efficient for server database systems, in which case you need to use DISTINCT to filter possible duplicate rows, since you need a list of results from the parent table. The list of results may contain duplicate rows if the detailed table contains more than one child row - bookings for the corresponding parent row cars . I leave it to you. :) This is how it happens.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<Cars> criteriaQuery = criteriaBuilder.createQuery(Cars.class); Metamodel metamodel = entityManager.getMetamodel(); Root<Cars> root = criteriaQuery.from(metamodel.entity(Cars.class)); criteriaQuery.select(root).distinct(true); ListJoin<Cars, Bookings> join = root.join(Cars_.bookingsList, JoinType.LEFT); ParameterExpression<Date> fromDate1 = criteriaBuilder.parameter(Date.class); Predicate exp1 = criteriaBuilder.lessThanOrEqualTo(join.get(Bookings_.fromDate), fromDate1); ParameterExpression<Date> toDate1 = criteriaBuilder.parameter(Date.class); Predicate exp2 = criteriaBuilder.greaterThanOrEqualTo(join.get(Bookings_.toDate), toDate1); Predicate and1 = criteriaBuilder.and(exp1, exp2); ParameterExpression<Date> fromDate2 = criteriaBuilder.parameter(Date.class); Predicate exp3 = criteriaBuilder.lessThanOrEqualTo(join.get(Bookings_.fromDate), fromDate2); ParameterExpression<Date> toDate2 = criteriaBuilder.parameter(Date.class); Predicate exp4 = criteriaBuilder.greaterThanOrEqualTo(join.get(Bookings_.toDate), toDate2); Predicate and2 = criteriaBuilder.and(exp3, exp4); ParameterExpression<Date> fromDate3 = criteriaBuilder.parameter(Date.class); Predicate exp5 = criteriaBuilder.greaterThanOrEqualTo(join.get(Bookings_.fromDate), fromDate3); ParameterExpression<Date> toDate3 = criteriaBuilder.parameter(Date.class); Predicate exp6 = criteriaBuilder.lessThanOrEqualTo(join.get(Bookings_.toDate), toDate3); Predicate and3 = criteriaBuilder.and(exp5, exp6); Predicate or = criteriaBuilder.not(criteriaBuilder.or(and1, and2, and3)); Predicate isNull = criteriaBuilder.or(criteriaBuilder.isNull(join.get(Bookings_.fkCarId))); criteriaQuery.where(criteriaBuilder.or(or, isNull)); List<Cars> list = entityManager.createQuery(criteriaQuery) .setParameter(fromDate1, new Date("2015/05/04")) .setParameter(toDate1, new Date("2015/05/04")) .setParameter(fromDate2, new Date("2015/05/06")) .setParameter(toDate2, new Date("2015/05/06")) .setParameter(fromDate3, new Date("2015/05/04")) .setParameter(toDate3, new Date("2015/05/06")) .getResultList();
It produces the following SQL query.
SELECT DISTINCT cars0_.car_id AS car_id1_7_, cars0_.manufacturer AS manufact2_7_ FROM project.cars cars0_ LEFT OUTER JOIN project.bookings bookingsli1_ ON cars0_.car_id=bookingsli1_.fk_car_id WHERE ( bookingsli1_.from_date>? OR bookingsli1_.to_date<? ) AND ( bookingsli1_.from_date>? OR bookingsli1_.to_date<? ) AND ( bookingsli1_.from_date<? OR bookingsli1_.to_date>? ) OR bookingsli1_.fk_car_id IS NULL
As you can see, the Hibernate provider accesses conditional statements in the WHERE in response to WHERE NOT(...) . Other providers can also generate exact WHERE NOT(...) , but in the end it is the same as the one written in the question and gives the same list of results as in the previous cases.
The correct connections are not indicated. Therefore, JPA providers should not implement them. Most of them do not support the correct connection.
Corresponding JPQL just for the sake of completeness :)
Request IN() :
SELECT c FROM cars AS c WHERE c.carid NOT IN (SELECT b.fkcarid.carid FROM bookings AS b WHERE b.fromdate <=? AND b.todate >=? OR b.fromdate <=? AND b.todate >=? OR b.fromdate >=? AND b.todate <=? )
Request EXISTS() :
SELECT c FROM cars AS c WHERE NOT ( EXISTS (SELECT 1 FROM bookings AS b WHERE ( b.fromdate <=? AND b.todate >=? OR b.fromdate <=? AND b.todate >=? OR b.fromdate >=? AND b.todate <=? ) AND c.carid = b.fkcarid) )
The last one that uses the left join (with named parameters):
SELECT DISTINCT c FROM Cars AS c LEFT JOIN c.bookingsList AS b WHERE NOT (b.fromDate <=:d1 AND b.toDate >=:d2 OR b.fromDate <=:d3 AND b.toDate >=:d4 OR b.fromDate >=:d5 AND b.toDate <=:d6) OR b.fkCarId IS NULL
All of the above JPQL statements can be run using the following method, as you already know.
List<Cars> list=entityManager.createQuery("Put any of the above statements", Cars.class) .setParameter("d1", new Date("2015/05/04")) .setParameter("d2", new Date("2015/05/04")) .setParameter("d3", new Date("2015/05/06")) .setParameter("d4", new Date("2015/05/06")) .setParameter("d5", new Date("2015/05/04")) .setParameter("d6", new Date("2015/05/06")) .getResultList();
Replace named parameters with appropriate indexed / positional parameters as necessary and required.
All these JPQL statements also generate identical SQL statements as those generated by the criteria API, as described above.
I have always avoided the IN() subqueries in such situations and especially during using MySQL. I would use IN() subqueries if and only if they are absolutely necessary for situations, for example, when we need to define a result set or delete a list of rows based on a list of static values, such as
SELECT * FROM table_name WHERE id IN (1, 2, 3, 4, 5);` DELETE FROM table_name WHERE id IN(1, 2, 3, 4, 5);
etc.
I always prefer queries using the EXISTS operator in such situations, since the list of results includes only one table, based on a condition in another table (s). joins in this case duplicate lines will be produced, as mentioned earlier, which must be filtered using DISTINCT , as shown in one of the queries above.
- I would prefer to use joins when the result set to be restored, joins from several database tables, should still be used as obvious.
In any case, it all depends on many things. This is not a milestone.
Disclaimer: I have very little knowledge of RDBMS.
Note: I used the parameterized / overloaded obsolete date constructor - Date(String s) for indexed / positional parameters associated with the SQL query in all cases for the pure purpose of testing, only to avoid all the jumble of java.util.SimpleDateFormat noise that you already you know. You can also use other better APIs such as Joda Time (Hibernate supports it), java.sql.* (These are subclasses of java.util.Date ), Java Time in Java 8 (most often they are not supported, unless configured) as and when required / necessary.
Hope this helps.