One SQL to complete dynamic hierarchical query

When I used ActiveReports report designer to design a report template in a recent movable type project , I encountered a multi-level classification problem: I need to summarize all sales of a department and the sales amount of subordinate departments, because the level of subordinate levels is uncertain, Therefore, the method of splicing sub-queries obviously cannot meet the requirements. After some experiments, CTE (Common Table Expression) is used to solve this problem easily!

Example: There is the following department table

and employee table

If you want to query all employees in the Northwest District (including Northwest, Xi'an, Lanzhou), as shown below:

How to use CTE to achieve it?

Talk is cheap. Show me the code

-- The following code tests using SQLite 3.18.0 to pass 
WITH 
    [ depts ] ( [ dept_id ] ) AS (
         SELECT  [ d ] . [ dept_id ] 
        FROM    [ dept ]  [ d ] 
               JOIN  [ employees ]  [ e ]  ON  [ d ] . [ dept_id ]  =  [ e ] . [ dept_id ] 
        WHERE   [ e].[emp_name] = '西北-经理'
        UNION ALL
        SELECT [d].[dept_id]
        FROM   [dept] [d]
               JOIN [depts] [s] ON [d].[parent_id] = [s].[dept_id]
    )
SELECT *
FROM   [employees]
WHERE  [dept_id] IN (SELECT [dept_id]
       FROM   [depts]);

Some students may not be familiar with CTE (Common Table Expression), here is a brief introduction, interested students can google or Baidu, introduce a lot (here takes SQLite as an example): 

I still prefer to call CTE (Common Table Expression) a "common table variable" rather than a "common expression", because in terms of behavior and usage scenarios, CTEs are more often used to generate (iterative or non-iterative) result sets , for subsequent statements (query, insert, delete or update), such as the above example is a typical iterative traversal of tree-structured data.

Advantages of CTE:

  • The recursive feature makes the logic that originally needed to be completed by using temporary tables and stored procedures can be completed through SQL, especially for some tree or graph data models
  • Because it is a temporary result set within the session, there is no need to explicitly declare or destroy
  • The readability of the rewritten SQL statement is improved (you can only modify it if you understand it)
  • The possibility of optimizing the execution plan for the database engine (this is not certain, it needs to be related to the implementation of the specific CTE), optimize the execution plan, and the performance will naturally increase

 

In order to better illustrate the capabilities of CTE, here are two examples (transferred from the SQLite official website documentation)

Mandelbrot set

--The following code is tested with SQLite 3.18.0 
WITH RECURSIVE
  xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),
  yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),
  m(iter, cx, cy, x, y) AS (
    SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
    UNION ALL
    SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m 
     WHERE (x*x + y*y) < 4.0 AND iter<28
  ),
  m2(iter, cx, cy) AS (
    SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
  ),
  a(t) AS (
    SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '') 
    FROM m2 GROUP BY cy
  )
SELECT group_concat(rtrim(t),x'0a') FROM a;

运行后的结果,如下图:(使用SQLite Expert Personal 4.2 x64)

 

数独问题(Sudoku)

假设有类似下图的问题:

 

-- 以下代码使用SQLite 3.18.0 测试通过
WITH RECURSIVE
  input(sud) AS (
    VALUES('53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79')
  ),
  digits(z, lp) AS (
    VALUES('1', 1)
    UNION ALL SELECT
    CAST(lp+1 AS TEXT), lp+1 FROM digits WHERE lp<9
  ),
  x(s, ind) AS (
    SELECT sud, instr(sud, '.') FROM input
    UNION ALL
    SELECT
      substr(s, 1, ind-1) || z || substr(s, ind+1),
      instr( substr(s, 1, ind-1) || z || substr(s, ind+1), '.' )
     FROM x, digits AS z
    WHERE ind>0
      AND NOT EXISTS (
            SELECT 1
              FROM digits AS lp
             WHERE z.z = substr(s, ((ind-1)/9)*9 + lp, 1)
                OR z.z = substr(s, ((ind-1)%9) + (lp-1)*9 + 1, 1)
                OR z.z = substr(s, (((ind-1)/3) % 3) * 3
                        + ((ind-1)/27) * 27 + lp
                        + ((lp-1) / 3) * 6, 1)
         )
  )
SELECT s FROM x WHERE ind=0;

执行结果(结果中的数字就是对应格子中的答案)

附:SQLite中CTE(WITH关键字)语法图解:

WITH

 

cte-table-name

 

Select-stmt:

 

总结

CTE是解决一些特定问题的利器,但了解和正确的使用是前提,在决定将已有的一些SQL重构为CTE之前,确保对已有语句有清晰的理解以及对CTE足够的学习!Good Luck~~~

附件:用到的SQL脚本

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326310698&siteId=291194637
Recommended