JPA Выбор на основе совпадения элемента коллекции или пустой коллекции - PullRequest
1 голос
/ 18 мая 2011

У меня есть Unit, который имеет UnitType и организацию. У меня есть Контракт, в котором есть коллекция UnitTypes и Организация. Я хотел бы выбрать модуль и либо контракт, в котором имеется UnitType модуля, либо ИЛИ UnitType с пустой коллекцией UnitType, если совпадения нет.

Чтобы уточнить, в каждом случае я хочу выбрать Единицу. Если существует Контракт с указанным типом модуля в коллекции контрактов UnitTypes, то я бы хотел выбрать этот контракт. Если такого контракта не существует, я бы хотел выбрать тот Контракт, в котором нет UnitTypes. Другими словами, я хотел бы, чтобы контракт, который применяется к типу этого юнита, но если он не существует, я возьму контракт, который является независимым от типа юнита, по умолчанию.

Пример 1:

Unit A has type XYZ.
Contract B has types [ ABC, DEF ]
Contract C has types []

в этом случае я бы выбрал единицу и Контракт C, потому что B не соответствует типу.

Пример 2:

Unit A has type XYZ
Contract B has types [XYZ, ABC]
Contract C has types []

В этом случае я бы выбрал Контракт B, поскольку он соответствует типу Юнита.

Следующий запрос работает для примера 2, но не для примера 1.

SELECT NEW mypackage.view.MyAggregateView( 
    u
    , MAX(sc.serviceDate)
    , c.survey.key )
FROM Contract c
    , Unit u 
    LEFT JOIN u.serviceCalls sc 
    WHERE c.organization.key = u.organization.key 
    AND u.organization.key = :organizationKey 
    AND ((u.unitType MEMBER OF c.unitTypes) 
        OR (c.unitTypes IS EMPTY))
    GROUP BY u, c.survey.key

Как мне выполнить эту работу в обоих случаях и убедиться, что я получаю правильный контракт?

Еще один пример:

Я недавно столкнулся с этим снова. У меня есть регион, в котором есть коллекция почтовых индексов и, при желании, коллекция организаций. У меня также есть подразделение, которое имеет 1-1 для организации и имеет один почтовый индекс. Я хочу получить все соответствующие единицы в почтовых индексах региона. Если в регионе нет организаций, я должен получить все единицы в почтовых индексах, в противном случае я должен получить только те единицы, в которых есть организация, соответствующая одной из организаций, указанных в этом регионе.

Вот мой запрос

Select u.key, u.organization.key
from Unit u, Region r
where r.key = -1
and u.address.postalCode member of r.zips
and r.organizations is empty

Этот запрос возвращает мне все ожидаемые результаты. Следующий запрос, который никоим образом не должен ограничивать набор результатов, так как он только добавляет OR, не дает мне никаких результатов.

Select u.key, u.organization.key
from Unit u, Region r
where r.key = -1
and u.address.postalCode member of r.zips
and ((r.organizations is empty) OR (r.organizations is not empty and u.organization member of r.organizations))

Я использую ссылку eclipse 2.0.1 против postgres 9. Я также получил тот же результат с eclipselink 2.2.0.

1 Ответ

1 голос
/ 27 ноября 2011

Ваша основная проблема заключается в том, что "из Юнита u, Контракта c ... где c.organization.key = u.organization.key" является внутренним соединением между Юнитом и Контрактом. По определению, это никогда не вернет результат, если нет соответствующего контракта. Строки удаляются из результирующего набора до того, как «или c.unitTypes пуст», даже половина условия даже может сработать.

Существует еще одна более тонкая проблема, заключающаяся в том, что, если у вас есть несколько контрактов, ссылающихся на один и тот же тип, вы можете получить дубликаты в своем запросе. Однако этого объединения нельзя избежать, поскольку вы пытаетесь получить как контракты, так и единицы, а не только единицы. (В противном случае вы можете использовать существует / не существует вместо объединений.)

Теперь, похоже, вы действительно не можете выполнить логику, которую хотите, с помощью одного соединения. Условная логика, такая как «взять что-то, если оно существует, иначе взять что-то другое, , но не оба », потребует нескольких соединений, а затем логики case / when, чтобы выбрать, какое из них использовать.

В этот момент я бы подумал, что вам будет лучше с двумя отдельными запросами. В зависимости от общего случая и общей архитектуры вашего приложения, ваша производительность может быть даже лучше, если вы выполняете два простых запроса и выполняете два обхода, а не сложный запрос, чтобы избежать обхода. Даже если у вас небольшой удар по производительности, я бы предпочел это для удобочитаемости и удобства обслуживания.

Тем не менее, если бы мне пришлось делать это в SQL, и это обязательно должно было быть в одном запросе, это было бы выполнимо, но совсем не красиво:

  select u.*, c.* from (
    select unit_id, 
         coalesce(matching.contract_id, non_matching.contract_id) 
         as contract_id
      from Unit u
        **left** join Contract matching on ([match on unit type]) 
        left join Contract non_matching on ([unit types empty])
   ) subquery join Unit u on subquery.unit_id = u.unit_id 
     join Contract c on subquery.contract_id = c.contract_id

Либо так, либо объедините два запроса вместе с UNION ALL, и остановите после возврата первого результата.

Однако ни один из этих вариантов не переводится в JPA / JPQL. В JPQL нет оператора UNION, а JPQL не поддерживает внешние объединения по произвольным критериям, только для навигационных отношений, как вы это делали с «left join u.serviceCalls». Я не думаю, что вы можете превратить декартово «из контракта с, юнит u» во внешнее соединение.

Так что для JPQL мне грустно говорить, что нет хорошего способа получить то, что вы хотите, в одном запросе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...