导读

SQL 是一种每位数据开发者必备的开发语言,不同的用户使用 SQL 语言的程度不同,最开始接触到的 SQL 就是 SELECT ,INSERT, UPDATE, DELETE 以及 WHERE 子句对数据进行筛选,如果需要关联,可能会使用 JOIN 关联查询多张表。随着数据量的增多以及需求复杂性的要求,对数据开发者的要求可以不仅仅以上简单的使用方式。今天我们一起来了解一些日常开发中常用的几种 SQL 高级概念,带你在 SQL Server 数据开发中起飞。

1,公共表达式 CTE

CTE ( Common Table Expression ), 公共表达式,在 SQL Server 2005 中引入的一个特性。

  1. 单个语句的执行范围内定义的临时结果集
  2. 只在查询期间有效
  3. 可以自引用,也可以在查询中多次引用
  4. 实现代码的重复利用,提升代码可读性
  5. 以优雅的方式实现递归等复杂的查询
[ WITH <common_table_expression> [ ,...n ] ]

<common_table_expression>::=    expression_name [ ( column_name [ ,...n ] ) ]    AS    ( CTE_query_definition )

对比下面的两种查询语句,第一个语句中使用了子查询进行查询,近乎难以理解。

第一种:一般写法

select Orders.orderid, Orders.orderdate, Orders.requireddate,Orders.shippeddate,Orders.shipcity,Orders.shipaddressfrom Sales.Ordersleft join Sales.OrderDetailson OrderDetails.orderid = Orders.orderidwhere custid in(select custidfrom sales.Customerswhere country in ('USA','Italy'))and OrderDetails.qty * OrderDetails.unitprice > 100and datediff(day,requireddate,shippeddate) > 1;

第二种:公共表达式写法

with cust as (select custidfrom sales.Customerswhere country in ('USA','Italy')),qty as(select orderidfrom Sales.OrderDetailswhere OrderDetails.qty * OrderDetails.unitprice > 100)select Orders.orderid, Orders.orderdate, Orders.requireddate,Orders.shippeddate,Orders.shipcity,Orders.shipaddressfrom Sales.Orders, qty, custwhere Orders.orderid = qty.orderid and Orders.custid = cust.custidand datediff(day,requireddate,shippeddate) > 1;

对比以上两种写法,第一种写法主要使用子查询,第二种写法是使用 CTE 公共表达式的写法,代码可读性更高;其中 CTE 将代码分解为较小的快,更利于后期的运维工作;而且 CTE 允许为每个 CTE 分配不同的名称。代码可读性也是项目交付的指标之一,除了代码可读性之外,CTE 可以用于实现递归查询。

2,递归查询

递归 CTE 是引用自己的 CTE, 就像编程中的递归函数一样。递归 CTE 经常用于查询组织结构图,文件系统,网页之间的链接图等的分层数据。

CTE 递归查询构建需要三个部分:初始条件(也称为锚构件),递归调用表达式(引用 CTE 的递归查询),终止条件(停止递归构建的终止条件)。CTE 递归查询的伪代码如下:

WITH cte_name ( column_name [,...n] )AS(--Anchor member is defined 初始条件CTE_query_definition UNION ALL--Recursive member is defined referencing cte_name --递归调用表达式CTE_query_definition )-- Statement using the CTE-- 递归查询没有显式的递归终止条件,只有当递归子查询返回空结果集(没有数据行返回)或是超出了递归次数的最大限制时,才停止递归。SELECT *FROM cte_name

如下案例是使用递归查询行政区划的例子,详细代码可通过关注发送 “高级SQL” 获取样例代码。

with cte(Id,ParentID,Name,Level) as(select ID,ParentID,Name,0 as Levelfrom dbo.hierarchy where id=1

union allselect h.ID,h.ParentID,h.Name,c.Level+1 as Levelfrom dbo.hierarchy hinner join cte c on h.ParentID=c.id --where c.id!=h.ID)select *from cteorder by ParentID

