Chapter 5 Table Expressions

一个表表达式(table expression)是一个命名的查询表达式,代表一个有效的关系表。SQL Server包括4种表表达式:派生表(derived tables)、公用表表达式(common table expressions (CTEs),)、视图(views)、内联表值函数(inline table-valued functions (inline TVFs))。使用表表达式的好处通常在于逻辑层面而非性能。

派生表(derived tables)

举个例子先:

  1. SELECT *
  2. FROM (SELECT custid, companyname
  3. FROM Sales.Customers
  4. WHERE country = N'USA') AS USACusts;

在这里你用了一个查询来定义了一个叫USACusts的派生表,它只存活于外部查询中,也就是当外部查询结束了,它也没了。

想用一个查询定义一个表表达式,这个查询必须满足以下几点:

1.Order is not guaranteed(不保证查出来的结果集有顺序,也就是说不能用ORDER BY,除非是用TOP或者OFFSET的时候).

2.All columns must have names.

3.All column names must be unique.这种情况通常发生在联接两个表的时候,如果两个表有相同名字的列,那么在SELECT中要分别给他们定义别名。

其实以上几点都基于这么一个事实:一个表表达式代表了一个关系。

本来需要写两遍某个表达式的SQL:

  1. SELECT
  2. YEAR(orderdate) AS orderyear,
  3. COUNT(DISTINCT custid) AS numcusts
  4. FROM Sales.Orders
  5. GROUP BY YEAR(orderdate);

现在可以只写一遍:

  1. SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  2. FROM (SELECT YEAR(orderdate) AS orderyear, custid
  3. FROM Sales.Orders) AS D
  4. GROUP BY orderyear;

之前说过,表表达式只是逻辑层面,SQL Server在执行时还是会把表表达式扩展为第一个要写两遍的那种形式。

关于用AS定义列名,还有另一种语法:

  1. SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  2. FROM (SELECT YEAR(orderdate), custid
  3. FROM Sales.Orders) AS D(orderyear, custid)
  4. GROUP BY orderyear;

但作者不推荐这种写法,因为不是很清晰。但是如果不再修改表表达式,只是拿它当一个黑箱使用,那么也许你可以这么写(因为你只关心列名是什么)。

当然,也可以嵌套多层派生表,如:

  1. SELECT orderyear, numcusts
  2. FROM (SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  3. FROM (SELECT YEAR(orderdate) AS orderyear, custid
  4. FROM Sales.Orders) AS D1
  5. GROUP BY orderyear) AS D2
  6. WHERE numcusts > 70;

但作者不建议这样,因为他说这样会problematic,还不如“写两遍”的写法:

  1. SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts
  2. FROM Sales.Orders
  3. GROUP BY YEAR(orderdate)
  4. HAVING COUNT(DISTINCT custid) > 70;

如果你想在FROM子句中定义多个派生表,然后把他们Join一下,是可以的,但是你不能:比如先定义好一个派生表叫A,然后再直接LEFT OUTER JOIN A(也就是不能refer to它的多个实例,只能一模一样的再重新写一遍)。

Common table expressions (CTEs)

要定义一个CTE:

  1. WITH <CTE_Name>[(<target_column_list>)]
  2. AS
  3. (
  4. <inner_query_defining_CTE>
  5. )
  6. <outer_query_against_CTE>;

举个例子:

  1. WITH USACusts AS
  2. (
  3. SELECT custid, companyname
  4. FROM Sales.Customers
  5. WHERE country = N'USA'
  6. )
  7. SELECT * FROM USACusts;

与派生表一样,当outer query完成时,CTE结束生命。

用CTE来完成刚才说的“嵌套多层的派生表”,可读性会更好一些:

  1. WITH C1 AS
  2. (
  3. SELECT YEAR(orderdate) AS orderyear, custid
  4. FROM Sales.Orders
  5. ),
  6. C2 AS
  7. (
  8. SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  9. FROM C1
  10. GROUP BY orderyear
  11. )
  12. SELECT orderyear, numcusts
  13. FROM C2
  14. WHERE numcusts > 70;

也就是说后定义的CTE可以引用之前定义的CTE。

同样地,CTE可以解决之前“在FROM中无法引用同一个派生表的多个实例”的问题。但最终SQL Server还是会多次扫描同一个表,如果你介意性能的话,可以把结果存到一个临时表或者表变量。

定义一个Recursive CTEs(递归CTE)如下:

  1. WITH <CTE_Name>[(<target_column_list>)]
  2. AS
  3. (
  4. <anchor_member>
  5. UNION ALL
  6. <recursive_member>
  7. )
  8. <outer_query_against_CTE>;

