Ограничение API критериев приводит к подзапросу

Я пытаюсь написать запрос, похожий на

select * from Table a
 where a.parent_id in 
  (select b.id from Table b
   where b.state_cd = ?
   and rownum < 100)

с помощью Criteria API. Я могу выполнить запрос без ограничения rownum для штрафа подзапроса, используя код, аналогичный https://stackoverflow.com/a/4668015/597419, но я не могу понять, как установить ограничение на _ 2_


person Danny    schedule 12.05.2016    source источник
comment
Если вы не можете понять, как ограничить подзапрос, как насчет использования соединения?   -  person ASA    schedule 12.05.2016
comment
@Traubenfuchs В запросе должно быть более 100 результатов. Подумайте о подзапросе как о родительских записях и о результатах, которые я хочу вернуть в качестве дочерних. У некоторых родителей несколько детей. Я не понимаю, как я смогу найти детей для первых 100 родителей с помощью объединения.   -  person Danny    schedule 12.05.2016
comment
Предложение where является динамическим и обычно занимает больше, чем только первые X строк в таблице. Я должен был упомянуть об этом в вопросе.   -  person Danny    schedule 18.05.2016
comment
Это возможно с вашей собственной реализацией интерфейса выражения docs.oracle.com/javaee/7/api/javax/persistence/criteria/   -  person user3624390    schedule 04.08.2018


Ответы (2)


В Hibernate вы можете добавить фактическое ограничение SQL, но стоит отметить, что это будет специфично для Oracle. Если вы перейдете на PostgreSQL, это сломается, и вместо этого вам понадобится LIMIT 100.

DetachedCriteria criteria = DetachedCriteria.forClass(Domain.class)
   .add(Restrictions.sqlRestriction("rownum < 100"));

В JPA API краткий ответ заключается в том, что вы не можете ... В своем вопросе вы предложили использовать API критериев (вместе с подзапросом). Однако только когда вы действительно вызовете EntityManager.createQuery(criteriaQuery), вы получите TypedQuery, где вы можете указать значение maxResult.

Тем не менее, вы можете разбить его на 2 запроса: первый, где вы получаете результаты внутреннего выбора (максимум 100), а затем второй Criteria, где вы берете результирующий список в in():

// inner query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<YourClass> innerCriteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = innerCriteriaQuery.from(YourClass.class);

innerCriteriaQuery.select(yourClass).where(
    cb.equal(yourClass.get(YourClass_.stateCode), someStateValue));

// list of 100 parent ids
List<YourClass> list = em.createQuery(innerCriteriaQuery).setMaxResults(100).getResultList();

// outer query
CriteriaQuery<YourClass> criteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = criteriaQuery.from(YourClass.class);

criteriaQuery.select(yourClass).where(
    cb.in(yourClass.get(YourClass_.parentId)).value(list);

return em.createQuery(criteriaQuery).getResultList();
person Dean Clark    schedule 18.05.2016
comment
Здесь используется API Hibernate, а не Criteria API - person Danny; 18.05.2016
comment
Надзор с моей стороны ... также добавил информацию об API. К сожалению, это невозможно сделать с помощью одного Criteria. - person Dean Clark; 19.05.2016
comment
Это полезно, но все же не идеальный ответ для меня. Бывают случаи, когда этот список может содержать более 1000 элементов, но, к сожалению, Oracle имеет ограничение в 1000 элементов в предложении in. Я все еще не вижу в API ничего, что позволило бы мне использовать rownum в подзапросе, поэтому мне, вероятно, придется пойти по этому пути и выполнить несколько запросов, ограничивая каждый до 1000 элементов. - person Danny; 19.05.2016
comment
Мне неясно ваше экономическое обоснование, поэтому, возможно, вам поможет более подробная информация. Тем не менее, попытка ограничить размер, если ваш внутренний выбор кажется невозможным с чистым JPA ... Так есть ли конкретная причина, по которой вы ограничиваете свой набор результатов? Возможно, сработала бы стратегия разбиения по страницам, если бы вы могли перемещать свой первый результат и максимальные результаты (query.setFirstResult(offset).setMaxResults(max)), чтобы вы могли получать результаты по частям? - person Dean Clark; 24.05.2016
comment
Я показываю пользователю таблицу, в которой отображаются все родительские записи (подзапрос). Один из столбцов в этих таблицах также является списком дочерних записей, связанных с родительским (подзапрос, который я пытаюсь ограничить), который пользователь может щелкнуть, чтобы просмотреть дополнительные сведения. В зависимости от производительности мы хотим ограничить эти запросы максимальным количеством записей для отображения примерно (~ 2000 родительских записей). Этот предел будет контролироваться нами через свойство, но мы никогда не захотим отображать все записи. - person Danny; 25.05.2016
comment
Этот запрос может генерировать слишком большой IN (...), некоторые базы данных имеют ограничение на предложение IN. - person John John Pichler; 30.11.2016
comment
Существует открытый запрос JPA для добавления поддержки для этого: github.com/javaee/jpa- spec / issues / 88 - person shelley; 01.08.2018
comment
Java EE перемещен в Eclipse; вот обновленный открытый билет JPA: github.com/eclipse-ee4j/jpa- api / issues / 88 - person shelley; 19.12.2018

Для этого нет решения JPA Criteria. Вы можете использовать настраиваемую функцию SQL, которая запускается во время генерации SQL-запроса. Все провайдеры JPA так или иначе поддерживают что-то подобное.

Если вы не хотите реализовывать это самостоятельно или даже хотите иметь подходящий API для построения таких запросов, я могу только порекомендовать вам реализованную мной библиотеку под названием Blaze-Persistence.

Вот документация, демонстрирующая вариант использования ограничения / смещения с подзапросами: https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#pagination.

Ваш запрос может выглядеть так с API построителя запросов:

criteriaBuilderFactory.create(entityManager, SomeEntity.class)
  .where("id").in()
    .select("subEntity.id")
    .from(SomeEntity.class, "subEntity")
    .where("subEntity.state").eq(someValue)
    .orderByAsc("subEntity.id")
    .setMaxResults(100)
  .end()

По сути, это сводится к использованию LIMIT функции SQL, зарегистрированной Blaze-Persistence. Поэтому, когда вы загружаете Blaze-Persistence с помощью EntityManagerFactory, вы даже можете использовать его вот так

entityManager.createQuery(
    "select * from SomeEntity where id IN(LIMIT((" +
    "  select id " +
    "  from SomeEntity subEntity " +
    "  where subEntity.state = :someParam " +
    "  order by subEntity.id asc" +
    "),1)) "
)

или что-то вроде

criteriaQuery.where(
   cb.in(yourClass.get(YourClass_.parentId)).value(cb.function("LIMIT", subquery));

Если вы используете EclipseLink, соглашение о вызове таких функций выглядит как OPERATOR('LIMIT', ...).

person Christian Beikov    schedule 03.08.2018