sql 臨時表

臨時表就是那些名稱以井號 (#) 開頭的表。如果當用戶斷開連接時沒有除去臨時表,SQL Server 將自動除去臨時表。臨時表不存儲在當前數據庫內,而是存儲在系統數據庫 tempdb 內。 

臨時表有兩種類型: 

本地臨時表 
以一個井號 (#) 開頭的那些表名。只有在創建本地臨時表的連接上才能看到這些表,鏈接斷開時臨時表即被刪除(本地臨時表爲創建它的該鏈接的會話所獨享)或者這樣說局部臨時表是有當前用戶創建的,並且只有當前用戶的會話纔可以訪問

如果本地臨時表由存儲過程創建或由多個用戶同時執行的應用程序創建(其實可看作是不同的鏈接,不同的會話),則數據庫引擎必須能夠區分由不同用戶創建的表。爲此,數據庫引擎在內部爲每個本地臨時表的表名追加一個數字後綴。存儲在 tempdb 的 sysobjects 表中的臨時表,其全名由 CREATE TABLE 語句中指定的表名和系統生成的數字後綴組成。爲了允許追加後綴,爲本地臨時表指定的 table_name 不能超過 116 個字符。

對於本地臨時表來說,需要注意在不同情形下應用本地臨時表其刪除的實際。如假設數據庫在執行一個存儲過程的時候建立了本地臨時表。那麼此時這個本地臨時表並不是在會話終止的時候自動刪除,而是在這個存儲過程執行完畢後就會刪除。這是什意思呢?也就是說,用戶發起的某個會話,爲了執行一個特殊的作業(如用戶的這個會話調用了某個存儲過程)。此時其實就是會話再創建一個子會話的過程。在這種情況下需要注意的是,子會話創建的本地臨時表只在子會話內部有效。當這個子會話終止的時候(存儲過程執行完畢),此時這個臨時表就會自動刪除。即對於調用這個子會話的會話來說,這個其子會話的創建的臨時表對於其也是無效的,因爲臨時表已經在子會話關閉的時候自動刪除。做一個形象的比喻。即現在做父親的去叫兒子造一座房子。當兒子死亡的時候,這座房子也會消失。對於這種情況,數據庫管理員需要注意。父會話只能夠引用子會話從臨時表中傳遞出來的數據。也就是說,父會話要訪問子會話創建的臨時表的數據,只有一種手段。即先讓子會話對臨時表中的數據進行查詢或者操作,然後把結構回傳給父會話。父會話是不能夠直接訪問子會話所創建的臨時表。當然這個限制是專門針對本地臨時表而言的。對於全局臨時表來說,本身就是所有用戶都可以訪問,爲此就沒有這個限制。

全局臨時表 
以兩個井號 (##) 開頭的那些表名。在所有連接上都能看到全局臨時表或者這樣說只要這個全局臨時表存在,那麼用戶創建會話後對所有的用戶都是可見的。如果在創建全局臨時表的連接斷開前沒有顯式地除去這些表,那麼只要所有其它任務停止引用它們,這些表即被除去。當創建全局臨時表的連接斷開後,新的任務不能再引用它們(換句話說舊的任務還何以引用)。當前的語句一執行完,任務與表之間的關聯即被除去;因此通常情況下,只要創建全局臨時表的連接斷開,全局臨時表即被除去。


--失敗的----
declare 
@sql varchar(100)
set @sql='select id as a,name as b into #b from shopName'
print @sql
exec(@sql)
select * from #b
drop table #b

消息 208,級別 16,狀態 0,第 5 行
對象名  '#b' 無效。


---成功的-----
declare @sql varchar(100)
set @sql='select id as a,name as b into ##b from shopName'
print @sql
exec(@sql)
select * from ##b
drop table ##b  //一定要DROP不然又要報錯
 

 當然勿論是全局的還是本地表,只要是能訪問的都能用DROP TABLE 來強制地刪除臨時表。

當創建本地或全局臨時表時,CREATE TABLE 語法支持除 FOREIGN KEY 約束以外的其他所有約束定義。如果臨時表中指定了 FOREIGN KEY 約束,則該語句將返回一條表明已跳過此約束的警告消息。此表仍將創建,但不使用 FOREIGN KEY 約束。在 FOREIGN KEY 約束中不能引用臨時表。

以一個實際的例子來談談普通表、本地臨時表、全局臨時表三個表的差異。如現在有一個保存員工信息的表user。這個表是一個普通表,只要其建立就不會自動刪除,任何好在數據庫中有使用這個表(具有訪問權限)的用戶都可以訪問這個表,除非這個表被所有者刪除或者更改了權限。在用戶A(具有訪問權限)訪問這個表的過程中,數據庫可能會根據需要生成一張本地臨時表#user。此時只有這個會話纔可以訪問這個本地臨時表。當這個用戶的會話中斷之後,這個本地臨時表也會被自動刪除。不過根據需要,數據庫也可能會建立全局臨時表##user(在名字上與本地臨時表不同)。此時數據庫中的任何用戶只要連接到了數據庫就可以訪問這個全局臨時表(訪問權限上的不同)。當這個創建臨時表會話的用戶中斷數據庫連接時,這個臨時表是否會刪除是一個未知數,這要看當時的實際情況(在可用性上不同)。如果此時還有其他用戶連接在這個表上的話,那麼這個全局臨時表就不會被刪除。只有在中斷連接時,沒有其他用戶在訪問這個表時,即某個用戶(不一定是創建這張全局臨時表的用戶)斷開連接並且所有其他的會話不再使用這個表時纔會被刪除。

  可見無論是全局臨時表還是本地臨時表,其跟普通表相比,最重要的一個差異就是其會根據需要自動創建。當不再需要時其又會自動刪除。這也正是臨時表的魅力所在,其可以在數據處理的過程中,減少很多中間表格。

臨時表對日誌與鎖的影響

  日誌文件是數據庫中很重要的一個工具。無論是SQL Server數據庫還是Oracle數據庫,都有日誌這個工具。如憑藉重做日誌工具,數據庫管理員可以在數據庫故障的時候藉此來恢復數據,將數據恢復到故障的那個點上。但是在使用臨時表的時候,需要注意一點,就是臨時表不會有日誌文件。即對臨時表進行的DML等操作不會形成日誌文件。這個特性即有好處,也有壞處。好處是對於臨時表的更改不會保存到日誌文件中。也就是說,如果數據庫發生了故障,則保存在臨時表中的數據是不能夠恢復的。爲此數據庫管理員不得不重新執行某些作業以重新生成臨時表中的數據。好處就是對於臨時表的DML操作速度會非常的塊。除了其他的原因導致其性能的提升外,在更改其內容時不會生成日誌信息也是一個重要的原因。爲此對臨時表的操作不生成日誌信息,這是一個雙刃劍。數據庫管理員在日常工作中,要儘量發揮其優勢,減少其負面作用的影響。

  另外,若採用臨時表這種處理機制的話,還需要注意其對鎖的影響。在介紹本地臨時表與全局臨時表差異的時候,筆者就介紹過,本地臨時表只對當前的會話有效。即使當前會話又創建了另外一個子會話,也只對子會話有效。當某個會話終止的時候,這臨時表就會自動被刪除。而對於普通表或者全局臨時表來說,可能同時多個會話都可以訪問這個表。這兩者有什麼區別呢?若允許多個會話可以同時訪問某個表的話,那麼這個表就可能會遇到鎖的情況。即某個用戶會話在對錶中地記錄進行DML等操作時,爲了保證數據的一致性,會對相關的記錄進行加鎖等措施。而採用本地臨時表的話,由於只有一個會話可以訪問臨時表中的數據,所以即使這個會話更改臨時表中的數據,也不會有鎖衝突的問題。故其在更改本地臨時表中的數據時,就不用爲其加鎖。所以,對於本地臨時表的操作速度就要比其他表來的快。故在何時的情況下使用臨時表無疑可以提高數據庫的整體性能。如可以將一些操作在臨時表中完成,然後再將最後的結果更新到基本表中。

 

 

利用SQL的全局臨時表防止用戶重複登錄

      在我們開發商務軟件的時候,常常會遇到這樣的一個問題:怎樣防止用戶重複登錄我們的系統?特別是對於銀行或是財務部門,更是要限制用戶以其工號身份多次登入。
  
  可能會有人說在用戶信息表中加一字段判斷用戶工號登錄的狀態,登錄後寫1,退出時寫0,且登錄時判斷其標誌位是否爲1,如是則不讓該用戶工號登錄。但是這樣那勢必會帶來新的問題:如發生象斷電之類不可預知的現象,系統是非正常退出,無法將標誌位置爲0,那麼下次以該用戶工號登錄則不可登入.
    


create 
procedure gp_findtemptable 
      /* 尋找以操作員工號命名的全局臨時表  
  * 如無則將out參數置爲0並創建該表,如有則將out參數置爲1  
  * 在connection斷開連接後,全局臨時表會被SQL Server自動回收   
  * 如發生斷電之類的意外,全局臨時表雖然還存在於tempdb中,但是已經失去活性  
  * 用object_id函數去判斷時會認爲其不存在.
  
*/
  @v_userid varchar(6), -- 操作員工號
  
  @i_out int out -- 輸出參數 0:沒有登錄 1:已經登錄
  
  as
  
  declare @v_sql varchar(100)
  
  if object_id('tempdb.dbo.##'+@v_useridis null
  
  begin
  
  set @v_sql = 'create table ##'+@v_userid+'(userid varchar(6))'
  
  exec (@v_sql)
  
  set @i_out = 0
  
  end
  
  else
  
  set @i_out = 1

  
  在這個過程中,我們看到如果以用戶工號命名的全局臨時表不存在時過程會去創建一張並把out參數置爲0,如果已經存在則將out參數置爲1。
  
  這樣,我們在我們的應用程序中調用該過程時,如果取得的out參數爲1時,我們可以毫不客氣地跳出一個message告訴用戶說”對不起,此工號正被使用!”

上面還涉及到一個OBJECT_ID ()函數:

 

Syntax:

OBJECT_ID ( '[ database_name . [ schema_name ] . | schema_name . object_name' [ ,'object_type' ] )

一般語法:int object_id('objectname');

此方法返回數據庫對象標識號。

其中,參數objectname 表示要使用的對象,其數據類型爲nchar或char(如果爲char,系統將其轉換爲nchar)

object_type:爲可選參數,其數據類型爲nchar或char(如果爲char,系統將其轉換爲nchar),指明架構範圍的對象類型(object_name爲字符串通過它,可以說明這個字符串究竟是說明對象,其列表見文章結尾)

 

ps:使用 OBJECT_ID 不能查詢非架構範圍內的對象(如 DDL 觸發器)。對於在 sys.objects 目錄視圖中找不到的對象,需要通過查詢適當的目錄視圖來獲取該對象的標識號。例如,若要返回 DDL 觸發器的對象標識號,請使用 SELECT OBJECT_ID FROM sys.triggers WHERE name = 'DatabaseTriggerLog'

 

返回類型爲int,表示該對象在系統中的編號,如果找不到或發生錯誤一律返回NULL。

 

例子:

A.返回數據庫AdventureWorks中Production.WorkOrder表的標識號

 

USE master;
GO
SELECT OBJECT_ID(N'AdventureWorks.Production.WorkOrder'AS 'Object ID';
GO

 

B.存在性檢查

下列會確認資料表有物件的標識碼,藉此檢查指定的資料表是否存在。如果存在就刪除。

USE AdventureWorks;
GO
IF OBJECT_ID (N'dbo.AWBuildVersion', N'U'IS NOT NULL
DROP TABLE dbo.AWBuildVersion;
GO

此方法一般用來判斷數據庫中本來用沒有此對象(procedures,views,functions等).

注意:
當該參數對系統函數可選時,則系統採用當前數據庫、主機、服務器用戶或數據庫用戶。內置函數後面必須跟圓括號。 
如果指定一個臨時表名,除非當前數據庫爲tempdb(廢話),否則必須在臨時表名前面加上數據庫名,例如: 
SELECT OBJECT_ID('tempdb..#mytemptable')

 

 

Object_Type列表:

 

 

AF = 聚合函數 (CLR)
C = CHECK 約束
D = DEFAULT(約束或獨立)
F = FOREIGN KEY 約束
FN = SQL 標量函數
FS = 程序集 (CLR) 標量函數
FT = 程序集 (CLR) 表值函數
IF = SQL 內聯表值函數
IT = 內部表
P = SQL 存儲過程
PC = 程序集 (CLR) 存儲過程
PG = 計劃指南
PK = PRIMARY KEY 約束
R = 規則(舊式,獨立)
RF = 複製篩選過程
S = 系統基表
SN = 同義詞
SQ = 服務隊列
TA = 程序集 (CLR) DML 觸發器
TF = SQL 表值函數
TR = SQL DML 觸發器
U = 表(用戶定義類型)
UQ = UNIQUE 約束
V = 視圖
X = 擴展存儲過程
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章