以下是使用递归 CTE 从父集向子集查询得到上海市(包含)下的所有行政区划的信息示例:

行政区划查询

3,虚拟数字辅助表

数字辅助表是一个整数序列,可以用于生成日期和时间值序列,分裂值列表。通常建议在数据库中保存这样一张表,并填充尽可能多的数字,在需要的时候使用它。开发人员并不是所有的环境可以创建和向表中填充值以得到需要的逻辑,此时虚拟数字辅助表就派上了用场。

虚拟数字辅助表同样是通过创建内联表值型函数使用 CTE 公共表达式以及交叉连接创造的一张整数数字辅助表。下面就是数字辅助表的构建语句,在5级可以得到 4,294,967,296 行,满足了大多数的场景。

CREATE FUNCTION [dbo].[GetNums](@low AS BIGINT, @high AS BIGINT) RETURNS TABLEASRETURN  WITH    L0   AS (SELECT c FROM (SELECT 1 UNION ALL SELECT 1) AS D(c)),    L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),    L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),    L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),    L4   AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),    L5   AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),    Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum            FROM L5)  SELECT TOP(@high - @low + 1) @low + rownum - 1 AS n  FROM Nums  ORDER BY rownum;

GO

使用该函数可以产生实际需要的数字,比如我要获取100到110之间的数字,可以通过如下方法获得:

select * from dbo.getnums(100,110)

执行结果如下:

4,日期和时间值序列

在数据仓库的构建过程中,我们常常遇到生成一个日期和时间序列的需求,序列的范围是从开始日期到结束日期,且具有一定的时间间隔,比如 1天,12小时等。比如上一篇中介绍的数据仓库维度表中的日期维度表,可以借助上面实现的 GetNums 函数,接受输入 start 和 end 作为日期时间,使用 DateDiff 函数计算两个时间之间的时间间隔。调用 GetNums 函数,输入 low 为0, high 为时间间隔,产生最终的时间序列如下。

declare @start as date= '20220810',        @end as date = '20220820'select dateadd(day, n, @start) as dt from dbo.getnums(0, datediff(day,@start,@end)) as nums

执行结果如下,我们获取了从2022-08-10 开始的10天的日期数据

间隔为天的时间序列

加入间隔的单位是小时,调整查询语句如下:

declare @start as datetime2= '2022-08-10 00:00:00.0000000',        @end as datetime2 = '2022-08-20 12:00:00.0000000'select dateadd(hour, n*12, @start) as dt from dbo.getnums(0, datediff(hour,@start,@end)/12) as nums

间隔为12小时的时间序列

5,自联结

一个 SQL 表自行连接自己,你可能会感觉没有什么用处,但是实际在某些场景下又是非常常见。许多现实场景中,比如员工信息,产品类别等等层级信息,需要通过自联结查询符合某些特殊场景的数据。

比如一个对阵表,参赛队伍表中存储了所有的参赛队伍信息,明天所有的参赛队伍就要开始比赛,我们需要为所有的参赛队伍随机生成一份对阵表,这个 SQL 如何写呢?使用表的自联结就可以解决,如下为两种查询语句:

--注意关联条件,第一种查询简单高效select a.name, a.city, b.name, b.cityfrom team as a, team as bwhere a.name < b.nameorder by 1;

--第二种使用了窗口函数降序排列获取奇数数据select *from(select a.name as aname, a.city as acity, b.name as bname, b.city as bcity,row_number() over(order by a.name + b.name) rn from team as a, team as bwhere a.name <> b.name) resultwhere result.rn%2 = 1;

6,EXCEPT vs NOT IN

EXCEPT 和 NOT IN 用来比较两个查询或者表之间的行,但是他们之间存在细微的差别。

  1. EXCEPT 会去重复,NOT IN 不会,除非在 SELECT 语句中显式指定了去重;
  2. EXCEPT 比较的是所有列,如果查询的左侧是右侧具有不同数量的列,则查询会导致错误。此外 UNION, INTERSECT运算符组合的查询也必须具有相等数量的表达式。NOT IN 要求将一个表中的单个列或者子查询中的单个列进行比较,否则会导致错误。在进行多列比较时 NOT EXISTS 也是不错的选择;
  3. 如果右边的表中包含 NULL 值,NOT IN 会返回一个空结果集,除非在右边的表中进行了空值的处理,此时 EXCEPT 更好;

