天天看點

MySQL遞歸查詢,Oracle遞歸查詢,MyBatis+MySQL實作遞歸查詢

    遞歸查詢用于查詢樹形結構的清單,比如行政區清單。包括向下遞歸查詢:根據父級查詢子級;向上查詢:根據子級查詢父級。mysql需要使用存儲函數,oracle可以使用connect by語句直接查詢。

MySQL遞歸查詢

    首先建立一張表

DROP TABLE IF EXISTS `tb_base_region`;
CREATE TABLE `tb_base_region` (
  `PKID` int(10) NOT NULL AUTO_INCREMENT,
  `REGION_CODE` varchar(16) NOT NULL,
  `REGION_NAME` varchar(40) NOT NULL,
  `SHORT_NAME` varchar(20) DEFAULT NULL,
  `PARENT_CODE` varchar(16) DEFAULT NULL,
  `REGION_LEVEL` int(2) DEFAULT NULL,
  `STATUS` varchar(2) DEFAULT NULL,
  `REMARKS` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`PKID`)
) ENGINE=InnoDB AUTO_INCREMENT=385 DEFAULT CHARSET=utf8;
           

    插入資料

INSERT INTO `tb_base_region` VALUES (601, '01', '中華人民共和國', '中國', NULL, 1, '1', NULL);
INSERT INTO `tb_base_region` VALUES (602, '0101', '河北省', '冀', '01', 2, '1', NULL);
INSERT INTO `tb_base_region` VALUES (603, '0102', '河南省', '豫', '01', 2, '1', NULL);
INSERT INTO `tb_base_region` VALUES (604, '010101', '石家莊市', '石家莊', '0101', 3, '1', NULL);
INSERT INTO `tb_base_region` VALUES (605, '010102', '保定市', '保定', '0101', 3, '1', NULL);
INSERT INTO `tb_base_region` VALUES (606, '010201', '鄭州市', '鄭州', '0102', 3, '1', NULL);
INSERT INTO `tb_base_region` VALUES (607, '010202', '新鄉市', '新鄉', '0102', 3, '1', NULL);
           

    MySQL遞歸查詢需要用到存儲函數,使用存儲函數查詢出符合要求的資料的region_code集合,以逗号分隔拼接成字元串。然後使用FIND_IN_SET(region_code,存儲函數傳回結果)函數查詢出需要的資料。

    MySQL遞歸查詢存儲函數如下:

-- 建立向下查詢的存儲函數,查詢出符合要求的資料的region_code集合,以逗号拼接成字元串。
CREATE FUNCTION getRegionCodeListForDown(upCode VARCHAR(10))
RETURNS VARCHAR(4096)
BEGIN
DECLARE returnCodeList VARCHAR(4096);
DECLARE selectCode VARCHAR(4096);
SET returnCodeList = '';
SET selectCode = upCode;
WHILE selectCode IS NOT NULL DO
SET returnCodeList = CONCAT(returnCodeList,',',selectCode);
select GROUP_CONCAT(region_code) INTO selectCode FROM tb_base_region WHERE FIND_IN_SET(parent_code,selectCode) > 0;
END WHILE;
RETURN returnCodeList;
END;
-- 建立向上查詢的存儲函數,查詢出符合要求的資料的region_code集合。
CREATE FUNCTION getRegionCodeListForUp(downCode VARCHAR(10))
RETURNS VARCHAR(4096)
BEGIN
DECLARE returnCodeList VARCHAR(4096);
DECLARE selectCode VARCHAR(4096);
SET returnCodeList = '';
SET selectCode = downCode;
-- WHILE selectCode IS NOT NULL DO  -- 判斷條件視情況而定
WHILE selectCode >= 0 DO
SET returnCodeList = CONCAT(returnCodeList,',',selectCode);
SELECT parent_code into selectCode from tb_base_region WHERE region_code = selectCode;
END WHILE;
RETURN returnCodeList;
END;
           

    語句中 concat(a,b,c) 函數可以将a、b、c三個字元串做拼接;

    group_concat(a) into a 語句可以将 a 字段查詢結果以逗号為分隔符做拼接,拼接結果放入 a 字段。

    group_concat(a) as a .... group by b 語句可以按 b 字段分組,将 a 字段查詢結果以逗号分割符做拼接,拼接結果放入 a 字段。

    find_in_set(a,b) > 0 語句可以篩選出符合“ a 字段中的資料存在于 b 參數列舉的值中 ”條件的資料。

    這樣一來就可以使用存儲函數實作遞歸查詢