与正常CTE定义的唯一区别只在于AS括号里面的查询。<anchor_member>是第一次调用返回的结果,<recursive_member>是之后每一次调用的结果,直到为空。举个例子,如果你想得到一个员工的所有下属(直接或非直接),那么就:

  1. WITH EmpsCTE AS
  2. (
  3. SELECT empid, mgrid, firstname, lastname
  4. FROM HR.Employees
  5. WHERE empid = 2
  6. UNION ALL
  7. SELECT C.empid, C.mgrid, C.firstname, C.lastname
  8. FROM EmpsCTE AS P
  9. JOIN HR.Employees AS C
  10. ON C.mgrid = P.empid
  11. )
  12. SELECT empid, mgrid, firstname, lastname
  13. FROM EmpsCTE;

得到结果:



注意上面结果中mgrid是指这个人的manager的empid是多少。第一次调用就是返回第一行,这个结果作为第二次调用里面的EmpsCTE这个玩意儿,于是第二次调用返回第2,第3行,再第三次调用用第二次调用的结果集....以此类推。

Views(视图)

与上面的两种表表达式不同,Views和inline TVFs都会被存储为database objects,所以他们的生命周期更长。定义一个叫USACusts的视图:

  1. CREATE VIEW Sales.USACusts
  2. AS
  3. SELECT
  4. custid, companyname, contactname, contacttitle, address
  5. FROM Sales.Customers
  6. WHERE country = N'USA';

定义View的时候不要用SELECT *,因为如果之后又加了一个列到你SELECT FROM的表中的话,由于你的视图的metadata还是不变,所以你查询这个视图还是只能得到原来的列(可以用sp_refreshview或sp_refreshsqlmodule来刷新view的metadata)。

创建View的时候可以指定一些选项(跟在WITH 后面):

ENCRYPTION选项就是告诉SQL Server在存储这个东西的定义的时候,进行obfuscate(混淆)处理,可以用在create or alter views, stored procedures, triggers, and user-defined functions (UDFs)的时候,比如:

ALTER VIEW Sales.USACusts WITH ENCRYPTION AS...

然后如果你再:SELECT OBJECT_DEFINITION(OBJECT_ID('Sales.USACusts'));就会得到NULL。

SCHEMABINDING,当你指定了这个选项的话,有点像外键约束,就比如定义视图的查询是SELECT address FROM Sales.Customers,那么如果你想删掉这一行的话:

ALTER TABLE Sales.Customers DROP COLUMN address;你会得到错误。这个选项是个good practice。

CHECK OPTION,举个例子吧:默认情况下,你可以INSERT INTO Sales.USACusts这个视图一些country不是N'USA'的rows, 因为最终其实还是INSERT到实际的table中去,如果你不想让这样的INSERT(或类似的UPDATE)发生,就指定这个选项。

Inline Table-Valued Functions可以理解为有输入参数的视图,例如:

  1. CREATE FUNCTION dbo.GetCustOrders
  2. (@cid AS INT) RETURNS TABLE
  3. AS
  4. RETURN
  5. SELECT orderid, custid, empid, orderdate, requireddate,
  6. shippeddate, shipperid, freight, shipname, shipaddress, shipcity,
  7. shipregion, shippostalcode, shipcountry
  8. FROM Sales.Orders
  9. WHERE custid = @cid;

然后要用的话就:

  1. SELECT orderid, custid
  2. FROM dbo.GetCustOrders(1) AS O;

CROSS APPLY这个运算符有点像CROSS JOIN,它接受两个表,右边的表可以是一个表表达式,对于左边的表的每一行,都对应一个右边的表,然后合并起来,举个例子:

  1. SELECT C.custid, A.orderid, A.orderdate
  2. FROM Sales.Customers AS C
  3. CROSS APPLY
  4. (SELECT TOP (3) orderid, empid, orderdate, requireddate
  5. FROM Sales.Orders AS O
  6. WHERE O.custid = C.custid
  7. ORDER BY orderdate DESC, orderid DESC) AS A;

这个的意思就是:对于每个Customer(左边的表),返回他最新的三个订单(右边的表),查询结果为:



可以注意到,每个custid都有三行。如果是子查询的话,每个custid就只有一行了,你只能把每个Customer的最新的三个订单合并成一个标量(比如用 FOR XML PATH)。

如果对于左边表里面的某个列,右边的表是个空集,那么左边这一行就不会在整个的查询结果中,如果你想包含它们的话,就用OUTER APPLY,如果把上面的CROSS APPLY改成OUTER APPLY,那么查询结果为:



如果改用incline TVS的话会增加可读性和可维护性:

  1. CREATE FUNCTION dbo.TopOrders
  2. (@custid AS INT, @n AS INT)
  3. RETURNS TABLE
  4. AS
  5. RETURN
  6. SELECT TOP (@n) orderid, empid, orderdate, requireddate
  7. FROM Sales.Orders
  8. WHERE custid = @custid
  9. ORDER BY orderdate DESC, orderid DESC;

然后就可以用了:

  1. SELECT
  2. C.custid, C.companyname,
  3. A.orderid, A.empid, A.orderdate, A.requireddate
  4. FROM Sales.Customers AS C
  5. CROSS APPLY dbo.TopOrders(C.custid, 3) AS A;

