【问题标题】:QueryDSL filtering with a label table使用标签表进行 QueryDSL 过滤
【发布时间】:2021-04-17 06:54:51
【问题描述】:

我有一个表格,它使用标签作为过滤器来查询网格视图。

架构:

项目: id, col_a

标签: ID、名称、类型

label_project: id、label_id、project_id

我的问题是我想获取所有带有用户正在使用的标签的项目记录,但对于某些标签需要做 OR,

这是查询需要执行的工作示例:

SELECT  DISTINCT gd.*
    FROM  project p
    JOIN  label_project lp1  ON lp1.label_id=306
    JOIN  label_project lp2  ON lp2.label_id=135
    JOIN  label_project lp3  ON lp3.label_id=285
    JOIN  label_project lp4  ON lp4.label_id=173
    WHERE  (      lp1.project_id=p.id
              OR  lp2.project_id=p.id
           ) -- labels of lp1  and  lp2 have the same type
      AND  lp3.project_id=p.id
      AND  lp4.project_id=p.id;
            -- labels of (lp1, lp2), lp3 and lp4 have different types

假设有 6 个标签“类型”,对于相同类型的标签,需要在它们之间进行 OR(参见查询中的第一个 where 子句),其余使用 AND(参见 where 子句的其余部分)

示例查询的问题在于它在 QueryDSL 中的显示非常明显,单个查询大约需要 10 秒。我读这主要是因为查询使用了 distinct。

有人知道用 QueryDSL 编写这个查询的方法吗?或者在 SQL 中

添加标签过滤前查询:

        query.distinct().from(PROJECT)
                        .leftJoin(FAVORITE_PROJECT)
                        .on(PROJECT.eq(FAVORITE_PROJECT.project).and(FAVORITE_PROJECT.employee.eq(employee)))
                        .where(ProjectService.restrictedProjectWhereClause(context.getEmployee()));
    }

    /**
     * Returns a predicate that filters out results of restricted projects where the employee has no rights for
     * @param employee The logged in employee
     * @return The predicate
     */
    public static Predicate restrictedProjectWhereClause(Employee employee) {
        return PROJECT.restricted.isFalse()
                .or(PROJECT.restricted.isTrue()
                        .and(PROJECT.employee.eq(employee)
                                .or(PROJECT.leaderEmployee.eq(employee)
                                        .or(PROJECT.managerEmployee.eq(employee)
                                                .or(hasRestrictedRoleAccess(employee).exists())))));
    }

    private static JPQLQuery<Integer> hasRestrictedRoleAccess(Employee employee) {
        return JPAExpressions.selectFrom(USER_SECURITY_ROLE)
                .join(USER)
                .on(USER_SECURITY_ROLE.user.eq(USER))
                .join(EMPLOYEE)
                .on(USER_SECURITY_ROLE.user.eq(EMPLOYEE.user))
                .where(USER_SECURITY_ROLE.securityRole.in(ESecurityRole.RESTRICTED_SECURITY_ROLES)
                        .and(EMPLOYEE.eq(employee)))
                .select(USER_SECURITY_ROLE.id);
    }

如何将标签过滤添加到 QueryDSL 中的查询中:

        // First add necessary joins
        for (int i = 0; i < labels.size(); i++) {
            QLabelProject lp = new QLabelProject(String.format("lp%d", i));
            labelMap.computeIfAbsent(labels.get(i).getSystemLabelType(), k -> new HashMap<>());
            labelMap.get(labels.get(i).getSystemLabelType()).put(labels.get(i), lp);

            query = query.join(lp)
                    .on(lp.project.eq(qProject));
        }

        // Decide where clause
        BooleanExpression expression = null;
        for (Map.Entry<ESystemLabelType, Map<Label, QLabelProject>> entry : labelMap.entrySet()) {
            BooleanExpression subExpression = null;
            for (Map.Entry<Label, QLabelProject> lp : entry.getValue().entrySet()) {
                if (entry.getKey() == null) {
                    subExpression = subExpression == null ? lp.getValue().label.id.eq(lp.getKey().getId()) :
                            subExpression.and(lp.getValue().label.id.eq(lp.getKey().getId()));
                } else {
                    subExpression = subExpression == null ? lp.getValue().label.id.eq(lp.getKey().getId()) :
                            subExpression.or(lp.getValue().label.id.eq(lp.getKey().getId()));
                }
            }
            expression = expression == null ? (BooleanExpression)new BooleanBuilder().and(subExpression).getValue() :
                    expression.and(subExpression);
        }

【问题讨论】:

  • QueryDSL 生成的 SQL 查询与底层 SQL 查询一样快。所以如果你有一个高性能的 SQL 查询,它应该很容易转换为 QueryDSL。如果 SQL 查询甚至没有性能,QueryDSL 也不会让它更快......你是如何将 SQL 片段转换为 QueryDSL 的?
  • @Jan-WillemGmeligMeyling 嗨,我已经用 QueryDSL 代码更新了我的帖子。尽管根据我的经验,SQL 查询比 QueryDSL 代码快得多。我应该提到,在添加标签过滤器之前,在代码中添加了一些其他连接和 where 子句。所以我想那里有区别
  • 所以您是说另一个查询比您与我们共享的查询慢?您希望我们在这里做什么 ;-) 我们需要完整的查询来提供一些相关的 SQL 优化。
  • 请重新排列查询,以便 ON 子句说明表是如何相关的 (lgd4.grid_data_id=gd.id) 并且 WHERE 子句执行过滤 (lgd2.label_id=135) .这样做时,您可能会发现 cmets 不同意该代码。之后,我可能对如何加快查询有一些想法。
  • @Jan-WillemGmeligMeyling 是的,我的错...我再次更新了帖子,显示了在添加标签过滤之前的查询。

标签: java mysql performance querydsl


【解决方案1】:

我不太明白你想要达到什么目的,但看看这样的事情是否可行:

SELECT gd.*
    FROM  grid_data gd
    JOIN label_grid_data AS lgd ON lgd.grid_data_id = gd.id
    WHERE lgd.label_id IN (285, 173, 306, 135)

WHERE 子句可能需要更复杂一些,但我怀疑您并不真的需要所有这些子查询。

另一种方法:

( SELECT grid_data_id FROM label_grid_data
      WHERE label_id IN (285, 173)   -- "OR"
)
UNION ALL
( SELECT grid_data_id FROM label_grid_data
      WHERE label_id IN (306, 135)
      HAVING COUNT(*) = 2            -- kludge to achieve "AND"
)

然后

SELECT gd.*
    FROM ( the above union ) AS lgd
    JOIN grid_data gd  ON gd.id = lgd.grid_data_id

这将为您提供具有 285 或 277 或同时具有 406 和 135 的行。

那么,请提供SHOW CREATE TABLE,以便我们提供最佳INDEXes 建议。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-23
    • 2018-04-13
    • 2015-02-19
    • 2019-01-29
    • 1970-01-01
    • 2021-12-17
    • 2019-04-24
    • 1970-01-01
    相关资源
    最近更新 更多