Utworzyłem następujące dwie tabele w bazie danych MySQL z tylko niezbędnymi polami.
mysql> desc cars;
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| car_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| manufacturer | varchar(100) | YES | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
2 rows in set (0.03 sec)
mysql> desc bookings;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| booking_id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| fk_car_id | bigint(20) unsigned | NO | MUL | NULL | |
| from_date | date | YES | | NULL | |
| to_date | date | YES | | NULL | |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
booking_id
w tabeli bookings
jest kluczem podstawowym i fk_car_id
jest klucz obcy, która odwołuje się do klucza podstawowego (car_id
) tabeli cars
.
Odpowiednie JPA kryteria zapytania przy użyciu IN()
sub-zapytanie idzie jak poniżej.
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();
Produkuje następujące zapytanie SQL zainteresowanie (testowane na Hibernate 4.3.6 final, ale nie powinno być żadnych rozbieżności średnio ram ORM w tym kontekście).
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<=?
)
nawiasy wokół wyrażeń warunkowych w klauzuli powyższego zapytania WHERE
są technicznie całkowicie zbędne, które są potrzebne tylko dla lepszej dokładności odczytu, który zignoruje Hibernate - Hibernate nie musi brać je pod uwagę.
Osobiście jednak wolę używać operatora EXISTS
. W związku z tym kwerendę można zrekonstruować w następujący sposób.
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();
Generuje następujące zapytanie SQL.
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))
Która zwraca tę samą listę wyników.
dodatkowe:
tutaj subquery.select(criteriaBuilder.literal(1L));
, przy użyciu wyrażeń takich jak criteriaBuilder.literal(1L)
w złożonych sprawozdania sub-zapytanie o EclipseLink, EclipseLink gets confused i powoduje wyjątek. Dlatego może być konieczne wzięcie pod uwagę podczas pisania złożonych pod-zapytań w EclipseLink. Wystarczy wybrać id
w tym przypadku jak
subquery.select(subRoot.get(Bookings_.fkCarId).get(Cars_.carId));
jak w pierwszym przypadku. Uwaga: Pojawi się odd behaviour w generowaniu zapytań SQL, jeśli uruchomisz wyrażenie jak powyżej na EclipseLink, chociaż lista wyników będzie identyczna.
Możesz również użyć sprzężeń, które okazują się bardziej wydajne w systemach baz danych z zaplecza. W takim przypadku musisz użyć DISTINCT
, aby odfiltrować możliwe duplikaty wierszy, ponieważ potrzebujesz listy wyników z tabeli nadrzędnej. Lista wyników może zawierać zduplikowane wiersze, jeśli istnieje więcej niż jeden wiersz podrzędny w szczegółowej tabeli - bookings
dla odpowiedniego wiersza nadrzędnego cars
. Zostawiam to Tobie. :) Tak to się dzieje tutaj.
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();
Generuje następujące zapytanie SQL.
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
Jak można zauważyć dostawca Hibernacja odwraca warunkowe oświadczeń w klauzuli w odpowiedzi na WHERE NOT(...)
WHERE
. Inni dostawcy mogą również wygenerować dokładną wartość: WHERE NOT(...)
, ale ostatecznie jest to ta sama, co w pytaniu i daje taką samą listę wyników, jak w poprzednich przypadkach.
Prawe sprzężenia nie są określone. Dlatego dostawcy JPA nie muszą ich wdrażać. Większość z nich nie obsługuje właściwych połączeń.
odpowiednich JPQL tylko przez wzgląd na kompletność :)
IN()
zapytania:
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 <=?)
EXISTS()
zapytania:
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))
Ostatnim, który używa left join (z nazwanymi parametrami):
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
Wszystkie powyższe instrukcje JPQL można uruchomić przy użyciu poniższej metody, jak już wiesz.
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();
Wymień nazwane parametry na odpowiednie parametry indeksowane/pozycyjne w razie potrzeby/wymagane.
Wszystkie te instrukcje JPQL generują również identyczne instrukcje SQL, jak wygenerowane przez API kryteriów, jak wyżej.
ja zawsze unikać IN()
sub-zapytań w takich sytuacjach i szczególnie podczas MySQL. użyłbym IN()
sub-zapytań wtedy i tylko wtedy, gdy są one absolutnie potrzebne w sytuacjach takich jak, kiedy musimy określić wynik ustawić lub usunąć listę wierszy w oparciu o listy wartości statycznych takich jak
SELECT * FROM table_name WHERE id IN (1, 2, 3, 4, 5);`
DELETE FROM table_name WHERE id IN(1, 2, 3, 4, 5);
i podobne.
W takich sytuacjach wolałbym zawsze używać zapytań z użyciem operatora EXISTS
, ponieważ lista wyników obejmuje tylko jedną tabelę na podstawie warunku w innej tabeli (tabelach). Łączy się z w tym przypadku będzie produkować zduplikowane wiersze, jak wspomniano wcześniej, które należy odfiltrować przy użyciu DISTINCT
, jak pokazano w jednym z powyższych zapytań.
- Preferowałbym użycie złącz, gdy zestaw wyników do pobrania jest połączony z wielu tabel bazy danych - muszą być używane tak czy inaczej oczywiste.
Wszystko zależy od wielu rzeczy. To wcale nie są kamienie milowe.
Oświadczenie: Mam bardzo małą wiedzę na temat RDBMS.
Uwaga: Użyłem parametryczne/przeciążony przestarzałe datę konstruktora - Date(String s)
dla indeksowanych/pozycyjny parametrów związanych z kwerendy SQL we wszystkich przypadkach do czystego celu testowania tylko uniknąć cały bałagan java.util.SimpleDateFormat
hałasu, który już znasz.Możesz również użyć innych lepszych interfejsów API, takich jak Czas Jody (Hibernate obsługuje go), java.sql.*
(są to podklasy java.util.Date
), Czas Java w Javie 8 (w większości przypadków nie jest obsługiwany, chyba że dostosowany) w razie potrzeby/potrzeby .
Nadzieję, że pomaga.
Należy zwrócić uwagę na to, że twoje warunki datowe są sumowane do "fromDate> = '2015-05-04' AND toDate <= '2015-05-06''. – RealSkeptic
Dzięki, będę go edytować. Powinien to być dynamiczny zakres czasu. Jeśli samochód jest rezerwowany od 2015-05-05 do 2015-05-07, nie należy rezerwować go, gdy data rozpoczęcia, data zakończenia lub oba są sprzeczne z istniejącą rezerwacją. –