7,使用 CASE WHEN 实现行转列

行转列有两种实现方法,SQL Server 2005 版本退出了 PIVOT 函数之外,我们也可以使用 CASE WHEN 语句来实现行转列。例如,如果您有一个月列存储了当月收入情况,但是您希望为每个月创建一个单个列,则可以使用 CASE WHEN 实现数据重新格式化,以便每月都有一个收入列。

如下即为我们使用 CASE WHEN 可以实现的行转列功能。

Initial table:  +------+---------+-------+  | id   | revenue | month |  +------+---------+-------+  | 1    | 8000    | Jan   |  | 2    | 9000    | Jan   |  | 3    | 10000   | Feb   |  | 1    | 7000    | Feb   |  | 1    | 6000    | Mar   |  +------+---------+-------+  

Result table:  +------+-------------+-------------+-------------+-----+-----------+  | id   | Jan_Revenue | Feb_Revenue | Mar_Revenue | ... | Dec_Revenue |  +------+-------------+-------------+-------------+-----+-----------+  | 1    | 8000        | 7000        | 6000        | ... | null        |  | 2    | 9000        | null        | null        | ... | null        |  | 3    | null        | 10000       | null        | ... | null        |  +------+-------------+-------------+-------------+-----+-----------+

8,Rank vs Dense Rank vs Ntile vs Row Number

SQL 标准支持 4 种用于排名计算的窗口函数,分别是 RANK, DENSE_RANK, NTILE, ROW_NUMBER。我们使用下面的例子来了解这 4 个窗口函数的区别。

select id,testid,ROW_NUMBER() over( order by testid) as ROW_NUMBER_NO,RANK() over(order by testid) as RANK_NO,DENSE_RANK() over(order by testid) as DENSE_RANK_NO,Ntile(4) over ( order by testid) as NTILE_NOfrom testorder by testid
函数 区别
ROW_NUMBER 按照 testid 升序排列为每一个 testid 生成与之对应的一个序列数字且由小到大的不间断数字,每个序列数字是唯一的
RANK 按 testid 升序排列为每一个 testid 生成与之对应的一个排名数字,这些数字是从1开始由小到大排序(可能间断)。相同的 testid 生成的排名数字也相同,但是下一排名数字不是由之前的排名数字加1计算出的,而是排名总数即行数。
DENSE_RANK 按 testid 升序排列为每一个 testid 生成与之对应的一个排名数字,这些数字是从1开始由小到大排序的不间断数字(可能重复)。相同的 testid 生成的排名数字也相同,但是下一排名数字是由之前的排名数字加1计算出,而不是排名总数或行数。
NTILE 按 testid 升序排列并将所有testid平均分成4组(最后一组 testid 总数可能少于其它组),然后为每一个 testid 生成与之对应的一个所属组编号。组编号是从1开始由小到大的不间断数字

窗口函数区别

9,Delta 值计算

Delta 值是一个希腊字母,最早出现在数学领域中使用,在渐渐发展过程中,也延伸到了投资领域。金融领域常遇到 Delta 数据指标的计算,代表衡量数据指标的变化幅度,可以是环比,也可能是同比。变化幅度的计算中,Lead() 和 LAG() 也就发挥作用了。SQL Server 2012 版本开始,引入了 LEAD 和 LAG 函数。

LAG/LEAD (scalar_expression [,offset] [,default])      OVER ( [ partition_by_clause ] order_by_clause )

lag 和lead 有三个参数,第一个参数是列名,第二个参数是偏移的offset,第三个参数是 超出记录窗口时的默认值。下面我们用例子来:

