一句SQL完成動態分級查詢

在最近的活字格項目中使用ActiveReports報表設計器設計一個報表模板時,遇到一個多級分類的難題:須要將某個部門全部銷售及下屬部門的銷售金額彙總,由於下屬級別的層次不肯定,因此靠拼接子查詢的方式顯然是不能知足要求,通過一番實驗,利用了CTE(Common Table Expression)很輕鬆解決了這個問題!git

舉例:有以下的部門表sql

圖片描述

以及員工表數據庫

圖片描述

若是想查詢全部西北區的員工(包含西北、西安、蘭州),以下圖所示:性能

圖片描述

如何用CTE的方式實現呢?學習

Talk is cheap. Show me the code測試

-- 如下代碼使用SQLite 3.18.0 測試經過
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]);

可能有些同窗對CTE(Common Table Expression)還不太熟悉,這裏簡單說一下,有興趣的同窗能夠google或者百度,介紹不少(這裏以SQLite舉例): google

我仍是更喜歡稱CTE(Common Table Expression)爲「公用表變量」而不是「公用表達式」,由於從行爲和使用場景上講,CTE更多的時候是產生(分迭代或者不迭代)結果集,供其後的語句使用(查詢、插入、刪除或更新),如上述的例子就是一個典型的利用迭代遍歷樹形結構數據。spa

CTE的優勢:設計

遞歸的特色使得本來須要使用臨時表、存儲過程才能完成的邏輯,經過SQL就能夠完成,尤爲針對一些樹或者是圖的數據模型
由於是會話內的臨時結果集,不須要去顯示的聲明或銷燬
改寫後的SQL語句可讀性提升(看的明白才能修改)
給數據庫引擎優化執行計劃的可能性(這個不是確定的,須要根據具體CTE的實現有關),優化了執行計劃,天然地性能就能上升

爲了更好的說明CTE的能力,這裏附上兩個例子(轉自SQLite官網文檔)

曼德勃羅集合(Mandelbrot set)

-- 如下代碼使用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腳本

相關文章
相關標籤/搜索