走向面試之數據庫基礎:三、SQL進階之變量、事務、存儲過程與觸發器


一、變量那點事兒

1.1 局部變量

  (1)聲明局部變量

DECLARE @變量名  數據類型
DECLARE @name varchar(20)
DECLARE @id int

  (2)為變量賦值

SET @變量名 =--set用於普通的賦值
SELECT @變量名 =--用於從表中查詢數據並賦值,,可以一次給多個變量賦值

SET @name=‘張三’
SET @id = 1
SELECT @name = sName FROM student WHERE sId=@id

  (3)輸出變量的值

  SELECT 以表格的方式輸出,可以同時輸出多個變量;而PRINT 則是以文本的方式輸出,一次只能輸出一個變量的值

SELECT @name,@id
PRINT @name
PRINT @id
print @name,@id  --錯誤!!

1.2 全局變量

  (1)關於全局變量與局部變量

  局部變量:

  ①局部變量必須以標記@作為前綴 ,如@Age int;

  ②局部變量需要先聲明,再賦值

  全局變量(系統變量):

  ①全局變量必須以標記@@作為前綴,如@@version;

  ②全局變量由系統定義和維護,我們只能讀取,不能修改全局變量的值;

  (2)有哪些全局變量?

補充:@@error變量,在每次執行完SQL語句后,都會為@@error變量賦值,如果上次執行的SQL語句有錯,則將@@errro賦值為一個不為0的值,否則(執行沒錯),則將@@error賦值為0.

  (3)怎么使用全局變量

select @@LANGUAGE as '當前使用語言'
select @@SERVERNAME as '當前服務器名稱'
select @@TRANCOUNT as '當前連接打開的事務數'
select @@MAX_CONNECTIONS as '可以同時連接的最大數目'
select @@VERSION as '當前服務器版本'
select @@ERROR as '最后一個T-SQL錯誤的錯誤號'

二、選擇與循環:if(小蘋果) begin 一直聽根本停不下來 end 

2.1 無處不在的 IF ELSE

  (1)條件選擇語法

IF(條件表達式)
  BEGIN --相當於C#里的{
    語句1  ……
  END --相當於C#里的}
ELSE
 BEGIN
    語句1
    ……
  END
