Jooq 错误:嵌套查询的表缺少 FROM 子句条目(求和和分组依据)

Roi*_*eck 5 java sql postgresql jooq postgresql-9.5

我在 Postgres 9.6 上执行了以下 jooq 语句:

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);
Run Code Online (Sandbox Code Playgroud)

其翻译如下:

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 ?]; 
Run Code Online (Sandbox Code Playgroud)

exceptedLedgers -> 是一个空字符串数组

结果是:

org.postgresql.util.PSQLException: ERROR: missing FROM-clause entry for table "test_safes_funds_allocation"
  Position: 334
Run Code Online (Sandbox Code Playgroud)

有人可以告诉我查询中有什么问题吗?正如你所看到的,它是嵌套的......但我似乎无法理解这个问题。查询执行以下操作:将同一分类帐 id(分组依据)的所有金额行相加,然后从输出中返回金额大于可变金额的最小行,我们可以通过数组排除特定的行ledger_id(在此为空)例子)。

任何帮助将不胜感激,

问候

Luk*_*der 4

未命名的派生表仅在少数 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 ?
Run Code Online (Sandbox Code Playgroud)

规范修复

因此,您的 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();
Run Code Online (Sandbox Code Playgroud)

我用来Table.field(Field)从别名表中提取字段,而不会丢失类型信息。

改进修复,利用现有表类型

鉴于您的派生有两列,其名称也出现在原始表中,您可以使用“技巧”从 jOOQ API 中获得更多类型安全性,从而获得更方便的取消引用列的方法。首先为表命名:

// 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();
Run Code Online (Sandbox Code Playgroud)

请记住,这是一个技巧,只有当派生表的列与派生表从中选择的原始表具有相同的名称和类型时,该技巧才有效。

重写你的SQL

派生表(和公共表表达式)是 jOOQ 的 DSL 并不像本机 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()
Run Code Online (Sandbox Code Playgroud)