-- 向下查詢(根據父級查詢子級)
select * from tb_base_region WHERE FIND_IN_SET(region_code,getRegionCodeListForDown('01'));
-- 向上查詢(根據子級查詢父級)
select * from tb_base_region WHERE FIND_IN_SET(region_code,getRegionCodeListForUp('010101'));
           

Oracle遞歸查詢

    oracle提供了遞歸查詢的語句:start with ... connect by ... prior

    start with:遞歸查詢的起始節點

    connect by:遞歸查詢的連接配接條件

    prior:prior和放在parent_code前代表向上查詢;prior放在code前代表向下查詢;

        connect by prior region_doce = parent_code:向下查詢;

        connect by region_code = prior parent_code:向上查詢。

-- 向下查詢
select * from tb_base_region start with region_code = '01' connect by prior region_code = parent_code;
-- 向上查詢
select * from tb_base_region start with region code = '010101' connect by region_code = prior parent_code;
           

MyBatis+MySQL實作遞歸查詢

    方法一:如果我們需要向前端提供一個List集合,前端可以使用tree插件實作樹形清單的展示,這種方法可以結合MySQL的存儲函數實作遞歸查詢。

    方法二:如果我們需要在後端查詢出一個樹形結構的對象,前端可以使用html标簽實作層級關系清單的展示,這種方法不用使用MySQL的存儲函數,而是在mapper.xml的結果集<association>标簽或<collection>标簽中使用select屬性實作遞歸查詢,并使用column屬性指定遞歸查詢的參數。

方法一:輸出一個List集合(結合MySQL的存儲過程實作遞歸查詢)

    mapper.xml

<!-- 通用查詢映射結果 -->
<resultMap id="BaseResultMap" type="com.shiningcity.company.pojo.BaseRegion">
    <id column="PKID" property="pkid" />
    <result column="REGION_CODE" property="regionCode" />
    <result column="REGION_NAME" property="regionName" />
    <result column="SHORT_NAME" property="shortName" />
    <result column="PARENT_CODE" property="parentCode" />
    <result column="REGION_LEVEL" property="regionLevel" />
    <result column="STATUS" property="status" />
    <result column="REMARKS" property="remarks" />
</resultMap>
    