WITH T AS ( SELECT 1 ID,10 NUM UNION ALL SELECT 1,20  UNION ALL SELECT 1,30  UNION ALL SELECT 2,40 UNION ALL SELECT 2,50 UNION ALL SELECT 2,60 ) SELECT ID,NUM, LAG(NUM) OVER (PARTITION BY ID ORDER BY NUM) AS OneDelta, LAG(NUM,1) OVER (PARTITION BY ID ORDER BY NUM) AS TwoDelta, LAG(NUM,2,0) OVER (PARTITION BY ID ORDER BY NUM) AS ThreeDelta FROM T;

按照上面的语句执行 LAG 函数,三个参数设置为不同值,详细执行结果如下:

LAG 用法

lead 函数与 lag 函数方向刚好相反,lead 是向前偏移指定的行数,默认都是1行。

WITH T AS ( SELECT 1 ID,10 NUM UNION ALL SELECT 1,20  UNION ALL SELECT 1,30  UNION ALL SELECT 2,40 UNION ALL SELECT 2,50 UNION ALL SELECT 2,60 ) SELECT ID,NUM, LEAD(NUM) OVER (PARTITION BY ID ORDER BY NUM) AS OneDelta, LEAD(NUM,1) OVER (PARTITION BY ID ORDER BY NUM) AS TwoDelta, LEAD(NUM,2,0) OVER (PARTITION BY ID ORDER BY NUM) AS ThreeDelta FROM T;

按照上面的语句执行 LEAD 函数,三个参数设置为不同值,详细执行结果如下:

LEAD 用法

10,Running Total 计算

Running total 中文名称为累积统计,是一种常见的需求,比如计算银行账户余额,跟踪仓库中产品的库存,跟踪累计销售额等等。SQL 中通常使用具有 SUM() 的窗口函数来计算运行总数。

DECLARE @T TABLE (ID char(1), Value int); 

INSERT INTO @T (ID, Value) VALUES ('A', 1), ('A', 1), ('B', 1), ('B', 1), ('B', 1), ('C', 1), ('C', 1); 

