SQL recursive query tree classification data

Table of contents

foreword

 1. Prepare classification data

 2. The principle of recursion

 3. Realize

 4. Combined with mybatis query

Summarize


foreword

I believe that everyone often encounters classified data when dealing with business. How should we deal with this situation? Here I use two ways to solve it: one way to use sql recursion , and the other way to deal with java code (next issue).


1. SQL recursion

1. Prepare classification data

The code is as follows (example):

DROP TABLE IF EXISTS `course_category`;
CREATE TABLE `course_category`  (
  `id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '分类名称',
  `label` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分类标签默认和名称一样',
  `parentid` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0' COMMENT '父结点id(第一级的父节点是0,自关联字段id)',
  `is_show` tinyint NULL DEFAULT NULL COMMENT '是否显示',
  `orderby` int NULL DEFAULT NULL COMMENT '排序字段',
  `is_leaf` tinyint NULL DEFAULT NULL COMMENT '是否叶子',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '课程分类' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of course_category
-- ----------------------------

INSERT INTO `course_category` VALUES ('1-3-3', '.NET', '.NET', '1-3', 1, 3, 1);
INSERT INTO `course_category` VALUES ('1-3-4', 'Objective-C', 'Objective-C', '1-3', 1, 4, 1);
INSERT INTO `course_category` VALUES ('1-3-5', 'Go语言', 'Go语言', '1-3', 1, 5, 1);

INSERT INTO `course_category` VALUES ('1-4', '数据库', '数据库', '1', 1, 4, 0);
INSERT INTO `course_category` VALUES ('1-4-1', 'Oracle', 'Oracle', '1-4', 1, 1, 1);
INSERT INTO `course_category` VALUES ('1-4-2', 'MySQL', 'MySQL', '1-4', 1, 2, 1);
INSERT INTO `course_category` VALUES ('1-4-3', 'SQL Server', 'SQL Server', '1-4', 1, 3, 1);
INSERT INTO `course_category` VALUES ('1-4-4', 'DB2', 'DB2', '1-4', 1, 4, 1);
INSERT INTO `course_category` VALUES ('1-4-5', 'NoSQL', 'NoSQL', '1-4', 1, 5, 1);
INSERT INTO `course_category` VALUES ('1', '根结点', '根结点', '0', 1, 1, 0);
INSERT INTO `course_category` VALUES ('1-1', '前端开发', '前端开发', '1', 1, 1, 0);
INSERT INTO `course_category` VALUES ('1-1-1', 'HTML/CSS', 'HTML/CSS', '1-1', 1, 1, 1);
INSERT INTO `course_category` VALUES ('1-1-2', 'JavaScript', 'JavaScript', '1-1', 1, 2, 1);
INSERT INTO `course_category` VALUES ('1-1-3', 'jQuery', 'jQuery', '1-1', 1, 3, 1);
INSERT INTO `course_category` VALUES ('1-1-4', 'ExtJS', 'ExtJS', '1-1', 1, 4, 1);
INSERT INTO `course_category` VALUES ('1-1-5', 'AngularJS', 'AngularJS', '1-1', 1, 5, 1);

INSERT INTO `course_category` VALUES ('1-2', '移动开发', '移动开发', '1', 1, 2, 0);
INSERT INTO `course_category` VALUES ('1-2-1', '微信开发', '微信开发', '1-2', 1, 1, 1);
INSERT INTO `course_category` VALUES ('1-2-2', 'iOS', 'iOS', '1-2', 1, 2, 1);
INSERT INTO `course_category` VALUES ('1-2-3', '手游开发', '手游开发', '1-2', 1, 3, 1);
INSERT INTO `course_category` VALUES ('1-2-4', 'Swift', 'Swift', '1-2', 1, 4, 1);
INSERT INTO `course_category` VALUES ('1-2-5', 'Android', 'Android', '1-2', 1, 5, 1);

INSERT INTO `course_category` VALUES ('1-3', '编程开发', '编程开发', '1', 1, 3, 0);
INSERT INTO `course_category` VALUES ('1-3-1', 'C/C++', 'C/C++', '1-3', 1, 1, 1);
INSERT INTO `course_category` VALUES ('1-3-2', 'Java', 'Java', '1-3', 1, 2, 1);

 表图如下所示:

 2. The principle of recursion

 To implement sql recursive query, we must first understand its principle.

Recursive query principle : Recursive query in SQL Server is implemented through CTE (table expression). Contains at least two subqueries, the first query is a fixed-point member (seed query), and the seed query is only used as a root query for recursive positioning; the second query is called a recursive query, and these two subqueries can be connected together through UNION, UNION ALL or UNION  DISTINCT .

Note : The RECURSIVE keyword is only valid in MySQL8+ version. The seed query will only be executed once and get the initial data as the root subset, while the recursive query has no explicit recursive termination condition, and the recursion will only stop when the second recursive query is repeatedly executed (recursive) until no new rows are generated, an empty result set is returned, or the maximum limit of recursion times is exceeded. Finally, all the result sets are queried, which is very useful for deep queries (such as queries with parent-child relationships).

Advantages: high efficiency, under a large amount of data sets, the speed is faster than the query of the program.

Common forms of SQL recursion:

WITH RECURSIVE CTE AS (

SELECT column1,column2... FROM tablename WHERE conditions

UNION ALL

SELECT column1,column2... FROM tablename 

INNER JOIN CTE ON conditions 

)

3. Realize

The sql is as follows:

with recursive t1 as (
select * from course_category p where id= "1"
    union all
    select t.* from course_category t inner join t1 on t1.id = t.parentid
    )
    select * from t1 order by t1.id, t1.orderby

The query results are as follows:

 4. Combined with mybatis query

The import dependencies are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>testdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>testdemo</name>
    <description>testdemo</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <!-- xml放在java目录下-->
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 yml configuration:

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver

# Note that the configuration here is mybatis-plus: 
  mapper-locations: com/example/testdemo/dao/*.xml after putting the mapper mapping in the java directory

The directory structure is as follows:

Course entity class: 

@Data
@TableName(value = "course_category")
public class Course implements Serializable {

    @TableField(value = "id")
    private String id;

    private String name;

    private String label;

    @TableField(value = "parentid")
    private String parentId;

    @TableField(value = "is_show")
    private Integer isShow;

    private Integer orderby;

    @TableField(value = "is_leaf")
    private Integer isLeaf;

    @TableField(exist = false)
    List<Course> courseList;
}

 CourseMapper mapping class:

@Mapper
public interface CourseMapper extends BaseMapper<Course> {

    List<Course> selectTreeByRootId(String id);
}

CourseMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.testdemo.mapper.CourseMapper">

    <select id="selectTreeByRootId" resultType="com.example.testdemo.entity.Course" parameterType="string">

        with recursive t1 as (
            select * from course_category p where id= #{id}
    union all
    select t.* from course_category t inner join t1 on t1.id = t.parentid
    )
    select * from t1 order by t1.id, t1.orderby
    </select>
</mapper>

Because the returned data does not satisfy the classification format, it is enough to encapsulate the data into Crouse at the end. The encapsulation operation is as follows:

CourseServiceImpl class:
    @Autowired
    CourseMapper courseMapper;
    @Override
    public  List<Course> selectTreeNode(String id){
        List<Course> courseList =  courseMapper.selectTreeByRootId(id);
        List<Course> categoryTreeDtos = new ArrayList<>();
        HashMap<String, Course> mapTemp = new HashMap<>();
        courseList.stream().forEach(item->{
            mapTemp.put(item.getId(),item);
            //只将根节点的下级节点放入list
            if(item.getParentId().equals("1")){
                categoryTreeDtos.add(item);
            }
            Course fatherCourse = mapTemp.get(item.getParentId());
            if(fatherCourse!=null){
                if(fatherCourse.getCourseList() ==null){
                    fatherCourse.setCourseList(new ArrayList<Course>());
                }
                //向节点的下级节点list加入节点
                fatherCourse.getCourseList().add(item);
            }
        });
        return categoryTreeDtos;
    }

test:

    @Autowired
    CourseService courseService;

    @Test
    void context() {
        List<Course> courseList = courseService.selectTreeNode("1");
        System.out.println(courseList);
    }

The result is as shown in the figure:


Summarize

Thank you for watching

This article is just an attempt to practice recursively searching classified data. For beginners, please take care of me.

Guess you like

Origin blog.csdn.net/weixin_58403235/article/details/129635380