《SQL Server 2012 T-SQL基础》读书笔记 - 5.表表达式的更多相关文章

  1. SQL Server Window Function 窗体函数读书笔记二 - A Detailed Look at Window Functions

    这一章主要是介绍 窗体中的 Aggregate 函数, Rank 函数, Distribution 函数以及 Offset 函数. Window Aggregate 函数 Window Aggrega ...

  2. SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第1部分)

    为了缩小读取操作所涉及范围,本文首先着眼于简单的SELECT查询,然后引入执行更新操作有关的附加过程.最后你会读到,优化性能时SQLServer使用还原工具的相关术语和流程. 关系和存储引擎 如图所示 ...

  3. SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第2部分)

    计划缓存(Plan Cache) 如果SQL Server已经找到一个好的方式去执行一段代码时,应该把它作为随后的请求重用,因为生成执行计划是耗费时间且资源密集的,这样做是有有意义的. 如果没找到被缓 ...

  4. SQL Sever 各版本下载 SQL Server 2012下载SQL Server 2008下载SQL Server 2005

    SQL Server 2012SQL Server 2012 开发版(DVD)(X64,X86)(中文简体)ed2k://|file|cn_sql_server_2012_developer_edit ...

  5. sql server 2012 导出sql文件

    导出表数据和表结构sql文件 在工作中,经常需要导出某个数据库中,某些表数据:或者,需要对某个表的结构,数据进行修改的时候,就需要在数据库中导出表的sql结构,包括该表的建表语句和数据存储语句!在这个 ...

  6. SQL Server Window Function 窗体函数读书笔记一 - SQL Windowing

    SQL Server 窗体函数主要用来处理由 OVER 子句定义的行集, 主要用来分析和处理 Running totals Moving averages Gaps and islands 先看一个简 ...

  7. SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第3部分)(完结)

    一个简单的更新查询 现在应该知道只读取数据的查询生命周期,下一步来认定当你需要更新数据时会发生什么.这个部分通过看一个简单的UPDATE查询,修改刚才例子里读取的数据,来回答. 庆幸的是,直到存取方法 ...

  8. SQL Server 2012 - 动态SQL查询

    动态SQL的两种执行方式:EXEC @sql 和 EXEC sys.sp_executesql @sql DECLARE @c_ids VARCHAR(200) SET @c_ids ='1,2' - ...

  9. SQL Server 2012 - 数据库的基础操作

    数据库基本操作 --新建数据库卡 use master go create database SchoolDB on ( Name=SchoolDB, FileName='D;\DB\SchoolDB ...

随机推荐

  1. 【7.24校内交流赛】T1&T2

    T1: 一个脑洞很大的题,将输入的所有数异或起来输出就好了: (话说我为什么这么喜欢用异或啊) #include<bits/stdc++.h> using namespace std; i ...

  2. linux-导入python自定义模块的使用方法

    #!/usr/bin/python # -*- coding:utf -8 -*- import os import sys sys.path.append("/h/s/compare_f& ...

  3. 五、JVM — 类加载器

    回顾一下类加载过程 类加载器总结 双亲委派模型 双亲委派模型介绍 双亲委派模型实现源码分析 双亲委派模型的好处 如果我们不想要双亲委派模型怎么办? 自定义类加载器 推荐 回顾一下类加载过程 类加载过程 ...

  4. P2516 [HAOI2010]最长公共子序列

    传送门 看到数据范围,显然 $n^2$ 的 $dp$... 设 $f[i][j]$ 表示 $A$ 串考虑了前 $i$ 位,$B$ 串考虑了前 $j$ 位,最优情况下的方案数 但是好像没法判断转移来的是 ...

  5. js实现404页面倒计时跳转

    <script type="text/javascript"> (function(){ var i=5,timer=null,number=document.getE ...

  6. k3 cloud注册插件的时候提示,请选择一个有效的插件程序集

    插件类的访问类型需要是public类型的,由于你的插件类没有标记为public类型,所以注册的时候并没有发现有插件,就是下面的单据体没有加载出数据.标记public之后,下面会有你的插件,然后选择对应 ...

  7. 锋利的jQuery ——jQuery选择器(二)

    一.jQuery选择器 1)CSS选择器 CSS选择器有:1>标签选择器  E{CSS规则} 2>ID选择器   #ID{CSS规则} 3>类选择器  E.className{CSS ...

  8. Bootstrap table 实现树形表格,实现联动选中,联动取消

    公司最近有需求要做树形式table.因为是前后端不分离项目,且之前已经引入了bootstrap table插件,现把实现方式分享一下: <!DOCTYPE HTML> <html l ...

  9. React全家桶入门

    http://blog.csdn.net/column/details/14545.html

  10. APP元素定位工具weditor

    github地址https://github.com/openatx/weditor python -m weditor --shortcut adb devices 在页面上输入手机设备号,点Con ...