SELECT     ID     ,Value     ,SUM(Value) OVER (PARTITION BY ID ORDER BY Value      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal1 ,SUM(Value) OVER (ORDER BY Value     ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal2FROM @T ORDER BY ID, RunningTotal1, RunningTotal2; 

执行累积统计计算结果如下:

累积统计计算

我们这里大概列举了 SQL Server 中经常使用到的一些中高级 SQL 使用方法,可以解决项目中常见的大多数问题。如果你对其中的用法或者还有其他的问题,可以加我的个人微信一起探讨学习。


往期文章

数据仓库系列

数据仓库之维度表

数据仓库之日期维度表

Hive 系列

Hive 必知必会(一)介绍

Hive 必知必会(二)基本操作

Hive 必知必会(三)基本操作(续)

SQL Server 优化

一文读懂 SQL Server 执行计划

本文由 mdnice 多平台发布

SQL 开发的十个高级概念相关推荐

  1. 数据库:学好SQL必须知道的10个高级概念

    今天给大家分享学好SQL必须知道的10个高级概念. 1.常见表表达式(CTEs) 如果您想要查询子查询,那就是CTEs施展身手的时候 - CTEs基本上创建了一个临时表. 使用常用表表达式(CTEs) ...

  2. 线性回归中oracle性质,66.Oracle数据库SQL开发之 高级查询——使用线性回归函数...

    66.Oracle数据库SQL开发之 高级查询--使用线性回归函数 线性回归函数可以用普通最小平方回归曲线拟合一组数值对.线性回归函数可用于聚合.串口或报表函数. 如下图1: 例如: store@PD ...

  3. oracle 窗口函数查询条件,62.Oracle数据库SQL开发之 高级查询——使用分析函数之窗口函数...

    62.Oracle数据库SQL开发之 高级查询--使用分析函数之窗口函数 窗口函数可以计算一定的记录范围内.一定值域内.或者一段时间内的累积和以及移动平均值.查询返回一组记录,称为结果集.窗口这个术语 ...

  4. oracle报表查询sql,63.Oracle数据库SQL开发之 高级查询——使用报表函数

    63.Oracle数据库SQL开发之 高级查询--使用报表函数 报表函数可用于执行跨越分组和组内分区的计算. 报表计算:SUM,AVG,MAX,MIN,COUNT,VARIANCE,STDDEV.可以 ...

  5. 初学者Web介绍一些前端开发中的基本概念用到的技术

    Web开发是比较费神的,需要掌握很多很多的东西,特别是从事前端开发的朋友,需要通十行才行.今天,本文向初学者介绍一些Web开发中的基本概念和用到的技术,从A到Z总共26项,每项对应一个概念或者技术. ...

  6. 研究人脸识别技术必须知道的十个基本概念

    研究人脸识别技术必须知道的十个基本概念 实验室研究人脸技术多年,不仅在技术方面有很好的积累,而且在公司内外的业务中有众多应用.在与产品.商务.工程开发同事交流过程中发现:不管是"从图中找到人 ...

  7. SQL开发技巧(二) 【转】感觉他写的很好

    本文转自: http://www.cnblogs.com/marvin/p/DevelopSQLSkill_2.html 本系列文章旨在收集在开发过程中遇到的一些常用的SQL语句,然后整理归档,本系列 ...

  8. SQL开发管理工具,SQL Studio成数据库管理工具热门

    达梦数据库冲击IPO成功:麒麟软件等国产Linux桌面操作系统在国防.教育等行业达到百万规模级应用:阿里云计算操作系统取得重大突破,阿里云市场份额全球第三:宝兰德.中创等厂商的应用服务器中间件能够实现 ...

  9. 金仓数据库KingbaseES数据库开发指南(4. 面向应用程序的PL/SQL开发)

    目录 4.1. PL/SQL子程序和包的编写 ¶ 4.1.1. PL/SQL 子程序概述 ¶ 4.1.2. PL/SQL 包概述 ¶ 4.1.3. PL/SQL 单元概述 ¶ 4.1.4. 创建 PL ...

最新文章

  1. 深度学习Dropout技术分析
  2. Django中使用UpdateView修改数据后,返回列表页
  3. android 6.0 自定义application,Android6.0之App中的资源管理对象创建
  4. R语言基本操作函数(1)变量的基本操作
  5. linux查看ssh端口8222,下载服务器SSH被用户恶意猜密码了
  6. java 内部类 加载_java内部类及类加载顺序
  7. Java面向对象(七)包、内部类、垃圾回收机制
  8. 五、线程管理————GCD
  9. 元素加了position:absolute则该元素的text-align:center居中失效的解决办法
  10. 允许指定IP访问远程桌面
  11. 职业高中计算机网络试讲稿,2021教师资格证考试面试高中信息技术试讲稿——《建立数据库的基本过程》...
  12. 跟面试官侃了半小时 MySQL 事务,把原子性、一致性、持久性的实现都讲完了
  13. 在VC++中使用Tab Control控件
  14. 第三章:react 应用( 基于 react )
  15. 泰勒展开-常用优化实例
  16. LSB 图像隐写与提取算法
  17. 正则表达式在线生成网站推荐
  18. 当遇到火狐浏览器“建立安全连接失败”问题,处理方法
  19. 小程序webview应用实践
  20. jboss下ejb简介

热门文章

  1. 菊风云 | 天塞鹰眼——实时音视频质量监控的“锐利之眼”
  2. 数字疗法 | “接受”还是“拒绝”?心理治疗背后的数字干预
  3. Echarts图表效果图(Make A Pie替代)
  4. “瞎买量”时代终结,游戏运营推广该如何选择?
  5. 多个小电容并联取代大电解电容的作用
  6. 啊哈c语言指针,其实《啊哈C》这本书介绍的东西有一些省略了。。。
  7. 带参函数简单案例-电灯 arduino玛克君
  8. 操作系统 第4章 习题整理
  9. JAVA中去掉字符串中的空格
  10. Reader entry: �����乱码问题