You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JOOQ多表关联查询中生成唯一学校列表

问题描述

我有三张表:SchoolTable、SchoolOrgTable和SchoolDetailsTable,表间关系如下:

  • SchoolTable与SchoolOrgTable为一对多关系。
  • SchoolOrgTable与SchoolDetailsTable为多对一关系。

当前使用的JOOQ查询语句:

SelectJoinStep<Record> result = dsl.select()
    .from(SchoolTable)
    .join(SchoolOrgTable)
    .on(SchoolTable.A_ID.eq(SchoolOrgTable.ATest_ID))
    .leftJoin(SchoolOrgTable)
    .on(SchoolTable.A_ID.eq(SchoolOrgTable.B_Id))
    .leftJoin(SchoolDetailsTable)
    .on(SchoolDetailsTable.C_ID.eq(SchoolOrgTable.B_ID));

当前查询结果(存在冗余)

[
  {
    "schoolId": 1,
    "schoolName": "JaySchool",
    "isActive": true,
    "SchoolDetails": [
      {
        "detailsID": 1,
        "detailsName": "Test",
        "details": "schoolIsGood"
      }
    ]
  },
  {
    "schoolId": 1,
    "schoolName": "JaySchool",
    "isActive": true,
    "SchoolDetails": [
      {
        "detailsID": 2,
        "detailsName": "Test1",
        "details": "awesome"
      }
    ]
  },
  {
    "schoolId": 2,
    "schoolName": "TermSchool",
    "isActive": true,
    "SchoolDetails": [
      {
        "detailsID": 3,
        "detailsName": "Test",
        "details": "Nice"
      }
    ]
  }
]

期望结果(聚合SchoolDetails)

[
  {
    "schoolId": 1,
    "schoolName": "JaySchool",
    "isActive": true,
    "SchoolDetails": [
      {
        "detailsID": 1,
        "detailsName": "Test",
        "details": "schoolIsGood"
      },
      {
        "detailsID": 2,
        "detailsName": "Test1",
        "details": "awesome"
      }
    ]
  },
  {
    "schoolId": 2,
    "schoolName": "TermSchool",
    "isActive": true,
    "SchoolDetails": [
      {
        "detailsID": 3,
        "detailsName": "test3",
        "details": "Nice"
      }
    ]
  }
]
解决方案

1. 修正JOIN逻辑

你的查询重复关联了SchoolOrgTable,这会直接导致数据膨胀。先调整关联逻辑,通过SchoolOrgTable一次性关联SchoolTable和SchoolDetailsTable:

SelectJoinStep<Record> result = dsl.select()
    .from(SchoolTable)
    .join(SchoolOrgTable)
    .on(SchoolTable.A_ID.eq(SchoolOrgTable.ATest_ID))
    .leftJoin(SchoolDetailsTable)
    .on(SchoolDetailsTable.C_ID.eq(SchoolOrgTable.B_ID));

2. 客户端聚合(用JOOQ fetchGroups)

如果要将同一学校的SchoolDetails聚合到列表中,可以使用JOOQ的fetchGroups方法按学校主键分组,再手动组装目标结构:

// 按学校记录分组,对应其所有详情记录
Map<Record, List<Record>> grouped = result.fetchGroups(
    r -> r.into(SchoolTable),
    r -> r.into(SchoolDetailsTable)
);

// 转换为期望的JSON结构
List<Map<String, Object>> finalResult = new ArrayList<>();
grouped.forEach((schoolRecord, detailsRecords) -> {
    Map<String, Object> schoolMap = new HashMap<>();
    schoolMap.put("schoolId", schoolRecord.get(SchoolTable.SCHOOL_ID));
    schoolMap.put("schoolName", schoolRecord.get(SchoolTable.SCHOOL_NAME));
    schoolMap.put("isActive", schoolRecord.get(SchoolTable.IS_ACTIVE));
    
    List<Map<String, Object>> detailsList = detailsRecords.stream()
        .map(dr -> {
            Map<String, Object> detailsMap = new HashMap<>();
            detailsMap.put("detailsID", dr.get(SchoolDetailsTable.DETAILS_ID));
            detailsMap.put("detailsName", dr.get(SchoolDetailsTable.DETAILS_NAME));
            detailsMap.put("details", dr.get(SchoolDetailsTable.DETAILS));
            return detailsMap;
        })
        .collect(Collectors.toList());
    
    schoolMap.put("SchoolDetails", detailsList);
    finalResult.add(schoolMap);
});

3. 数据库层聚合(支持分组查询)

如果需要按schoolName或detailsName分组,推荐直接在数据库层用聚合函数完成(适合MySQL 8+、PostgreSQL等支持JSON的数据库),减少客户端数据处理量:

// MySQL示例:用JSON_ARRAYAGG聚合详情数据
SelectConditionStep<Record> result = dsl.select(
        SchoolTable.SCHOOL_ID,
        SchoolTable.SCHOOL_NAME,
        SchoolTable.IS_ACTIVE,
        // 聚合生成SchoolDetails的JSON数组
        field("JSON_ARRAYAGG(JSON_OBJECT('detailsID', {0}, 'detailsName', {1}, 'details', {2}))", 
            JSON.class, 
            SchoolDetailsTable.DETAILS_ID, 
            SchoolDetailsTable.DETAILS_NAME, 
            SchoolDetailsTable.DETAILS)
    )
    .from(SchoolTable)
    .join(SchoolOrgTable)
    .on(SchoolTable.A_ID.eq(SchoolOrgTable.ATest_ID))
    .leftJoin(SchoolDetailsTable)
    .on(SchoolDetailsTable.C_ID.eq(SchoolOrgTable.B_ID))
    // 按学校主键、名称等分组,若要按schoolName分组,调整GROUP BY字段即可
    .groupBy(SchoolTable.SCHOOL_ID, SchoolTable.SCHOOL_NAME, SchoolTable.IS_ACTIVE);

// 转换结果
List<Map<String, Object>> finalResult = result.fetch().stream()
    .map(r -> {
        Map<String, Object> schoolMap = new HashMap<>();
        schoolMap.put("schoolId", r.get(SchoolTable.SCHOOL_ID));
        schoolMap.put("schoolName", r.get(SchoolTable.SCHOOL_NAME));
        schoolMap.put("isActive", r.get(SchoolTable.IS_ACTIVE));
        schoolMap.put("SchoolDetails", r.get(3, JSON.class).toList());
        return schoolMap;
    })
    .collect(Collectors.toList());

内容的提问来源于stack exchange,提问作者EveryDayLearner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 04:43:19