在编写T-SQL代码时,往往需要 临时存储某些结果集,下面给出三种方法:
3种方法比较
(一)、临时表:
需要在临时数据库TempDB中通过I/O操作来创建表结构,一旦用户退出SQL Server环境则自动被删除。
优点:能够长久存储数据,可以建立索引,和普通的物理表一样,能存储大量数据
缺点:不方便使用,使用完之后要手动的drop,不然就会一直存在(此次连接关闭后就没了)
- # 表示本地临时表,仅在当前会话中可见;
- ## 表示全局临时表,在所有会话中都可见;
--方法一:
SELECT
into #filterList
from dbo.table
--方法二:
CREATE table #tegeb ( nm int , amount decimal, sta varchar )
insert into #tegeb
SELECT nm, amount,sta
FROM dbo.order
with(NOLOCK)
-- 处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAST
-- with(NOLOCK) 可能把没有提交事务的数据也显示出来
-- with(READPAST) 会把被锁住的行不显示出来
SELECT * FROM #tegeb --临时表一直存在,直到链接关闭
Truncate table #tegeb --清空数据
DROP TABLE dbo.#tegeb --必须手动drop
(二)、表变量:
在内存中以表结构的形式存在,其定义与变量一致,其使用与表类似,不需要产生I/O。
DECLARE @temp table ( nm int , amount decimal, sta varchar )
insert into @temp
SELECT nm, amount,sta
FROM dbo.order
SELECT * FROM @temp
--单独运行最后一句,@temp已经不存在了
(三)、公用表表达式:Common Table Expression:
定义在内存中保存的临时存储结果集对象,不产生I/O,不需要按照表变量这样定义,使用方法和表类似。可以自己引用,也可以再查询中被多次引用。
- WITH AS-做子查询部分(subquery factoring)。
- 如果WITH AS所以定的表名被调用两次以上,则优化器会自动将WITH AS所获取的数据放入临时表里,如果只是被调用一次,则不会。
- WITH AS可以被紧跟着的一条SQL语句所使用多次,但不能被紧跟着的多条SQL语句使用。
- 如果将 CTE 用在属于批处理的一部分的语句中,那么在它之前的语句必须以分号结尾
CTE的定义语法如下,主要包括3个部分:
- Expression_name:CTE表达式的名称。
- Column_name:列名列表。
- CTE_query_definition:定义CTE结果集的Select查询语句
WITH expression_name [(column_name [,...n] )]
AS
(
cte_query_definition
)
根据微软对CTE好处的描述,可以归结为四点:
- 可以定义递归公用表表达式(CTE)
- 当不需要将结果集作为视图被多个地方引用时,CTE可以使其更加简洁
- GROUP BY语句可以直接作用于子查询所得的标量列
- 可以在一个语句中多次引用公用表表达式(CTE)
可以将公用表(CTE)表达式分为 递归 公用表表达式和 非递归 公用表表达式
非递归公用表表达式(CTE):
- 仅仅一次性返回一个结果集用于外部查询调用
- 并不在其定义的语句中调用其自身的CTE
非递归公用表表达式(CTE)的使用方式和视图以及子查询一致。
WITH CTE_Test
AS
(
SELECT * FROM Person_1
)
SELECT * FROM CTE_Test
可以在接下来一条语句中多次引用
WITH CTE_Test
AS
(
SELECT * FROM Person_1
)
SELECT *
FROM CTE_Test AS a --第一次引用
INNER JOIN CTE_Test AS b ON a.Id = b.Id --第二次引用
如果多条语句引用,会报错
WITH CTE_Test
AS
(
SELECT * FROM Person_1
)
SELECT * FROM CTE_Test
SELECT * FROM CTE_Test --这条语句会报错
当需要接下来的一条语句中引用多个CTE时,可以定义多个,中间用逗号分隔
WITH CTE_Test1
AS
(
SELECT * FROM Person_1
),
CTE_Test2
AS
(
SELECT * FROM Person_2
)
SELECT * FROM CTE_Test1
UNION
SELECT * FROM CTE_Test2
递归公用表表达式(CTE):
对于递归公用表达式来说,只需要在语句中定义两部分:
- 基本语句(定位点成员)
- 递归语句(递归成员)
递归 CTE 定义至少必须包含两个 CTE 查询定义,一个定位点成员和一个递归成员。 可以定义多个定位点成员和递归成员;但必须将所有定位点成员查询定义置于第一个递归成员定义之前。 所有 CTE 查询定义都是定位点成员,但它们引用 CTE 本身时除外。
•定位点成员必须与以下集合运算符之一结合使用:UNION ALL、UNION、INTERSECT 或 EXCEPT。 在最后一个定位点成员和第一个递归成员之间,以及组合多个递归成员时,只能使用 UNION ALL 集合运算符。
先建一张表栏目表如下,栏目Id,栏目名称,栏目的父栏目。
现在使用CTE查询其每个栏目是第几层栏目的代码如下:
WITH COL_CTE(Id,Name,ParentId,tLevel )
AS
(
--基本语句
SELECT Id,Name,ParentId,0 AS tLevel
FROM Col
WHERE ParentId = 0
UNION ALL
--递归语句
SELECT c.Id,c.Name,c.ParentId,ce.tLevel+1 AS tLevel
FROM COL as c
INNER JOIN COL_CTE AS ce ON c.ParentId = ce.Id --递归调用
)
SELECT * FROM COL_CTE
结果:
0表示顶级栏目,1就是1级栏目,语法非常优雅,就一个SELECT * FRON COL_CTE。
但是,这要有约束,否则如果无限制递归可以会消耗掉非常多的系统资源。下面来看看如何 限制递归的最大次数。
WITH COL_CTE(Id,Name,ParentId,tLevel )
AS
(
--基本语句
SELECT Id,Name,ParentId,0 AS tLevel
FROM Col
WHERE ParentId = 0
UNION ALL
--递归语句
SELECT c.Id,c.Name,c.ParentId,ce.tLevel+1 AS tLevel
FROM COL as c
INNER JOIN COL_CTE AS ce ON c.ParentId = ce.Id --递归调用
)
SELECT * FROM COL_CTE
OPTION(MAXRECURSION 2) --指定最大递归次数为2
【参考资料】
WITH common_table_expression (Transact-SQL)
T-SQL 公用表表达式(CTE)