HQL中带<>不等条件的LEFT JOIN返回错误行的问题求解
HQL多表关联排除场景实现方案
原写法问题说明
原写法的核心缺陷:主查询做多层LEFT JOIN后,直接在WHERE层判断关联person id不等于目标值或为NULL,本质是对JOIN生成的笛卡尔积结果逐行过滤,只会剔除匹配目标person的单条关联行,不会整体排除对应Track实体。如果同一个Track同时关联了目标person和其他person,其他person对应的关联行依然满足WHERE条件,会导致该Track被错误返回。
通用实现方案
这类「只要关联路径存在任意符合条件的记录,就整体排除主表实体」的场景,优先用NOT EXISTS子查询实现,比NOT IN稳定性更好、性能更优,不会受关联表NULL值影响。
方案1:NOT EXISTS 写法(生产环境推荐)
两个NOT EXISTS子查询分别校验两个关联路径是否存在目标person的关联记录,只要任意一个子查询命中匹配记录,当前Track就会被排除,完全匹配需求:
// 注意使用命名参数传值,不要直接拼接字符串,避免SQL注入 String hql = "SELECT DISTINCT track FROM TrackEntity track " + "WHERE NOT EXISTS (" + " SELECT 1 FROM track.releaseRelations rr " + " JOIN rr.release.publisherProducerArtists ra " + " JOIN ra.person rap " + " WHERE rap.id = :personId" + ") " + "AND NOT EXISTS (" + " SELECT 1 FROM track.releaseRelations rr " + " JOIN rr.release.publisherProducerGroupCasts rgc " + " JOIN rgc.artistRelations rgcar " + " JOIN rgcar.artist.person rgcp " + " WHERE rgcp.id = :personId" + ")";
子查询里不需要写LEFT JOIN加IS NULL判断,因为子查询的目标是找「存在匹配的关联记录」,用INNER JOIN执行效率更高。
方案2:NOT IN 写法
NOT IN确实可以实现该需求,但必须搭配子查询使用:先查出所有关联了目标person的Track ID,再从全量Track中排除这部分ID。
String hql = "SELECT DISTINCT track FROM TrackEntity track " + "WHERE track.id NOT IN (" + " SELECT DISTINCT t.id FROM TrackEntity t " + " JOIN t.releaseRelations rr " + " JOIN rr.release.publisherProducerArtists ra " + " JOIN ra.person rap " + " WHERE rap.id = :personId" + ") " + "AND track.id NOT IN (" + " SELECT DISTINCT t.id FROM TrackEntity t " + " JOIN t.releaseRelations rr " + " JOIN rr.release.publisherProducerGroupCasts rgc " + " JOIN rgc.artistRelations rgcar " + " JOIN rgcar.artist.person rgcp " + " WHERE rgcp.id = :personId" + ")";
使用该写法需要注意:NOT IN后面子查询的返回结果绝对不能包含NULL值,否则整个查询的逻辑会完全失效、返回空结果,这也是生产环境更推荐用NOT EXISTS的核心原因。
场景通用规则
- 涉及关联表的「存在/不存在」判断逻辑,优先选择
EXISTS/NOT EXISTS子查询,不要在主查询上做多层LEFT JOIN后再在WHERE中过滤关联字段,避免出现结果重复、过滤不彻底的问题。 - HQL中传入动态参数一律使用命名参数(格式为
:参数名),不要直接把变量拼接到HQL字符串中,避免SQL注入风险和参数类型转换错误。
内容的提问来源于stack exchange,提问作者CrisV
相关产品推荐
相关产品推荐

