SQL-Server使用点滴(三-方法篇)
除了基本的数据库,数据表,数据记录操作之外,SQL-Server还为我们提供了比较丰富的数据处理方法。这些方法包括:过程,函数,触发器,视图。
下面就针对这些方法做逐一介绍:
一,存储过程
Create Procedure ProName( --对应有Alter和Drop方法
@Param1 TypeName1=DefaultValue1,
@Param2 TypeName2=DefaultValue2,
...
@ParamN TypeNameN=DefaultValueN) --这里括号可以省略
With Encryption --加密改存储过程,该句可以省略掉
As
过程主体
--End
1, 存储过程可以看作是一个局部的临时回话,其中所有建立的临时表,变量都会在过程结束时自动释放掉。但是过程中可以使用过程外部的临时表(即调用过程的会话),
并且这些临时表不会在过程结束时释放。如果想使用外部变量,只有通过Output型参数进行传递,没办法直接使用。
2,存储过程的嵌套层数最大是32层,这点对于函数和触发器也是一样的,并且共享层数,使用@@NestLevel可以看到当前的嵌套层级; 存储过程最大的内容容量是128M,含注释。
3,可以用sp_helptext过程来查看存储过程内容(加密的只能通过工具SqlPrompt6.0以上工具查看),该内容系统存在系统表syscomments表中。
可以用sp_depends过程来查看存储过程中使用的对象列表;可以用sp_rename来为存储过程改名。
4,存储过程也可以创建成临时存储过程,只需要在名称前加#或##即可。临时存储过程会在创建该临时存储过程结束时释放,不能被其他用户调用,会占用tempDB空间。
5,存储过程中,如果进行了表结构或索引内容的更改,那么调用该存储过程的用户,必须具备这些表和索引的拥有权,否则无法正常执行。
6,存储过程中可以随意更改系统标记,例如:Set datefirst 1 --将星期一作为一周的第一天;Set Language N'Simplified Chinese',将系统语言设置为简体中文。
二,函数
函数定义相对过程较为复杂,常用的有三种,标量函数(返回标准定义数据类型),表值函数(返回一个表),多语句表值函数(返回表参与函数体)
1,标量函数
标量函数,即定义一个SQL标准类型的返回值,并用Return(参数)的模式进行返回。
Create Function FuncName( --对应有Alter和Drop方法
@Param1 TypeName1=DefaultValue1,
@Param2 TypeName2=DefaultValue2,
...
@ParamN TypeNameN=DefaultValueN) --这里括号可以省略
Returns ReturnType
With Encryption --加密函数,该句可以省略掉
As
Begin --注意这里的Begin和End不可以省略。
函数主体
Return(返回值) --这里返回的是一个ReturnType类型的值。
End
2,表值函数
定义的返回值是一个表,并且用Return(单记录集)来获取返回的表。
Create Function FuncName( --对应有Alter和Drop方法
@Param1 TypeName1=DefaultValue1,
@Param2 TypeName2=DefaultValue2,
...
@ParamN TypeNameN=DefaultValueN) --这里括号可以省略
Returns Table --注意,这里的Table是个固定保留字
With Encryption --加密函数,该句可以省略掉
As
Begin --注意这里的Begin和End不可以省略。
函数主体
Return(Select ...) --这里返回的是一个语句。
End
3,多语句表值函数
定义的返回值是一个表,这个表在定义返回值时,就确定表名和表结构,并且在函数主体中对这个表进行处理,返回时仅仅用一个不带参数的Return结束。
Create Function FuncName( --对应有Alter和Drop方法
@Param1 TypeName1=DefaultValue1,
@Param2 TypeName2=DefaultValue2,
...
@ParamN TypeNameN=DefaultValueN) --这里括号可以省略
Returns @TbName Table(ColList...) --注意,这里是一个完整的表定义
With Encryption --加密函数,该句可以省略掉
As
Begin --注意这里的Begin和End不可以省略。
函数主体 --函数主题中@TbName是可以参与语句运算的。
Return --这里是直接一个不带任何参数的返回。
End
4,函数规范中的约定
1>,函数不支持实参,只支持形参。
2>,函数参数的默认值在调用时不能缺省,必须用default来站位,这个跟过程不同。并且在调用函数的时候必须加拥有者,例如dbo.
3>,可以用sp_helptext,sp_depends,sp_rename来对参数进行基本操作。
4>,函数不允许更改系统标志位或使用非确定性的表达式。
例如: set datefirst 7; @@CPU_Busy; @@Max_Connections;@@Connections;@@Total_Errors;
Getdate(),NewId(),Rand()
5>,对于【多语句表值函数】的限制比较宽,除了常用的控制和赋值语句外,可以使用游标,Insert,Update,Delete语句,但是这些
语句也局限于处理局部变量和表,不允许将数据返回到外部。另外,可以使用Execute调用扩展存储过程。
三,触发器
触发器主要的功能是用来保证数据的完整性,这点跟外键的Reference的作用有点类似。不过它比外键或者Check约束更强大的是可以跨越几个表。
在处理的方法上,触发器算是比较复杂,而且很影响系统性能的一种方法,其执行的时候都是被动执行,常伴随随着表操作Insert,Update,Delete一起
执行。触发器不亦频繁使用,而且使用时要特别小心。
触发器从大类上分,主要有instead of 触发器和 After触发器,一般都以After触发器为主(默认选择)。触发器子功能又分为三种:insert,Update,Delete,
我们建Instead of 或After触发器时可以选择这三种触发器的一个子集。触发器创建格式如下:
Create Trigger TrNameOn (TbName 或 ViewName)
With Encryption
For 功能子集 或 instead of 功能子集 --这里For开头的代表是After触发器,Instead of代表instead触发器,两种只能选一种。子集是insert,Update,Delete
--中的一种或几种。
As
触发器方法主段 --这里会出现几张系统临时表,表名分别为:inserted,deleted。
--其中,insert子功能会使系统出现表Inserted;delete子功能会使系统出现表deleted;而update子功能会同时出现inserted和deleted
Go
这里主要针对After类型的触发器作一说明:
1,下面通过一个举例,来实现通过触发器,对一个表数据修改,插入,删除历史数据的保存。
先是建立了四个表,T_TRTest, T_TRTest_History, T_TRTest_Append, T_TRTest_Modify 第一个是数据表,后三个保存对应操作的历史记录。
IF not EXISTS(SELECT 1 FROM sysobjects WHERE NAME='T_TRTest' AND xtype='U')
CREATE TABLE T_TRTest --主测试表,用来建立触法器
(sid uniqueidentifier Default(Newid()) primary key,
TestData VARCHAR(10))
IF not EXISTS(SELECT 1 FROM sysobjects WHERE NAME='T_TRTest_History' AND xtype='U')
CREATE TABLE T_TRTest_History --历史表,记录删除的数据
(sid uniqueidentifier Default(Newid()) primary key,
msid UNIQUEIDENTIFIER, --用来保存测试表主键
TestData VARCHAR(10)IF not EXISTS(SELECT 1 FROM sysobjects WHERE NAME='T_TRTest_Append' AND xtype='U')
CREATE TABLE T_TRTest_Append --同步表,新增数据时,同时记录到这里
(sid uniqueidentifier Default(Newid()) primary key,
msid UNIQUEIDENTIFIER,
TestData VARCHAR(10))
IF not EXISTS(SELECT 1 FROM sysobjects WHERE NAME='T_TRTest_Modify' AND xtype='U')
CREATE TABLE T_TRTest_Modify --修改表,记录所有的修改细节
(sid uniqueidentifier Default(Newid()) primary key,
msid UNIQUEIDENTIFIER,
TestData VARCHAR(10),
OpSign VARCHAR(1) --用来记录操作类型,'I'为新增的数据,'D'为删除的数据。
)
GO
--==========分界线,上面是建表,下面是建出发器================================
---建立触发器,为了方便理解共建立三个独立事件的触发器
--先建立删除触发器
IF exists(select 1 from dbo.sysobjects where NAME='TRD_T_TRTest' AND xtype='TR')
DROP TRIGGER TRD_T_TRTest
GO
CREATE TRIGGER TRD_T_TRTest on T_TRTest --删除触发器
for DELETE
as
BEGIN
--把所有删除的数据都拷贝到历史表中
INSERT T_TRTest_History (msid,TestData)
SELECT sid, TestData FROM DELETED
END
GO
--再建立新增触发器
IF exists(select 1 from dbo.sysobjects where NAME='TRI_T_TRTest' AND xtype='TR')
DROP TRIGGER TRI_T_TRTest
GO
CREATE TRIGGER TRI_T_TRTest on T_TRTest --插入触发器
for INSERT
as
BEGIN
--把所有新增的数据都同时拷贝到新增记录表中
INSERT T_TRTest_Append (msid,TestData)
SELECT sid, TestData FROM INSERTED
END
GO
--最后建立修改触发器
IF exists(select 1 from dbo.sysobjects where NAME='TRU_T_TRTest' AND xtype='TR')
DROP TRIGGER TRU_T_TRTest
GO
CREATE TRIGGER TRU_T_TRTest on T_TRTest --更新触发器
for UPDATE
as
BEGIN
--先把所有新增的数据都拷贝到修改表中,并且操作标记设置为I
INSERT T_TRTest_Modify (msid,TestData,OpSign)
SELECT sid, TestData, 'I' FROM INSERTED
--再把所有删除的数据也拷贝到修改表中,并且操作标记设置为D
INSERT T_TRTest_Modify (msid,TestData,OpSign)
SELECT sid, TestData, 'D' FROM Deleted
END
GO
--INSERT T_TRTest(TestData) VALUES('AABBCC')
--INSERT T_TRTest(TestData) VALUES('BBCCDD')
--INSERT T_TRTest(TestData) VALUES('FFFFFF')
--UPDATE T_TRTest SET TestData='SSSSSS'
--WHERE TestData='FFFFFF'
--DELETE T_TRTest
--SELECT * FROM T_TRTest
--SELECT * FROM T_TRTest_History
--SELECT * FROM T_TRTest_Append
--SELECT * FROM T_TRTest_Modify
以上的例子分别说明了After触发器中的Update,Insert,Delete触发器的用法。
2,利用RollBack实现回滚,防止对特定的数据进行操作。同样举例如下:
IF not EXISTS(SELECT 1 FROM sysobjects WHERE NAME='T_TRTestRollBack ' AND xtype='U')
CREATE TABLE T_TRTestRollBack --主测试表,用来建立触法器
(sid uniqueidentifier Default(Newid()) primary key,
TestDataRollBack VARCHAR(10))
IF exists(select 1 from dbo.sysobjects where NAME='TRD_T_TRTestRollBack' AND xtype='TR')
DROP TRIGGER TRD_T_TRTestRollBack
GO
CREATE TRIGGER TRD_T_TRTestRollBack on T_TRTestRollBack --删除触发器
for DELETE
as
BEGIN
IF EXISTS(SELECT 1 FROM Deleted WHERE TestDataRollBack='AAA')
Rollback
END
GO
INSERT T_TRTestRollBack(TestDataRollBack)VALUES('AAA')
INSERT T_TRTestRollBack(TestDataRollBack)VALUES('BBB')
DELETE T_TRTestRollBack WHERE TestDataRollBack='BBB'
DELETE T_TRTestRollBack WHERE TestDataRollBack='AAA'
在这个例子中,我们会发现,试图删除内容为'AAA'的数据是永远删除不掉的,并且系统会报触发器回滚异常。
3,instead of触发器
Instead of 触发器会完全用触发器的主体语句去代替对表的Insert,Update,Delete等操作。其使用场景一般有两种,一是
防止误操作,这个比Rollback效率高,也不会报异常;另一个是保护特定的字段,不被操作。例如:
IF not EXISTS(SELECT 1 FROM sysobjects WHERE NAME='T_TRTestInstead ' AND xtype='U')
CREATE TABLE T_TRTestInstead --主测试表,用来建立触法器
(sid uniqueidentifier Default(Newid()) primary key,
TestDataInstead VARCHAR(10))
IF exists(select 1 from dbo.sysobjects where NAME='TRD_T_TRTestInstead' AND xtype='TR')
DROP TRIGGER TRD_T_TRTestInstead
GO
CREATE TRIGGER TRD_T_TRTestInstead
on T_TRTestInstead
instead of Delete
as
begin
IF EXISTS(SELECT 1 FROM Deleted WHERE TestDataInstead='AAA')
SELECT * FROM deleted
end
GO
IF exists(select 1 from dbo.sysobjects where NAME='TRD_T_TRTestInstead2' AND xtype='TR')
DROP TRIGGER TRD_T_TRTestInstead2
GO
CREATE TRIGGER TRD_T_TRTestInstead2
on T_TRTestInstead
instead of INSERT
as
begin
INSERT INTO T_TRTestInstead(TestDataInstead)
SELECT TestDataInstead FROM inserted
end
GO
INSERT T_TRTestInstead(sid,TestDataInstead)VALUES('00000000-0000-0000-0000-000000000000','AAA')
INSERT T_TRTestInstead(sid,TestDataInstead)VALUES('00000000-0000-0000-0000-000000000000','BBB')
INSERT T_TRTestInstead(sid)VALUES('00000000-0000-0000-0000-000000000000')
DELETE T_TRTestInstead WHERE TestDataInstead='BBB'
DELETE T_TRTestInstead WHERE TestDataInstead='AAA'
SELECT * FROM T_TRTestInstead
以上的例子建立了两个instead of触发器,我们在后面的执行代码中可以看到,主动插入Sid值时,全部失效了,系统会按照其默认的
DF约束增加值,这种方法有利于保护indentity,timestamp, 及计算数据字段不受其他语句的干扰;另外,我们发现数据根本删除不
掉了,并且删除数据总AAA内容的数据,还会直接显示出所有AAA内容的记录,原本的Delete功能已经消失了。
四,视图
Create View VwName
With Check Option --对View更新时即时改变数据,可省略
With Encryption --可省略
AS
具有 Select的语句段
Go
View 可以查看一个或多个表的内容,常用于使用频率比较高的表访问。设计中常用的方法是,一,将多个表当作一个表使用,这样有助于代码
和数据流程的简练。二,将一个表根据标志位和使用字段不同,拆作多个表(视图)使用,这样的好处是数据结构比较紧密,运行效率高。
在使用View中,应该注意以下几点:
1,不要企图使用Create View [其他数据库]..ViewName 来在其他数据库上建立View,只允许在当前数据库上建立View,但是可以在内容中
利用[其他数据库]的前缀来访问其他数据库中的表。
当然,也有种间接在其他服务器上建立View的方法: [其他数据库]..sp_executesql N' 创建View语句' 来实现跨数据库建视图。
2,视图的创建名称在sysobjects表;视图的创建语句在syscomments表;视图所用到的表在sysdepends表中。
这里需要注意的是调用sp_rename进行更改时,不会改变syscomments中的创建语句,可以用sp_helptext进行查看。
3,视图的语句有这样几个限制:
1>,不能包含Compute [By]字句。
2>,不能直接使用Order by,但是允许用Top N...Order By。
3>,不能使用into[表]。
4>,不能使用临时表或表变量。
4,如果想要让视图具备可更新性质(可写),那需要有以下几点限制:
1>,可以直接利用instead of 触发器来实现视图的更新。
2>,视图中不能包含聚合函数,Group by,Top N,Distinct 语句。
3>,列中不能有很复杂的计算列(非常规算法)。
4>,结果列,至少有一列是由实际表字段查询得到的,不能所有列都是纯数据列(例如常量'a')。
5>,分区视图,即由Union或Union All串起来的若干表组成的视图其可更新情况限制比较复杂,因用得少,这里不作表述。