ОШИБКА Jooq: отсутствует запись в предложении FROM для таблицы для вложенного запроса (сумма и группировка по) - PullRequest
0 голосов
/ 16 декабря 2018

У меня есть следующий оператор jooq, выполненный на Postgres 9.6:

DSLContext context = DSL.using(connection, POSTGRES_10);
testSafesFundsAllocationRecord record = 
context.select()
       .from(
          select(
            TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID,       
            sum(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT)
               .as(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT))
         .from(TEST_SAFES_FUNDS_ALLOCATION)
         .groupBy(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID))
       .where(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT.greaterOrEqual(amount))
       .and(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID.notIn(excludedLedgers))
       .orderBy(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT.asc())
       .limit(1)
       .fetchOne()
       .into(TEST_SAFES_FUNDS_ALLOCATION);

Это переводится следующим образом:

SQL [
select "alias_88420990"."ledger_id", "alias_88420990"."amount" from (
select "public"."test_safes_funds_allocation"."ledger_id", 
sum("public"."test_safes_funds_allocation"."amount") as "amount" from "public"."test_safes_funds_allocation" group by "public"."test_safes_funds_allocation"."ledger_id") as "alias_88420990"
 where ("public"."test_safes_funds_allocation"."amount" >= ? and 1 = 1) order by "public"."test_safes_funds_allocation"."amount" asc limit ?]; 

excludeLedgers -> массив пустых строк

и результат:

org.postgresql.util.PSQLException: ERROR: missing FROM-clause entry for table "test_safes_funds_allocation"
  Position: 334

Может кто-нибудь сказать мне, в чем проблема в запросе?как вы видите, он вложенный ... но я не могу понять проблему.Запрос выполняет следующее: суммирует все строки суммы одного и того же идентификатора главной книги (group by), а затем из выходных данных возвращает минимальную строку, сумма которой превышает переменную сумму, мы можем исключить конкретный ledger_id через массив (этопусто в этом примере).

любая помощь будет принята с благодарностью,

С уважением

1 Ответ

0 голосов
/ 17 декабря 2018

Безымянные производные таблицы поддерживаются только в нескольких диалектах SQL (например, Oracle), но не во всех (например, не в PostgreSQL).Вот почему jOOQ генерирует вам псевдоним для каждой производной таблицы.Теперь, когда ваша производная таблица имеет псевдоним, вы больше не можете обращаться к ее столбцам, используя исходные имена таблиц, но вы должны использовать псевдоним вместо этого.Вы можете увидеть, где это пошло не так в вашем сгенерированном SQL-запросе:

select 
  "alias_88420990"."ledger_id", -- These are correctly referenced, because you used
  "alias_88420990"."amount"     -- select(), so jOOQ did the dereferencing for your
from (
  select 
    "public"."test_safes_funds_allocation"."ledger_id", 
    sum("public"."test_safes_funds_allocation"."amount") as "amount"
  from "public"."test_safes_funds_allocation" 
  group by "public"."test_safes_funds_allocation"."ledger_id"
) as "alias_88420990" -- This alias is generated by jOOQ

-- In these predicates, you're referencing the original column name with full qualification
-- when you should be referncing the column from alias_88420990 instead
where ("public"."test_safes_funds_allocation"."amount" >= ? and 1 = 1) 
order by "public"."test_safes_funds_allocation"."amount" asc 
limit ?

Каноническое исправление

Таким образом, ваш запрос jOOQ можно переписать так, чтобы получить правильные имена таблиц:

// Create a local variable to contain your subquery. Ideally, provide an explicit alias
Table<?> t = table(
    select(
        TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID,       
        sum(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT)
           .as(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT))
   .from(TEST_SAFES_FUNDS_ALLOCATION)
   .groupBy(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID)).as("t");

// Now, use t everywhere, instead of TEST_SAFES_FUNDS_ALLOCATION
context.select()
       .from(t)
       .where(t.field(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT).greaterOrEqual(amount))
       .and(t.field(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID).notIn(excludedLedgers))
       .orderBy(t.field(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT).asc())
       .limit(1)
       .fetchOne();

Я использую Table.field(Field) для извлечения поля из таблицы с псевдонимами без потери информации о типах.

Улучшенное исправление, использующее существующие типы таблиц

Учитывая, что у вашего производного есть два столбца, имена которых также указаны в исходной таблице, вы можете использовать "хитрость", чтобы повысить безопасность типов из API jOOQ и, таким образом, более удобный способ разыменования столбцов.Псевдоним таблицы сначала:

// This t reference now has all the column references like the original table
TestSafesFundsAllocation t = TEST_SAFES_FUNDS_ALLOCATION.as("t");

// The subquery is also named "t", but has a different definition
Table<?> subquery = table(
    select(
        TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID,       
        sum(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT)
           .as(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT))
   .from(TEST_SAFES_FUNDS_ALLOCATION)
   .groupBy(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID)).as(t);

// Now, select again from the subquery, but dereference columns from the aliased table t
context.select()
       .from(subquery)
       .where(t.AMOUNT.greaterOrEqual(amount))
       .and(t.LEDGER_ID.notIn(excludedLedgers))
       .orderBy(t.AMOUNT.asc())
       .limit(1)
       .fetchOne();

Помните, что это хитрость, которая работает только до тех пор, пока столбцы производной таблицы имеют те же имена и типы, что и исходная таблица, из которой выбирается производная таблица.

Переписать ваш SQL

Производные таблицы (и общие выражения таблиц) - это область, в которой DSL в jOOQ не так мощен, как собственный SQL, поскольку jOOQ не может легко проверить тип производной таблицы обычным способом.Вот почему необходимо использовать локальные переменные и небезопасную разыменование.

Часто это предостережение является достаточно веской причиной, чтобы полностью избегать производных таблиц, если это вариант.В вашем случае вам не нужна производная таблица.Лучший запрос (даже лучше в нативном SQL) будет выглядеть так:

context.select(
          TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID, 
          sum(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT).as(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT))
       .from(TEST_SAFES_FUNDS_ALLOCATION)
       .where(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID.notIn(excludedLedgers))
       .groupBy(TEST_SAFES_FUNDS_ALLOCATION.LEDGER_ID)
       .having(sum(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT).greaterOrEqual(amount))
       .orderBy(sum(TEST_SAFES_FUNDS_ALLOCATION.AMOUNT).asc())
       .limit(1)
       .fetchOne()
...