《SQL Server 2012 T-SQL基础》读书笔记 - 10.可编程对象
Chapter 10 Programmable Objects
声明和赋值一个变量:
DECLARE @i AS INT;
SET @i = 10;
变量可以让你暂时存一个值进去,然后之后再用,作用域是同一个Batch(批处理)。
也可以这么用:
DECLARE @firstname AS NVARCHAR(10), @lastname AS NVARCHAR(20);
SELECT
@firstname = firstname,
@lastname = lastname
FROM HR.Employees
一个batch是从客户端发送到SQL Server的一条或多条T-SQL语句,作为一个单独的单元(我的理解就是作为一个整体)来解析并执行。batch经历的阶段有:语法分析,解析(检查引用的对象和列是否存在),检查是否有访问权限,优化。
batch和transaction(事务)是完全不同的概念。一个Batch里可以有很多个事务;也可以把一个事务分散到很多个batch里去。上面的加粗部分是我自己感觉的batch和事务的区别。
像SSMS这种客户端工具有GO这个命令,这个命令signals the end of a batch(是一个batch结束的信号)。注意:GO命令是一个客户端命令而不是一个T-SQL Server命令。GO可以加参数,比如GO 100就是执行一百次这个batch。像ADO.NET这些框架也会提供方法for submitting a batch of code to SQL Server for execution(用于提交一个batch的代码到SQL Server执行)。
举个例子:
-- Valid batch
PRINT 'First batch';
USE TSQL2012;
GO
-- Invalid batch
PRINT 'Second batch';
SELECT custid FROM Sales.Customers;
SELECT orderid FOM Sales.Orders;
GO
-- Valid batch
PRINT 'Third batch';
SELECT empid FROM HR.Employees;
注意第二个batch里面有个语法错误(FROM拼成了FOM),所以这整一个batch都没有被提交到 SQL Server执行(the whole batch is not submitted to SQL Server for execution),也就是说它的两个SELECT都没有被执行,而它前后两个Batch都被执行了。
规定有一些语句(大多是CREATE)不能和其他语句一起处于同一个批处理。
由于批处理中的语句作为一个整体进行解析,所以比如以下SQL语句:
ALTER TABLE dbo.T1 ADD col2 INT;
SELECT col1, col2 FROM dbo.T1;
如果以上两条语句作为一个batch的话,会解析失败。因为解析的时候还没有真正执行,所以解析到SELECT那句的时候,col2并不存在。所以应该在SELECT前面加个GO。
控制流:
IF...ELSE...。如果IF里面的东西算出来是FALSE或者UNKNOWN,那么就会运行ELSE块。所以注意NULL的情况。如果你要写多个语句,那么要用BEGIN,END,其实就相当于编程语言里面的大括号,比如:
IF DAY(SYSDATETIME()) = 1
BEGIN
PRINT '....';
PRINT '....';
END
ELSE
BEGIN
PRINT '....';
PRINT '....';
END
可以ELSE IF,也可以嵌套IF ELSE。
WHILE里面可以BREAK和CONTINUE,语法如下:
DECLARE @i AS INT = 1;
WHILE @i <= 10
BEGIN
IF @i = 6 BREAK;
PRINT @i;
SET @i = @i + 1;
END;
输出1 2 3 4 5。
游标(Cursor):一个没有ORDER BY的query返回一个set(或multiset),而一个带ORDER BY的query返回一个游标。T-SQL提供一个游标对象,可以让你从一个query的结果集中一行一行地处理,并根据指定顺序。作者不建议使用游标,原因如下:1.违背了集合论 2.造成性能损失 3.可读性不好。所以能不用尽量不用。用游标好比钓鱼,而用集合就像用网捕鱼。一般是当需要对每一行都做点工作的时候,才使用游标。例子不写了,需要的时候参考吧。
SQL Server提供了三种临时表:local temporary tables, global temporary tables, and table variables。他们都是被创建在tempdb数据库中。当你需要存储某些中间结果,或者说某个查询很昂贵,而且你要多次查询的话,可以考虑临时表。
一个local temporary table的scope有点类似于编程语言中的local variable,比如四个存储过程:Proc1调用Proc2,Proc2调用Proc3,Proc3调用Proc4,如果在Proc2中创建了一个local temporary table(并且在调用Proc3之前),那么这个临时表对Proc2,3,4都是可见的,但对Proc1不可见,Proc2完成时会被自动销毁。范围最大大到当前session。创建临时表只要把它命名为#tablename:
IF OBJECT_ID('tempdb.dbo.#MyOrderTotalsByYear') IS NOT NULL
DROP TABLE dbo.#MyOrderTotalsByYear;
GO
CREATE TABLE #MyOrderTotalsByYear
(
orderyear INT NOT NULL PRIMARY KEY,
qty INT NOT NULL
);
然后就可以像普通表一样用了:
INSERT INTO #MyOrderTotalsByYear(orderyear, qty)
SELECT
YEAR(O.orderdate) AS orderyear,
SUM(OD.qty) AS qty
FROM Sales.Orders AS O
JOIN Sales.OrderDetails AS OD
ON OD.orderid = O.orderid
GROUP BY YEAR(orderdate);
如果你从另一个session想访问这个临时表的话,你做不到。
Global Temporary Tables对所有session可见,当创建这个table的session断开连接,并且there are no active references to the table(没有对这个table有效的引用)时,被自动销毁。创建的时候只要##tablename就行了:
CREATE TABLE dbo.##Globals
(
id sysname NOT NULL PRIMARY KEY,
val SQL_VARIANT NOT NULL
);
一般是当你想跟别人共享的时候用。
Table variables有点类似于local temporary tables,它会被存到tempdb中,而不是只存在于内存中。但是它的scope更有限:只对当前Batch可见,对之后调用的,比如别的存储过程都不可见。如果回滚事务,对temporary tables的修改会被roll back,但对table variables的已经完成的修改不会被roll back。创建语法如下:
DECLARE @MyOrderTotalsByYear TABLE
(
orderyear INT NOT NULL PRIMARY KEY,
qty INT NOT NULL
);
由于性能的原因,数据量小的话就用table variable,否则用local temporary tables。
SQL Server 2008及以上支持Table Types,比如:
CREATE TYPE dbo.OrderTotalsByYear AS TABLE
(
orderyear INT NOT NULL PRIMARY KEY,
qty INT NOT NULL
);
然后你就可以:DECLARE @MyOrderTotalsByYear AS dbo.OrderTotalsByYear;
Table Types不仅可以用来定义Table,也可以作为存储过程和用户自定义函数的输入参数的类型。
SQL Server允许你创建一个字符串,这个字符串里面是a batch of T-SQL语句,然后可以执行它,这个叫dynamic SQL。可以用EXEC这个命令,它接受一个字符串:
DECLARE @sql AS VARCHAR(100);
SET @sql = 'PRINT ''This message was printed by a dynamic SQL batch.'';';
EXEC(@sql);
也可以用sp_executesql这个存储过程,首先看一下用法:
DECLARE @sql AS NVARCHAR(100);
SET @sql = N'SELECT orderid, custid, empid, orderdate
FROM Sales.Orders
WHERE orderid = @orderid;';
EXEC sp_executesql
@stmt = @sql,
@params = N'@orderid AS INT',
@orderid = 10248;
结果:
orderid | custid | empid | orderdate |
---|---|---|---|
10248 | 85 | 5 | 2006-07-04 00:00:00.000 |
@stmt就是你要执行的字符串,@params是输入和输出参数的声明,然后给这些声明的参数赋值(逗号隔开)。那么用这个sp_executesql有什么好处?一是可以防止SQL注入,因为用户输入只能作为操作数(通过 @params)。二是可以复用cached execution plans。cached execution plans就是一个physical processing plan,SQL Server为了某个query产生的,它包括一些列指令:用什么索引、什么算法、目标是哪个表什么的。如果一个query跟以前执行过的某个query一模一样,那么这个cached execution plan就可以得到复用,所以说最好的可以复用cached execution plan的方法就是一个接受输入参数的存储过程,因为即使参数变了,query string不变。类似地,用sp_executesql也会增加复用cached execution plans的机会。
Using PIVOT with Dynamic SQL这部分跳过
Routines(例程)是为了计算结果或者执行任务而对代码封装的可编程对象,SQL Server支持三种Routine:user-defined functions, stored procedures, and triggers。
SQL Server提供给你两种选择来develop一个routine:T-SQL或者.NET code。如果有很多data manipulation,T-SQL更好;如果有很多iterative logic(估计就是foreach这种), 字符串操作或者很密集的计算的时候,.NET code更好。
User-Defined Functions用作封装一些计算某些结果的逻辑,我们简称UDF。UDF不允许有side effects,比如修改数据库里的数据,以及一些会导致副作用的函数:比如RANK和NEWID。用法:
CREATE FUNCTION dbo.GetAge
(
@birthdate AS DATE,
@eventdate AS DATE
)
RETURNS INT
AS
BEGIN
RETURN 基于输入参数做一些计算得到一个值
END;
END;
然后就可以用了:
SELECT empid, dbo.GetAge(birthdate, SYSDATETIME()) AS age
FROM HR.Employees;
除了返回一个scalar,也可以返回一个table value,这样的话就可以被用在FROM子句。
Stored Procedures比UDF更爽,因为可以包含副作用,也就是可以修改数据,对数据库构架进行修改什么的。相比普通的代码,存储过程的好处在于:封装了逻辑、可以对某个用户赋予可以调用某个存储过程的权限、可以提高性能(由于cached execution plan的复用)并减少网络间传输的数据量(因为你只要告诉数据库你想调用的存储过程名就行了)。用法举例:
CREATE PROC Sales.GetCustomerOrders
@custid AS INT,
@fromdate AS DATETIME = '19000101', --设置默认值
@todate AS DATETIME = '99991231', --设置默认值
@numrows AS INT OUTPUT --这里的OUTPUT表示这个是输出参数
AS
SET NOCOUNT ON; --意思是不要显示“多少行affected”的消息
SELECT orderid, custid, empid, orderdate
FROM Sales.Orders
WHERE custid = @custid
AND orderdate >= @fromdate
AND orderdate < @todate;
SET @numrows = @@rowcount;
使用:
DECLARE @rc AS INT;
EXEC Sales.GetCustomerOrders
@custid = 1,
@fromdate = '20070101',
@todate = '20080101',
@numrows = @rc OUTPUT;
SELECT @rc AS numrows;
结果:
Triggers(触发器)封装的代码是只有当某些个事件发生,才会被执行。SQL Server支持两种可以给触发器的事件:data manipulation events(DML,比如INSERT什么的)、data definition events(DDL,比如CREATE TABLE什么的)。在一个事务中,如果发生了某个会trigger某个触发器的事件,然后又roll back了,那么触发器造成的改变也会被roll back。在SQL Server中,一个语句触发一个触发器。举例:
CREATE TRIGGER trg_T1_insert_audit ON dbo.T1 AFTER INSERT
AS
SET NOCOUNT ON;
INSERT INTO dbo.T1_Audit(keycol, datacol)
SELECT keycol, datacol FROM inserted;
GO
解释一下:trg_T1_insert_audit是触发器的名字。ON就是你要监听哪个表的事件。AFTER INSERT意思就是在INSERT之后触发(当然MERGE也有可能)。这里的AFTER也可以换成INSTEAD,可以用来代替相关的事件操作(但是书上没给例子),AFTER只能定义在持久化的表上,而INSTEAD可以定义在持久化的表和视图上。后面的FROM inserted的inserted是指有新值的表,也可以换成deleted,表示有老值的表。
再举一个基于DDL的例子(复习的时候可以直接跳过,自认为不太重要):
CREATE TRIGGER trg_audit_ddl_events
ON DATABASE FOR DDL_DATABASE_LEVEL_EVENTS
AS
SET NOCOUNT ON;
DECLARE @eventdata AS XML = eventdata();
INSERT INTO dbo.AuditDDLEvents(
posttime, eventtype, loginname, schemaname,
objectname, targetobjectname, eventdata)
VALUES(
@eventdata.value('(/EVENT_INSTANCE/PostTime)[1]', 'VARCHAR(23)'),
@eventdata.value('(/EVENT_INSTANCE/EventType)[1]', 'sysname'),
@eventdata.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname'),
@eventdata.value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname'),
@eventdata.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'),
@eventdata.value('(/EVENT_INSTANCE/TargetObjectName)[1]', 'sysname'),
@eventdata);
GO
ON DATABASE是指在数据库scope(还有个server scope,当你CREATE DATABASE的时候就是server scope),DDL_DATABASE_LEVEL_EVENTS是指所有的DDL事件。EVENTDATA()这个函数可以给你所有关于事件的信息(XML类型)。然后用XQuery表达式来提取其中的某些属性。然后你执行几个CREATE TABLE和ALTER TABLE的语句,然后再查一下:SELECT * FROM dbo.AuditDDLEvents;,得到结果:
(部分结果)
错误处理。语法如下:
BEGIN TRY
PRINT 10/2;
PRINT 'No error';
END TRY
BEGIN CATCH
PRINT 'Error';
END CATCH;
执行的流程和编程语言中一模一样,所以不再赘述。错误处理中最常用的估计就是ERROR_NUMBER()函数,它会返回一个数字,代表某种错误,比如2627是主键冲突,具体查文档吧。还有其他很多函数,比如ERROR_MESSAGE()代表错误信息,ERROR_SEVERITY()代表严重程度,等等。SQL Server 2012开始也可以用THROW;来在CATCH块里面把异常re-throw出来。
《SQL Server 2012 T-SQL基础》读书笔记 - 10.可编程对象的更多相关文章
- SQL Server Window Function 窗体函数读书笔记二 - A Detailed Look at Window Functions
这一章主要是介绍 窗体中的 Aggregate 函数, Rank 函数, Distribution 函数以及 Offset 函数. Window Aggregate 函数 Window Aggrega ...
- SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第1部分)
为了缩小读取操作所涉及范围,本文首先着眼于简单的SELECT查询,然后引入执行更新操作有关的附加过程.最后你会读到,优化性能时SQLServer使用还原工具的相关术语和流程. 关系和存储引擎 如图所示 ...
- SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第2部分)
计划缓存(Plan Cache) 如果SQL Server已经找到一个好的方式去执行一段代码时,应该把它作为随后的请求重用,因为生成执行计划是耗费时间且资源密集的,这样做是有有意义的. 如果没找到被缓 ...
- 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 ...
- sql server 2012 导出sql文件
导出表数据和表结构sql文件 在工作中,经常需要导出某个数据库中,某些表数据:或者,需要对某个表的结构,数据进行修改的时候,就需要在数据库中导出表的sql结构,包括该表的建表语句和数据存储语句!在这个 ...
- SQL Server Window Function 窗体函数读书笔记一 - SQL Windowing
SQL Server 窗体函数主要用来处理由 OVER 子句定义的行集, 主要用来分析和处理 Running totals Moving averages Gaps and islands 先看一个简 ...
- SQL Server 2012:SQL Server体系结构——一个查询的生命周期(第3部分)(完结)
一个简单的更新查询 现在应该知道只读取数据的查询生命周期,下一步来认定当你需要更新数据时会发生什么.这个部分通过看一个简单的UPDATE查询,修改刚才例子里读取的数据,来回答. 庆幸的是,直到存取方法 ...
- SQL Server 2012 - 动态SQL查询
动态SQL的两种执行方式:EXEC @sql 和 EXEC sys.sp_executesql @sql DECLARE @c_ids VARCHAR(200) SET @c_ids ='1,2' - ...
- SQL Server 2012 - 数据库的基础操作
数据库基本操作 --新建数据库卡 use master go create database SchoolDB on ( Name=SchoolDB, FileName='D;\DB\SchoolDB ...
随机推荐
- python 三元表达式
python 三元表达式(ternary expression) 把 if-else块 写到一行或者一个表达式中 并且产生一个值 value = true if condition else fal ...
- 万万没想到,Spring Boot 竟然这么耗内存!
Spring Boot总体来说,搭建还是比较容易的,特别是Spring Cloud全家桶,简称亲民微服务. 但在发展趋势中,容器化技术已经成熟,面对巨耗内存的Spring Boot,小公司表示用不起. ...
- webpack初体验之模块化开发
写在前面的话 上次写过一篇关于webpack入门的博客,当时只是说借助node来完成开发,并用webpack打包以让浏览器识别.其实其主要思想就是实现前端模块化开发. 众所周知,历史上,JavaScr ...
- Lucky Boy
Lucky Boy Problem Description Recently, Lur have a good luck. He is also the cleverest boy in his sc ...
- 各类最新Asp .Net Core 项目和示例源码
1.网站地址:http://www.freeboygirl.com2.网站Asp .Net Core 资料http://www.freeboygirl.com/blog/tag/asp%20net%2 ...
- Python 实用脚本
Python 实用脚本 脚本写的好,下班下得早!程序员的日常工作除了编写程序代码,还不可避免地需要处理相关的测试和验证工作. 例如,访问某个网站一直不通,需要确定此地址是否可访问,服务器返回什么,进而 ...
- /cat/cpuinfo信息查看
# 总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数 # 查看物理CPU个数cat /proc/cpuinfo| g ...
- 纯手写实现ajax分页功能
前言 最近用到了这个功能,百度大半天,网上的不是有各种问题就是需要引入其他的插件,无奈,只能自己写,大致功能已经完成.前端页面用bootstrap做样式,分页功能用ajax实现,没用其他插件哦,只引入 ...
- bash_profile和bashrc区别
[.bash_profile 与 .bashrc 的区别].bash_profile is executed for login shells, while .bashrc is executed f ...
- PC端网站微信扫码登录
需求分析:用户通过扫描我们网页的二维码,如果已经绑定我们平台的账户,即成功进入首页,否则提示先绑定个人微信账号. 1.绑定微信账号:是通过关注微信公众号实现绑定个人微信账号.首先通过后台接口获取到ti ...