【问题标题】:JPA Select based on collection element match, or empty collection基于集合元素匹配或空集合的 JPA 选择
【发布时间】:2011-05-18 05:22:54
【问题描述】:

我有一个 Unit,它有一个 UnitType 和一个 Organization。我有一个包含 UnitTypes 和一个组织集合的合同。我想选择单位,以及在其集合中包含 Unit 的 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))

我在 postgres 9 上使用 eclipse link 2.0.1。我也使用 eclipselink 2.2.0 得到了相同的结果。

【问题讨论】:

  • 你能试着解释一下这些例子吗?我无法弄清楚您要在这两个示例中的每一个上搜索什么,但您想要的结果很清楚。
  • 添加了说明。如果您需要更多信息,请告诉我。
  • 你的模型是这样的吗:Unit有\@ManyToOne unitType和\@ManyToOne组织;合同有 \@ManyToOne 组织和 \@OneToMany unitType(或 \@ManyToMany?)。我理解正确吗?

标签: java jpa jpa-2.0 jpql


【解决方案1】:

您的主要问题是“来自 Unit u, Contract c ... where c.organization.key = u.organization.key”是 Unit 和 Contract 之间的inner join。根据定义,如果没有匹配的合约,这将永远不会返回结果。在“或 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”所做的那样。我认为您不能将笛卡尔“来自合同 c,单位 u”的笛卡尔坐标转换为外部联接。

所以对于 JPQL,我很遗憾地说没有好方法可以在单个查询中获得您想要的内容。

【讨论】:

  • 我将在明天晚上使用这个解决方案。感谢您的回答,我会告诉您情况如何。
  • 这个问题有一个类似的查询结构,用coalesce/left join来实现默认逻辑:stackoverflow.com/questions/8290033/…
  • 关于您在回答中指出的第一个问题:contract.organization 和 unit.organization 的内部连接是有意的。总会有一个具有匹配组织的合同,所以我们绝对应该进入我们检查单元类型与合同类型的部分。不过,您答案的第二部分对于提供观点非常有帮助。我再给它几天时间看看是否有人插话。感谢您抽出时间来回答。
  • 可以贴出生成的SQL吗?我认为“成员”也可能会生成隐藏的内部连接——尽管它可能是特定于 JPA 提供程序的。这也可以解释为什么在您的示例 2 中,您的查询没有返回 both 合同 B 和 C,每个合同都匹配“OR”条件的不同部分。
  • 嗯,示例 2 的要点很好。我绝对不希望它返回 B 和 C,但如果它工作正常,它应该。这只是我应该进行 2 个单独查询的另一个原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 2012-11-24
  • 2020-11-27
  • 2020-08-08
  • 2017-09-15
相关资源
最近更新 更多