T-SQL語言基礎
- 十、可程式設計對象
-
- 10.1 變量
-
- 10.1.1 變量的聲明與指派
- 10.1.2 對多個變量指派
- 10.2 批處理
-
- 10.2.1 批處理是語句分析的單元
- 10.2.2 批處理和變量
- 10.2.3 不能在同一批進行中編譯的語句
- 10.2.4 批處理是語句解析的單元
- 10.2.5 go n 選項
- 10.3 流程控制元素
-
- 10.3.1 if...else... 流程控制元素
- 10.3.2 while流程控制元素
- 10.4 遊标
- 10.5 臨時表
-
- 10.5.1 局部臨時表
- 10.5.2 全局臨時表
- 10.5.3 表變量
- 10.5.4 表類型
- 10.6 動态SQL
-
- 10.6.1 執行動态SQL的方式
-
- 10.6.1.1 方式一:exec指令
- 10.6.1.2 方式二:sp_executesql存儲過程
- 10.6.2 在pivot中使用動态SQL
- 10.7 例程
-
- 10.7.1 使用者定義函數
- 10.7.2 存儲過程
- 10.7.3 觸發器
-
- 10.7.3.1 DML觸發器
- 10.7.3.2 DDL觸發器
- 10.8 錯誤處理
十、可程式設計對象
10.1 變量
10.1.1 變量的聲明與指派
- 變量聲明
- declare @variateName as dataType
- 例如,declare @i as int;
- 指派
- 聲明後指派
- set @variateName = variateValue;
- 例如,set @i = 10;
- 聲明的同時指派
- declare @variateName as dataType = variateValue;
- 例如,declare @i as int = 10;
10.1.2 對多個變量指派
- 每個變量調用一次set
- 例如,declare @firstName as nvarchar(20) , @lastName as nvarchar(40);
- set @firstName = (select firstname from hr.employees where empid = 3);
- set @lastName = (select lastname from hr.employees where empid = 3);
- set語句要求
- 當為标量變量指派時,标量子查詢的結果必須是一個
- 否則會報錯
- 指派select語句
- select @firstName = firstname, @lastName = lastname from hr.employees where empid = 3;
- 當select語句傳回結果有多條時,變量最終的值為查詢結果最後一行的值
10.2 批處理
- 批處理
- 從用戶端應用程式發送到SQL Server的一組單條或多條T-SQL語句
- 是單個可執行的單元
- 批處理要經曆的處理階段
- 分析(文法檢查)
- 解析(檢查引用的對象和列是否存在、是否具有通路權限)
- 優化(作為一個執行單元)
- 批處理指令
10.2.1 批處理是語句分析的單元
- 批處理内部SQL語句若存在錯誤,則整個批處理都将無法執行
10.2.2 批處理和變量
10.2.3 不能在同一批進行中編譯的語句
- create
- default
- function
- procedure
- rule
- schema
- trigger
- view
- 需要将上述語句單獨放在一個批處理内部
10.2.4 批處理是語句解析的單元
- 如果對資料對象的架構定義進行修改,并試圖在同一批進行中對該資料對象進行處理,則SQL Server無法察覺到修改
- DML和DDL應分隔到不同的批進行中
10.2.5 go n 選項
10.3 流程控制元素
10.3.1 if…else… 流程控制元素
if year(current_timestamp) <> year(dateadd(day,1,current_timestamp))
begin
print 'Today is the last day of the year.'
end
else
begin
print 'Today is not the last day of the year.'
end
10.3.2 while流程控制元素
declare @i as int = 1;
while @i <= 10
begin
if @i = 1 continue;
if @i = 6 break;
print @i;
set @i += 1;
end;
10.4 遊标
- 遊标
- 遊标的缺點
- 遊标的優點
- 可以為每一行應用特定的操作
- 對于計算型的操作,在某些情況下遊标效率更佳
- 使用遊标的步驟
- 在某個查詢的基礎上聲明遊标
- 打開遊标
- 從第一個遊标記錄中把列值提取到指定的變量
- 循環周遊遊标,直至@@fetch_status<>0
- 關閉遊标
- 釋放遊标
declare c cursor fast_forward for
select custid,ordermonth,qty
from sales.custorders
order by custid,ordermonth;
open c
fetch next from c into @custid,@ordermonth,@qty;
select @prvcustid = @custid, @runqty = 0;
while @@fetch_status =0
begin
if @custid <> @prvcustid
select @prvcustid = @custid, @runqty = 0;
set @runqty += @qty;
insert into @result values(@custid,@ordermonth,@qty,@runqty);
fetch next from c into @custid,@ordermonth,@qty;
end
close c;
deallocate c;
10.5 臨時表
10.5.1 局部臨時表
- 特點
- 隻對建立它的會話在建立級和調用堆棧内部級是可見
- 會話斷開會自動删除
- 使用場景
- 需要臨時儲存中間結果
- 需要多次通路某個開銷昂貴的處理結果
- 聲明方式
if object_id('tempdb.dbo.#MyOrderTotalsByYear') is not null
drop table dbo.#MyOrderTotalsByYear;
go
select
year(o.orderdate) as orderyear,
sum(od.qty) as qty
into dbo.#MyOrderTotalsByYear
from sales.orders as o
join sales.orderdetails as oo
on od.orderid = o.orderid
group by year(orderdate);
select cur.orderyear, cur.qty as curyearqty,
prv.qty as prvyearqty
from dbo.#MyOrderTotalsByYear as cur
left outer join dbo.#MyOrderTotalsByYear as prv
on cur.orderyear = prv.orderyear + 1;
10.5.2 全局臨時表
- 特點
- 對所有會話都是可見的
- 會話斷開且沒有活動在引用全局臨時表時會自動删除
- 所有都可以操作全局臨時表,也可以删除
- 使用場景
- 聲明方式
create table dbo.##globals
(
id sysname not null primary key,
val sql_variant not null
);
10.5.3 表變量
- 類似局部臨時表
- 在tempdb中有對應的表作為其實體表示
- 隻對建立它的會話可見,但隻對目前批處理可見
- 類似變量
- 對于少量的資料,使用表變量更有意義,否則應該使用臨時表
declare @MyOrderTotalsByYear table
(
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 o.orderid = od.orderid
group by year(orderdate);
select cur.orderyear, cur.qty as curyearqty,
prv.qty as prvyearqty
from @MyOrderTotalsByYear as cur
left outer join @MyOrderTotalsByYear as prv
on cur.orderyear = prv.orderyear + 1;
10.5.4 表類型
create type dbo.ordertotalsbyyear as table
(
orderyear int not null primary key,
qty int not null
);
declare @MyOrderTotalsByYear as dbo.ordertotalsbyyear;
10.6 動态SQL
- 動态SQL
- 作用
- 自動化管理任務
- 查詢資料庫中繼資料,為其執行backup database
- 重用以前緩存過的執行計劃
- pivot的in子句
10.6.1 執行動态SQL的方式
10.6.1.1 方式一:exec指令
declare @sql as varchar(100);
set @sql = 'print '' this message
was printed by a dynamic sql batch.'';';
exec(@sql);
10.6.1.2 方式二:sp_executesql存儲過程
- sp_executesql
- 隻支援unicode字元
- 支援參數化代碼
- 由兩個輸入參數部分和一個參數指派部分構成
- 使用步驟
- 聲明并構造參數化字元串
- 指定輸入參數和輸出參數
- 為輸入參數和輸出參數指派
declare @sql as nvarchar(100),
@sumfreight as float;
set @sql=N'set @totalfreight = (select SUM(freight)
from Sales.Orders
where custid = @custid)';
exec sp_executesql
@stmt=@sql,
@params = N'@custid as int,@totalfreight as float output',
@custid=85,
@totalfreight = @sumfreight output;
select @sumfreight as sumfreight;
10.6.2 在pivot中使用動态SQL
declare
@sql as nvarchar(1000),
@orderyear as int,
@first as int;
declare c cursor fast_forward for
select distinct(year(orderdate)) as orderyear
from sales.orders
order by orderyear;
set @first = 1;
set @sql = N'select *
from (select shipperid,year(orderdate) as orderyear,freight
from sales.orders) as d
pivot(sum(freight) for orderyear in (';
open c
fetch next from c into @orderyear;
while @@fetch_status =0
begin
if @first = 0
set @sql += N','
else
set @first = 0;
set @sql += quotename(@orderyear);
fetch next from c into @orderyear;
end
close c;
deallocate c;
set @sql += N')) as p;';
exec(@sql);
10.7 例程
- 例程
- 為了計算結果或執行任務而對代碼進行封裝的一種程式設計對象
10.7.1 使用者定義函數
create function dbo.fn_age
(
@birthdate as datetime,
@eventdate as datetime
)
returns int
as
begin
return
datediff(year,@birthdate,@eventdate)
- case when 100*month(@eventdate)+day(@eventdate)
< 100*month(@birthdate)+day(@birthdate)
then 1 else 0
end
end
go
10.7.2 存儲過程
- 與UDF的差別
- 優點
- 邏輯封裝
- 控制安全性
- 可授予存儲過程的權限,而不是底層表的操作權限
- 可以做到有限制的操作
- 錯誤處理
- 提高執行性能
if object_id('sales.usp_getcustomerorders','P') is not null
drop proc sales.usp_getcustomerorders;
go
create proc sales.usp_getcustomerorders
@custid as int,
@fromdate as datetime ='19000101',
@todate as datetime ='99991231',
@numrows as int output
as
set nocount on;
select orderid,custid,empid,orderdate
from sales.orders
where custid = @custid
and orderdate >= @fromdate
and orderdate < @todate;
set @numrows = @@rowcount;
go
declare @rc as int;
exec sales.usp_getcustomerorders
@custid = 1,
@fromdate = '20070101',
@todate = '20080101',
@numrows = @rc output;
select @rc as numrows;
10.7.3 觸發器
- 一種特殊的存儲過程,不能被顯式執行
- 分類
- SQL Server中,觸發器是按語句觸發,而不是按被修改的行觸發
10.7.3.1 DML觸發器
- SQL Server支援兩種DML觸發器
- after 觸發器
- 隻能在持久化表上定義
- 在與之關聯的事件完成後觸發
- after insert
- after update
- instead of 觸發器
- 在觸發器代碼中,可以通路 inserted 和 deleted 的兩個表
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
10.7.3.2 DDL觸發器
- SQL Server支援在兩個作用域内建立DDL觸發器
- SQL Server隻支援 after 類型的DDL觸發器
create trigger trg_audit_ddl_events
on datebase for ddl_datebase_level_events
as
...
go
10.8 錯誤處理
- error_number()
- error_message()
- error_severity()
- error_state()
- error_line()
- error_procedure()
begin try
insert into dbo.employees(empid,empname,mgrid)
values(1,'Emp1',null);
end try
begin catch
if error_number() = 2627
begin
...
end
...
end catch