<!-- 遞歸查詢(向下查詢)在sql語句中調用存儲過程,根據存儲過程傳回的id集合查詢資料集合 -->
<select id="selectRegionListDownProcedure"
    resultMap="BaseResultMap" statementType="CALLABLE">
    select * from tb_base_region WHERE
    FIND_IN_SET(region_code,getRegionCodeListForDown(#{upCode}));
</select>
    
<!-- 遞歸查詢(向上查詢)在sql語句中調用存儲過程,根據存儲過程傳回的id集合查詢資料集合 -->
<select id="selectRegionListUpProcedure"
    resultMap="BaseResultMap" statementType="CALLABLE">
    select * from tb_base_region WHERE
    FIND_IN_SET(region_code,getRegionCodeListForUp(#{downCode}));
</select>
           

    mapper.java

// 向下查詢(根據父級查詢子級)
List<BaseRegion> selectRegionListDownProcedure(String upCode);
// 向上查詢(根據子級查詢父級)
List<BaseRegion> selectRegionListUpProcedure(String downCode);
           

    service層調用mapper接口

@Service
public class BaseCompanyServiceImpl extends SuperServiceImpl<BaseCompanyMapper, BaseCompany> implements BaseCompanyService {
    Logger logger = LoggerFactory.getLogger("BaseCompanyServiceImpl");
    @Autowired
    private BaseCompanyMapper companyMapper;
    @Autowired
    private BaseRegionMapper regionMapper;
    
    @Override
    public String testProcedure() {
        // 向下查詢
        List<BaseRegion> regionList1 = regionMapper.selectRegionListDownProcedure("01");
        // 向上查詢
        List<BaseRegion> regionList2 = regionMapper.selectRegionListUpProcedure("010101");
        logger.warn(JSON.toJSONString(regionList1));
        logger.warn(JSON.toJSONString(regionList2));
        return "OK";
    }
}
           

    查詢結果轉成JSON後結果如下所示,list集合查詢結果适用于前端使用tree插件,根據regionCode和parentCode字段可以自動加載成樹形結構

// 向下查詢
[{"pkid":601,"regionCode":"01","regionLevel":1,"regionName":"中華人民共和國","shortName":"中國","status":"1"},
{"parentCode":"01","pkid":602,"regionCode":"0101","regionLevel":2,"regionName":"河北省","shortName":"冀","status":"1"},
{"parentCode":"01","pkid":603,"regionCode":"0102","regionLevel":2,"regionName":"河南省","shortName":"豫","status":"1"},
{"parentCode":"0101","pkid":604,"regionCode":"010101","regionLevel":3,"regionName":"石家莊市","shortName":"石家莊","status":"1"},
{"parentCode":"0101","pkid":605,"regionCode":"010102","regionLevel":3,"regionName":"保定市","shortName":"保定","status":"1"},
{"parentCode":"0102","pkid":606,"regionCode":"010201","regionLevel":3,"regionName":"鄭州市","shortName":"鄭州","status":"1"},
{"parentCode":"0102","pkid":607,"regionCode":"010202","regionLevel":3,"regionName":"新鄉市","shortName":"新鄉","status":"1"}]

// 向上查詢
[{"pkid":601,"regionCode":"01","regionLevel":1,"regionName":"中華人民共和國","shortName":"中國","status":"1"},
{"parentCode":"01","pkid":602,"regionCode":"0101","regionLevel":2,"regionName":"河北省","shortName":"冀","status":"1"},
{"parentCode":"0101","pkid":604,"regionCode":"010101","regionLevel":3,"regionName":"石家莊市","shortName":"石家莊","status":"1"}]
           

方法二:輸出一個帶層級關系的對象(使用結果集中<association>或<collection>标簽的select屬性實作遞歸查詢)

    如果是向下查詢,BaseRegion類中需要有一個childList字段,存放子級對象集合;如果是向上查詢,BaseRegion類中需要有一個parentInfo字段,存放父級對象,但是這樣查詢結果是倒序的,需要經過java代碼轉換,将子級對象存放到childList字段中。

    BaseRegion.java

/*
本案例使用MybatisPlus,實體類需要使用@TableName注解比對資料庫表,使用@TableId注解比對資料庫主鍵,
并繼承了MybatisPlus中com.baomidou.mybatisplus.extension.activerecord.Model<>類
*/
@TableName("tb_base_region")
public class BaseRegion extends Model<BaseRegion> {
    private static final long serialVersionUID=1L;
    @TableId(value = "PKID", type = IdType.AUTO)
    private Integer pkid;
    private String regionCode;
    private String regionName;
    private String shortName;
    private String parentCode;
    private Integer regionLevel;
    private String status;
    private String remarks;
    // 子級對象集合,一對多關系
    private List<BaseRegion> childList;
    // 父級對象,一對一關系
    private BaseRegion parentInfo;

    public Integer getPkid() {
        return pkid;
    }
    public void setPkid(Integer pkid) {
        this.pkid = pkid;
    }
    public String getRegionCode() {
        return regionCode;
    }
    public void setRegionCode(String regionCode) {
        this.regionCode = regionCode;
    }
    public String getRegionName() {
        return regionName;
    }
    public void setRegionName(String regionName) {
        this.regionName = regionName;
    }
    public String getShortName() {
        return shortName;
    }
    public void setShortName(String shortName) {
        this.shortName = shortName;
    }
    public String getParentCode() {
        return parentCode;
    }
    public void setParentCode(String parentCode) {
        this.parentCode = parentCode;
    }
    public Integer getRegionLevel() {
        return regionLevel;
    }
    public void setRegionLevel(Integer regionLevel) {
        this.regionLevel = regionLevel;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public String getRemarks() {
        return remarks;
    }
    public void setRemarks(String remarks) {
        this.remarks = remarks;
    }
    public List<BaseRegion> getChildList() {
        return childList;
    }
    public void setChildList(List<BaseRegion> childList) {
        this.childList = childList;
    }
    public BaseRegion getParentInfo() {
        return parentInfo;
    }
    public void setParentInfo(BaseRegion parentInfo) {
        this.parentInfo = parentInfo;
    }
}
           

    mapper.xml

    MyBatis中使用結果集中的 <collection>(一對多)或 <association>(一對一)标簽可以輕松實作遞歸查詢,其中 <collection> 标簽中 ofType 屬性或 <association> 标簽中 javaType 屬性指定了子級或父級對象的類依然是 BaseRegion 類,select 屬性指定遞歸查詢使用的方法,column 字段指定遞歸查詢方法中的參數。

<!-- 向下遞歸查詢映射結果 -->
<resultMap id="forEacheResultMapDown" type="com.shiningcity.company.pojo.BaseRegion">
    <id column="PKID" property="pkid" />
    <result column="REGION_CODE" property="regionCode" />
    <result column="REGION_NAME" property="regionName" />
    <result column="SHORT_NAME" property="shortName" />
    <result column="PARENT_CODE" property="parentCode" />
    <result column="REGION_LEVEL" property="regionLevel" />
    <result column="STATUS" property="status" />
    <result column="REMARKS" property="remarks" />
<!-- 使用select屬性調用selectRegionListHaveChild2接口實作遞歸查詢,ofType指定遞歸查詢結果的類,column指定遞歸查詢使用的參數 -->
    <collection property="childList"
        ofType="com.shiningcity.company.pojo.BaseRegion"
        select="com.shiningcity.company.mapper.BaseRegionMapper.selectRegionListHaveChild2"
        column="REGION_CODE">
    </collection>
</resultMap>
    
<!-- 向上遞歸查詢映射結果 -->
<resultMap id="forEacheResultMapUp" type="com.shiningcity.company.pojo.BaseRegion">
    <id column="PKID" property="pkid" />
    <result column="REGION_CODE" property="regionCode" />
    <result column="REGION_NAME" property="regionName" />
    <result column="SHORT_NAME" property="shortName" />
    <result column="PARENT_CODE" property="parentCode" />
    <result column="REGION_LEVEL" property="regionLevel" />
    <result column="STATUS" property="status" />
    <result column="REMARKS" property="remarks" />
<!-- 使用select屬性調用selectRegionListHaveParent接口實作遞歸查詢,javaType指定遞歸查詢結果的類,column指定遞歸查詢使用的參數 -->
    <association property="parentInfo"
        javaType="com.shiningcity.company.pojo.BaseRegion"
        select="com.shiningcity.company.mapper.BaseRegionMapper.selectRegionListHaveParent"
        column="PARENT_CODE">
    </association>
</resultMap>

<!-- 帶層級關系的遞歸查詢(向下查詢) -->
<!-- 查詢第一層資料的sql語句 -->
<select id="selectRegionListHaveChild1" resultMap="forEacheResultMapDown">
    select * from tb_base_region WHERE REGION_CODE = #{regionCode};
</select>
<!-- 遞歸查詢的sql語句 -->
<select id="selectRegionListHaveChild2" resultMap="forEacheResultMapDown">
    select * from tb_base_region WHERE PARENT_CODE = #{regionCode};
</select>
    
<!-- 帶層級關系的遞歸查詢(向上查詢) -->
<!-- 查詢第一層和遞歸查詢使用相同的sql語句 -->
<select id="selectRegionListHaveParent" resultMap="forEacheResultMapUp">
    select * from tb_base_region WHERE REGION_CODE = #{regionCode};
</select>
           

    mapper.java

// 向下查詢(根據父級查詢子級)
BaseRegion selectRegionListHaveChild1(String regionCode);
List<BaseRegion> selectRegionListHaveChild2(String regionCode);
// 向上查詢(根據子級查詢父級)
BaseRegion selectRegionListHaveParent(String regionCode);
           

    service層調用mapper接口

@Service
public class BaseCompanyServiceImpl extends SuperServiceImpl<BaseCompanyMapper, BaseCompany> implements BaseCompanyService {
    Logger logger = LoggerFactory.getLogger("BaseCompanyServiceImpl");
    @Autowired
    private BaseCompanyMapper companyMapper;
    @Autowired
    private BaseRegionMapper regionMapper;
    
    @Override
    public String testProcedure() {
        // 向下查詢
        BaseRegion regionList3 = regionMapper.selectRegionListHaveChild1("01");
        // 向上查詢
        BaseRegion regionList4 = regionMapper.selectRegionListHaveParent("010101");

        // 由于向上查詢的結果是倒序的(子級對象中包含父級對象),是以這裡要做一個轉換,轉成正序結果(父級對象中包含子級對象)。
        while (regionList4.getParentCode()!=null) {
            // 建構子級對象集合
            List<BaseRegion> childList = new ArrayList<BaseRegion>();
            BaseRegion info = new BaseRegion();
            info.setParentCode(regionList4.getParentCode());
            info.setPkid(regionList4.getPkid());
            info.setRegionCode(regionList4.getRegionCode());
            info.setRegionLevel(regionList4.getRegionLevel());
            info.setRegionName(regionList4.getRegionName());
            info.setShortName(regionList4.getShortName());
            info.setStatus(regionList4.getStatus());
            info.setChildList(regionList4.getChildList());
            childList.add(info);
            // 目前對象更新為父級對象
            regionList4 = regionList4.getParentInfo();
            // 将子級對象集合加入目前對象
            regionList4.setChildList(childList);
            // 直到目前對象更新為頂級對象後,while循環結束,實作正序結果的轉換
        }
        logger.warn(JSON.toJSONString(regionList3));
        logger.warn(JSON.toJSONString(regionList4));
        return "OK";
    }
}
           

    查詢結果轉成JSON後結果如下所示,我們向前端提供一個帶層級關系的對象,前端隻需使用html标簽即可展示層級關系的清單。

// 向下查詢
{"pkid":601,"regionCode":"01","regionLevel":1,"regionName":"中華人民共和國","shortName":"中國","status":"1",
"childList":[
    {"parentCode":"01","pkid":602,"regionCode":"0101","regionLevel":2,"regionName":"河北省","shortName":"冀","status":"1",
    "childList":[
        {"childList":[],"parentCode":"0101","pkid":604,"regionCode":"010101","regionLevel":3,"regionName":"石家莊市","shortName":"石家莊","status":"1"},
        {"childList":[],"parentCode":"0101","pkid":605,"regionCode":"010102","regionLevel":3,"regionName":"保定市","shortName":"保定","status":"1"}
    ]},
    {"parentCode":"01","pkid":603,"regionCode":"0102","regionLevel":2,"regionName":"河南省","shortName":"豫","status":"1",
    "childList":[
        {"childList":[],"parentCode":"0102","pkid":606,"regionCode":"010201","regionLevel":3,"regionName":"鄭州市","shortName":"鄭州","status":"1"},
        {"childList":[],"parentCode":"0102","pkid":607,"regionCode":"010202","regionLevel":3,"regionName":"新鄉市","shortName":"新鄉","status":"1"}
    ]}
]}

// 向上查詢
{"pkid":601,"regionCode":"01","regionLevel":1,"regionName":"中華人民共和國","shortName":"中國","status":"1",
"childList":[
    {"parentCode":"01","pkid":602,"regionCode":"0101","regionLevel":2,"regionName":"河北省","shortName":"冀","status":"1",
    "childList":[
        {"parentCode":"0101","pkid":604,"regionCode":"010101","regionLevel":3,"regionName":"石家莊市","shortName":"石家莊","status":"1"}
    ]}
]}
           

繼續閱讀