在最近的活字格项目中使用ActiveReports报表设计器设计一个报表模板时,遇到一个多级分类的难题:需要将某个部门所有销售及下属部门的销售金额汇总,因为下属级别的层次不确定,所以靠拼接子查询的方式显然是不能满足要求,经过一番实验,利用了CTE(Common Table Expression)很轻松解决了这个问题!

举例:有如下的部门表

以及员工表

如果想查询所有西北区的员工(包含西北、西安、兰州),如下图所示:

如何用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举例):

我还是更喜欢称CTE(Common Table Expression)为“公用表变量”而不是“公用表达式”,因为从行为和使用场景上讲,CTE更多的时候是产生(分迭代或者不迭代)结果集,供其后的语句使用(查询、插入、删除或更新),如上述的例子就是一个典型的利用迭代遍历树形结构数据。

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)
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脚本

相关阅读:

【报表福利大放送】100余套报表模板免费下载

新手逆袭!记我的第一次系统开发

菜鸟是如何在三天内完成系统开发的?

一句SQL完成动态分级查询的更多相关文章

  1. LINQ to SQL 运行时动态构建查询条件

    在进行数据查询时,经常碰到需要动态构建查询条件.使用LINQ实现这个需求可能会比以前拼接SQL语句更麻烦一些.本文介绍了3种运行时动态构建查询条件的方法.本文中的例子最终实现的都是同一个功能,从Nor ...

  2. SQL Server 一句Sql把表结构全部查询出来

    --一句Sql把表结构全部查询出来 SELECT 表名 = Case When A.colorder=1 Then D.name Else '' End, 表说明 = Case When A.colo ...

  3. 持久层之 MyBatis: 第二篇 :动态SQL And多表查询

    MyBatis入门到精通 完整CRUD UserDaoImpl 编写UserDao对应的UserDaoMapper.xml 添加UserDao的测试用例 编写UserDao的测试用例 解决数据库字段名 ...

  4. MyBatis构建sql时动态传入表名以及字段名

    今天项目需要用到动态表名,找到这一篇文章,亲测可用 用了mybatis很长一段时间了,但是感觉用的都是比较基本的功能,很多mybatis相对ibatis的新功能都没怎么用过.比如其内置的注解功能之类的 ...

  5. mybatis+maven+父子多模块进行crud以及动态条件查询

    使用IDEA创建maven项目,File→New→Project→maven→Next→填写GroupId(例:com.zyl)和ArtifactId(mybatis-demo-parent)→Nex ...

  6. SQL Server 动态行转列(参数化表名、分组列、行转列字段、字段值)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段: 方法二:使用拼接SQL, ...

  7. mybatis 使用记录(二) 动态拼接查询条件

    2016-12-16 阅读项目代码时,在项目的xml文件中发现如下写法: SELECT student_user_id FROM tbr_student_class WHERE 1=1 <if ...

  8. 使用Expression Tree构建动态LINQ查询

    这篇文章介绍一个有意思的话题,也是经常被人问到的:如何构建动态LINQ查询?所谓动态,主要的意思在于查询的条件可以随机组合,动态添加,而不是固定的写法.这个在很多系统开发过程中是非常有用的. 我这里给 ...

  9. SQL Server DBA日常查询视图_数据库对象视图

    1.数据库 use master; exec sp_helpdb 1.1查询数据库大小 1.2查询数据库状态 use msdb select name, user_access_desc, --用户访 ...

随机推荐

  1. MFCC特征参数提取流程概述

      一 概念概述: 在语音识别(Speech Recognition)和话者识别(Speaker Recognition)方面,最常用到的语音特征就是梅尔倒谱系数(Mel-scale Frequenc ...

  2. Xamarin.Android 嵌入web端界面

    在程序中嵌入Web端界面. 首先在前台界面上创建一个webview <android.webkit.WebView android:layout_width="match_parent ...

  3. IndexedDB 简单封装

    IndexedDB 浏览器数据库,是一个非关系型数据库,数据形式使用的是json,IndexedDB适合存储大量数据,它的API是异步调用的,当然他的api 也相对复杂. 当然浏览器数据存储 还有Lo ...

  4. 总结jenkins Android自动打包遇到的坑

    一.ndk-build报错 [root@hejianlai-jenkins LearnGradle]# ndk-build /usr/local/android-ndk-r8/ndk-build: / ...

  5. .Net 从零开始构建一个框架之基本实体结构与基本仓储构建

    本系列文章将介绍如何在.Net框架下,从零开始搭建一个完成CRUD的Framework,该Framework将具备以下功能,基本实体结构(基于DDD).基本仓储结构.模块加载系统.工作单元.事件总线( ...

  6. Analyzing .net core application with SonarQube Scanner for MSBuild

    SonarQube是管理代码质量一个开放平台,省略安装过程,下面介绍下如何使用sonarqube去扫描c# 代码. 前提:下载SonarQube Scanner for MSBuild.https:/ ...

  7. (转)python生态环境简介

    Python生态环境简介 作者: Mir Nazim 原文: Python Ecosystem - An Introduction 译者: dccrazyboy  原译: Python生态环境简介 当 ...

  8. Python快速学习02:基本数据类型 & 序列

    前言 系列文章:[传送门] 也就每点一点点的开始咯,“还有两年时间,两年可以学很多东西的” Python ['paɪθən]  n. 巨蛇,大蟒 基本数据类型 变量不需要声明 a=10 # int 整 ...

  9. Java NIO的工作方式

    1.BIO带来的挑战 BIO即阻塞IO,不管是磁盘IO,还是网络IO,数据在写入OutputStream或者从InputStream读取时都有可能发生阻塞,一旦有阻塞,当前线程将会被挂起,即线程进入非 ...

  10. 翻译:insert select(已提交到MariaDB官方手册)

    本文为mariadb官方手册:insert select的译文. 原文:https://mariadb.com/kb/en/insert-select/ 我提交到MariaDB官方手册的译文:http ...