View Code

  (2)假設我們有一張選課成績表SC,其中包括三個字段{S#,C#,Score},其中S#為學號,C#為課程號,而Score則為成績。S#為Student表的外鍵,C#為課程表的外鍵。那么,根據這三張表,我們有一個需求:

  計算平均分數並輸出:如果平均分數超過60分輸出成績最高的三個學生的成績,否則輸出后三名的學生;

declare @avgscore float = 0
select @avgscore = AVG(Score) from SC
if(@avgscore>60)
begin
    print '前三名'
    select top 3 s.Sname,sc.Score from Student s join SC sc on s.S#=sc.S#
    order by Score desc
end
else
begin
    print '后三名'
    select top 3 s.Sname,sc.Score from Student s join SC sc on s.S#=sc.S#
    order by Score asc
end

2.2 死了都要愛 WHILE

  (1)循環語句語法

WHILE(條件表達式)
  BEGIN --相當於C#里的{
    語句
    ……
    continue --退出本次循環
    BREAK    --退出整個循環
  END --相當於C#里的}
View Code

  (2)經典案例:計算1-100之間所有奇數的和

declare @index int = 1
declare @sum int = 0
while(@index <= 100)
begin
    if(@index%2!=0)
    begin
        set @sum=@sum+@index
    end
    set @index=@index+1
end

三、事務:錘錘,不是說好一起同生共死嗎?

3.1 什么是事務?

  (1)事務(Transaction)是並發控制的基本單位

  所謂的事務,它是一個操作序列,這些操作要么都執行,要么都不執行,它是一個不可分割的工作單位。  例如,銀行轉賬工作:從一個賬號扣款並使另一個賬號增款,這兩個操作要么都執行,要么都不執行。所以,應該把它們看成一個事務。事務是數據庫維護數據一致性的單位,在每個事務結束時,都能保持數據一致性。

  事務具有以下4個基本特征:簡稱ACID

  ● Atomic(原子性):事務中的所有元素作為一個整體提交或回滾,事務的個元素是不可分的,事務是一個完整操作。

  ● Consistency(一致性):事物完成時,數據必須是一致的,也就是說,和事物開始之前,數據存儲中的數據處於一致狀態。

  ● Isolation(隔離性):對數據進行修改的多個事務是彼此隔離的。這表明事務必須是獨立的,不應該以任何方式以來於或影響其他事務。

  ● Durability(持久性):事務完成之后,它對於系統的影響是永久的,該修改即使出現系統故障也將一直保留,真實的修改了數據庫。

  (2)事務的語法步湊

  • 開始事務:BEGIN TRANSACTION       開啟事務

  • 事務提交:COMMIT TRANSACTION   --提交操作

  • 事務回滾:ROLLBACK TRANSACTION --取消操作

3.2 為什么需要事務?

  以最經典的轉賬情形為例,我們要從A賬戶轉一筆錢到B賬戶,需要進行兩部操作:第一步,從A賬戶扣除指定的金額數目;第二部,將B賬戶增加指定的金額數目;

update bank set balance=balance-1000 where cid='0001'
update bank set balance=balance + 1000 where cid='0002'

  這里假設A賬戶有1000元,B賬戶有10元,並且銀行約束每個賬戶必須保留10元最低存款額。這時,如果我們要從A賬戶轉1000元到B賬戶的話,會在第一步從A賬戶扣除1000元時違反約束條件,從而出現錯誤,阻止了此次轉賬操作;但是,這並沒有影響到第二步操作,於是B賬戶得到了天上掉下來的1000元。該怎么解決?我們可以將這兩步放到一個操作序列里邊,如果任何一步出現錯誤,都不會執行下一步操作,於是我們就可以用到事務了。

3.3 使用事務完成同生共死

  這里使用事務解決剛剛的那個轉賬的問題,注意這里使用到了系統變量@@ERROR,但是@@ERROR只能判斷當前一條T-SQL語句執行是否有錯,為了判斷事務中所有T-SQL語句是否有錯,我們需要對錯誤進行累計,於是我們可以定義一個局部變量來記錄整個操作序列期間的錯誤數。

begin transaction
declare @sumerror int = 0
update bank set balance=balance-1000 where cid='0001'
set @sumerror = @sumerror + @@ERROR
update bank set balance=balance + 1000 where cid='0002'
set @sumerror = @sumerror + @@ERROR
if(@sumerror = 0)
begin
    commit transaction
end
else
begin
    rollback transaction
end

四、存儲過程:別以為你藏在數據庫里我就不用你

4.1 什么是存儲過程?

  存儲過程(Procedure)是一組為了完成特定功能的SQL語句集合,經編譯后存儲在數據庫中,用戶通過指定存儲過程的名稱並給出參數來執行。

  存儲過程中可以包含邏輯控制語句和數據操縱語句,它可以接受參數、輸出參數、返回單個或多個結果集以及返回值。因此,我們可以簡單的理解為:使用存儲過程就像在數據庫中運行方法。  

4.2 存儲過程的優點  

  (1)執行速度更快 – 在數據庫中保存的存儲過程SQL語句都是編譯過的

  (2)允許模塊化程序設計 – 類似方法的復用

  (3)提高系統安全性 – 防止SQL注入

  (4)減少網絡流量 – 只需要傳輸存儲過程的名稱

4.3 使用存儲過程

  (1)系統存儲過程

  由系統定義,存放在master數據庫中,名稱以“sp_”開頭或”xp_”開頭:

  (2)自定義存儲過程

  自定義的存儲過程可以以usp_開頭,由用戶在自己的數據庫中創建的存儲過程。

  這里我們可以創建一個Account表的分頁存儲過程,看看怎么使用的吧:

create proc usp_GetPagedAccountData
@pageIndex int = 1,
@pageSize int = 10
as
begin
    select * 
    from 
        (select ROW_NUMBER() OVER(order by Id) as rownum,* from Account) as t
    where 
        t.rownum between @pageSize*(@pageIndex-1)+1 and @pageIndex*@pageSize 
    Order by t.Id
end

  如何來執行存儲過程呢?

exec usp_GetPagedAccountData @pageIndex=2,@pageSize=10

  (3)使用輸出參數

  如果希望在使用存儲過程后,將用戶傳遞的某個參數輸出改變后的結果,可以使用輸出參數關鍵字:OUTPUT

  具體的使用語法為:

declare @a int
exec usp_pp @canshu= @a output
print @a
View Code

  這里我們看一個實例,加入有以下的一個存儲過程,它接收用戶傳遞過來的一個年齡,在Student表找出所有大於這個年齡的學生信息,並返回大於這個年齡的學生人數。

create proc usp_GetInfoByAge
@page int = 10,
@pcount int output
as
begin
    select @pcount=COUNT(*) from Student where Sage>@page
    select * from Student where Sage>@page
end
-- 下面是調用的代碼以及返回結果
declare @outputCount int=0 
exec usp_GetInfoByAge @page=18,@pcount=@outputCount output
select @outputCount

五、觸發器:If you jump,I will jump too.

5.1 什么是觸發器?

  觸發器(Trigger)是一種特殊類型的存儲過程,它不同於之前的我們介紹的存儲過程。觸發器主要是通過事件進行觸發被自動調用執行的。而存儲過程可以通過存儲過程的名稱被調用。

  觸發器對表進行插入、更新、刪除的時候會自動執行的特殊存儲過程,它一般用在比check約束更加復雜的約束上面。

  觸發器和普通的存儲過程的區別是:觸發器是當對某一個表進行操作,諸如:update、insert、delete這些操作的時候,系統會自動調用執行該表上對應的觸發器

5.2 觸發器的類型

  (1)after/for 觸發器(之后觸發):insert觸發器、update觸發器、delete觸發器

  (2)instead of 觸發器 (之前觸發) 

  兩種類型的區別是:After和for都是在增刪改執行的時候執行另外的SQL語句,而Instead of 是使用另外的SQL語句取代原來的操作;

5.3 使用觸發器

  (1)觸發器語法

CREATE TRIGGER triggerName ON 表名
after(for)(for與after都表示after觸發器)  |  instead of
 UPDATE|INSERT|DELETEinsert,update,deleteAS
beginend
View Code

  (2)after觸發器實例:

  假如我們有一張成績表Score{sId,cId,grade}和學生表Student{sId,sName,sAge},其中Score中的sId是Student中的主鍵,即Scroe中的sId為外鍵。那么,現在我們有這樣一個需求:在每次向成績表中添加新數據的時候,首先判斷插入的學生學號是否存在於Student表中,如果存在則顯示“插入成功”,如果不存在(也就是操作人員輸入有誤)那么則此次新增操作作廢。

CREATE TRIGGER tgForScoreOnInsert ON Score 
after INSERT -- 后置的新增觸發器
AS
Begin
    declare @stuid int,@courseid int--定義兩個變量
    select @stuid = sId,@courseid = cId from inserted--獲得新增行的數據
    if exists(select * from Student where sId=@stuid)--判斷分數學員是否存在
        print ‘插入成功’
    else --如果不存在,則把更新增成功的分數記錄給刪除掉
        delete from Score where sId = @stuid and cId = @courseid
End

  (3)instead of觸發器實例:

    由instead of觸發器的定義可以知道,instead of觸發器表示並不執行其定義的操作(insert、update、delete)而僅是執行觸發器本身的內容。

  因此,借助instead of觸發器的這個特點,我們可以看看這個場景:假如我們有一張借書記錄表,圖書館規定每個學生最多只能借5本書,因此我們需要在添加借書記錄時首先判斷該生是否已經達到了最大的借書數量,如果達到了則提示“已達到借書最大限制,無法再繼續借閱”,如果沒有達到才會添加到記錄表中。

create trigger tgInsteadOfRecordOnInsert on Record
INSTEAD OF INSERT 
as
begin
--首先在記錄表中查找,判斷是否還能借
if exists(select 1 from inserted a join Record b on a.cardNo=b.cardNo group by a.cardNo having count(*)>=5)
--給出提示
print '已達到借書最大限制,無法再繼續借閱!'
else
insert into Record select * from inserted
end

5.4 觸發器使用建議

  (1)盡量避免在觸發器中執行耗時操作,因為觸發器會與SQL語句認為在同一個事務中。(事務不結束,就無法釋放鎖。)

  (2)避免在觸發器中做復雜操作,影響觸發器性能的因素比較多(如:產品版本、所使用架構等等),要想編寫高效的觸發器考慮因素比較多(編寫觸發器容易,編寫復雜的高性能觸發器難!)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM