T-SQL之表變量與臨時表


-- 對於表變量和臨時表的例子:
-- 約束(Constraint) 索引(Index) I/O 開銷 作用域(SCOPE)存儲位置 其他

/* **************************************
A) 約束(Constraint) : 在臨時表和表變量,都可以創建Constraint ,針對表變量,只有定義時能加 CONSTRAINT

******************************************* */
USE tempdb
GO
IF OBJECT_ID('TEMPDB..#1') IS NOT NULL
    DROP TABLE dbo.#1
GO

CREATE TABLE #1
(
    ID INT ,
    Nr NVARCHAR(50) NOT NULL,
    OperationTime DATETIME DEFAULT (GETDATE()),
    CONSTRAINT pk_#1_id PRIMARY KEY (ID)
)

ALTER TABLE #1 ADD CONSTRAINT CK_#1_Nr CHECK(Nr BETWEEN '10001' AND '1999')


/*    上面腳本 可以看出臨時表#1在創建時,創建了Constraint , 也可以看出創建臨時表#1后創建Constraint,
    下面來為表變量的場景,在定義表變量時不能指定Constaint名,定義表變量后不能對表變量創建Constraint    */

--EG:
USE tempdb
GO
DECLARE @1 TABLE 
(
    ID INT,
    Nr NVARCHAR(50) NOT NULL,
    OperationTime DATETIME DEFAULT (GETDATE()),
    CONSTRAINT [PK_@1_ID] PRIMARY KEY(ID)
)
/*    報錯:消息 156,級別 15,狀態 2,第 6 行
    關鍵字 'CONSTRAINT' 附近有語法錯誤。    */

USE tempdb
GO
DECLARE @1 TABLE 
(
    ID INT,
    Nr NVARCHAR(50) NOT NULL,
    OperationTime DATETIME DEFAULT (GETDATE())
)
ALTER TABLE @1 ADD CONSTRAINT [CK_@1_Nr] CHECK (Nr BETWEEN '10001' AND '19999')

--    報錯:消息 102,級別 15,狀態 1,第 7 行
--  '@1' 附近有語法錯誤。

/*
在上述代碼中發現,在解析T-SQL語句過程就發生錯誤,也就是說SQL SERVER不支持定義表變量時對Constraint命名,也不支持定義表變量后,
對其建Constraint命名,也不支持定義表變量后,對其建Constraint。 

------------------------------------------------------------------------------------------------------

在MSSSMS中我們先執行前創建臨時表#1,不關閉當前會話的情況下,另建一個查詢,執行也其相同的代碼。便會報出以下錯誤:
    消息 2714,級別 16,狀態 4,第 1 行
    數據庫中已存在名為 'pk_#1_id' 的對象。
    消息 1750,級別 16,狀態 0,第 1 行
    無法創建約束。請參閱前面的錯誤消息。
而當我們執行表變量的@1則不會報錯,說明表變量也不需要進行DROP Table的操作,一次執行完成后就會消失。


我們發現在創建臨時表 #1 的過程,明確給了一個主鍵名稱 'PK_#1_ID',當右邊再創建相同臨時表#1的時候就發生了對象重復錯誤問題。我們也可以通過SQL        SERVER 提供的系統視圖 SYS.OBJECTS 查詢約束'PK_#1_ID'的信息        
            
*/

USE tempdb
GO

SELECT * FROM sys.objects WHERE name = 'pk_#1_id'
/*
在系統視圖 sys.objects 發現'pk_#1_id'名稱后面不加任何的隨機數值表述不同會話有不同的對象。
根據 SQL SERVER 對 sys.objects 的描述規則, sys.objects 中的 name 列數據是唯一的。當另一個會話創建相同的對象時就會發生對象重復的錯誤。

在Constraint中, Foreign Key 是不能應用與表變量,對於臨時表,創建 Foreign Key 是沒有意義的。也就是說臨時表不受 Foreign Key 約束。
下面通過例子來說明臨時表的情況。
*/
USE tempdb
GO

IF OBJECT_ID('tempdb..#1') is not null
    DROP TABLE #1
GO

IF OBJECT_ID('TEMPDB..#2') IS NOT NULL
    DROP TABLE #2
GO

CREATE TABLE #1
(
id int,
Nr nvarchar(50) not null,
OperationTime datetime default (getdate()),
Constraint PK_#1_ID primary key(ID)
)

ALTER TABLE #1 ADD CONSTRAINT CK_#1_Nr check (Nr between '10001' and '19999')

CREATE TABLE #2(
    ID INT PRIMARY KEY ,
    FOREIGNID INT NOT NULL,
    FOREIGN KEY(FOREIGNID) REFERENCES #1(ID)
)
GO


/*    不為臨時表定義 FOREIGN KEY 約束 '#2'。無論是局部臨時表還是全局臨時表,都不會對它們強制使用 FOREIGN KEY 約束。
    可以看出對於臨時表不強制 FOREIGN KEY 約束,我們也可以通過 SQL SERVER 系統視圖 sys.foreign_keys 查詢        */

use tempdb
go
select * from sys.tables where name like '#[1-2]%'
select * from sys.foreign_keys

/*    上面的查詢,只能看到 sys.tables 表中存在剛才創建的臨時表 #1 和 #2,在sys.foreign_keys 看不到有關 foreign_key約束信息。
    這也驗證了左邊 SQL SERVER 提示的,在臨時表中無強制使用 FOREIGN KEY 約束。 */
  
 /* **************************************
B) 索引(INDEX) : 從索引方面看臨時表和表變量,與從Constraint上分析有些類似,在臨時表中,它與真實表一樣可以創建索引。在表變量定義過程中,也可以創建一些類似唯一和聚集索引
**************************************** */
--EG:
USE tempdb
GO

DECLARE @1 TABLE
(
    ID INT PRIMARY KEY CLUSTERED ,
    Nr nvarchar(50) UNIQUE Nonclustered
)

INSERT INTO @1(ID , Nr ) VALUES( 1 , '10001');
INSERT INTO @1(ID , Nr ) VALUES( 2 , '10002');
INSERT INTO @1(ID , Nr ) VALUES( 8 , '10003');
INSERT INTO @1(ID , Nr ) VALUES( 7 , '10004');
INSERT INTO @1(ID , Nr ) VALUES( 3 , '10005');
--INSERT INTO @1(ID , Nr ) VALUES( 1 , '10001');
SELECT  * 
FROM SYS.indexes AS A  INNER JOIN SYS.tables AS B ON B.object_id = A.object_id
ORDER BY B.create_date DESC

SELECT Nr FROM @1 WHERE Nr = '10005'

/*
第一個查詢在表變量中使用聚集PRIMARY KEY,創建非聚集的Unique約束
第二個查詢,看查詢計划應用到在變量創建的唯一索引'UN_#...'
--------------------------------------------------------------

下面是臨時表索引的例子,我們拿一個例子說明,與前邊說的Constraint例子有點相似,
這里我們對臨時表創建索引,並給索引一個具體名稱,測試是否會重復。
*/
-- EG:
USE tempdb
GO

IF OBJECT_ID('#1') IS NOT NULL
    DROP TABLE #1
GO

CREATE TABLE #1
(
    ID INT PRIMARY KEY ,
    Nr NVARCHAR(50) NOT NULL ,
    OperationTime DATETIME DEFAULT(GETDATE())
)

CREATE NONCLUSTERED INDEX IX_#1_Nr ON #1(Nr ASC)
GO

SELECT B.NAME AS TABLENAME , A.object_id , a.name , a.index_id , a.type 
FROM SYS.indexes AS A INNER JOIN SYS.TABLES AS B ON A.OBJECT_ID = B.OBJECT_ID
WHERE B.NAME LIKE '#1[_]%'
ORDER BY B.CREATE_DATE ASC

/*
從返回的結果,我們看到在系統視圖表SYS.INDEXS中,創建有兩個相同的索引 'IX_#1_Nr', 但注意下object_id 數據不同。在sql server 中允許不同的表索引名稱可以相同的。在並發的環境下,按原理是可以對臨時表創建的索引給明確名稱的。除非並發的情況會發生重復的表名或者重復的Constraint, 或其它系統資源不足的問題,才會導致出錯。
*/

  
 /* **************************************
C) I/O開銷  : 臨時表與表變量,在I/O開銷的描述,我們直接通過一個特殊的例子去描述它們,在SSMS上新增加兩個查詢,分別輸入臨時表和表變量的測試代碼:
**************************************** */
-- EG: 臨時表:
USE tempdb
GO
IF OBJECT_ID('#1') IS NOT NULL
    DROP TABLE #1
GO

CREATE TABLE #1
(
    ID INT PRIMARY KEY ,
    Nr NVARCHAR(50) NOT NULL ,
    OperationTime DATETIME DEFAULT(GETDATE())
)

INSERT INTO #1( ID, Nr )
SELECT TOP 5000 ROW_NUMBER() OVER(ORDER BY A.OBJECT_ID) ,
LEFT(A.NAME + B.NAME , 50) 
FROM master.SYS.all_objects AS A ,SYS.all_columns AS B 
WHERE TYPE = 'S'

SELECT Nr , COUNT(Nr) AS Sum_
FROM #1
WHERE Nr LIKE 'sysrscolss%'
GROUP BY Nr
------------------------------------------------------
-- EG: 表變量
USE tempdb
GO

DECLARE @1 TABLE
(
ID INT PRIMARY KEY ,
Nr NVARCHAR(50) NOT NULL ,
OperationTime DATETIME DEFAULT(GETDATE())
)

INSERT INTO @1( ID, Nr , OperationTime)
SELECT TOP 5000 ROW_NUMBER() OVER(ORDER BY a.object_id) ,
    LEFT(a.name + b.name ,50) , a.create_date
FROM master.sys.all_objects AS a , master.sys.all_columns AS b 
WHERE type = 'S'

SELECT Nr , COUNT(Nr) AS Sum_
FROM @1
WHERE Nr LIKE 'sysrscolss%'
GROUP BY Nr

/*
通過上面兩個查詢,查看運行的I/O圖形描述,可以看出來查詢開始,不管是臨時表還是表變量,都使用到了聚集索引掃描,兩者雖然返回的數據一致,
但 I/O 的開銷不同:臨時表的 I/O 開銷是0.0342361,而表變量只有0.003125,相差非常大。在臨時表的執行計划圖形中,我們發現一行 '缺少索引(影響48.3374); create nonclustered index ...'提示信息.我們對臨時表#1,在字段'Nr'上創建一個非聚集索引,再看執行計划結果:

*/

CREATE NONCLUSTERED INDEX  IX_#1_Nr ON #1(Nr)

/*
我們在臨時表 #1 上創建完索引 'IX_#1_Nr',再運行看執行計划。在臨時表 #1 查詢時用了索引查找(index seek) ,而且I/O開銷減少到了0.0053472 。雖然開始查詢的
這次的開銷為0.0053472。雖然開始查詢的時候I/O開銷還是比表變量開始查詢的時候大一些,但執行步驟中比變量少了一個排序(SORT) 開銷,最后的看SELECT結果,估計子樹的成本比使用表變量的大大減少。
這里的例子只是描述一個特殊的情況,在真實的環境中,要根據實際的數據量來判斷是否使用臨時表或者表變量。倘若在存儲過程中,當數據量非常少如只有不到50行記錄,數據占的頁面也不會超過1個頁面,那么使用表變量是一個很好的解決方案。
*/

 /* **************************************
D) 作用域(SCOPE): 表變量像局部變量(local variable) 一樣,有着很窄的作用域,只能應用於定義的函數、存儲過程或者批處理內。 如:一個會話里面有幾個批處理,那么表變量只能作用在它定義所在的批處理范圍內。其它的批處理無法再調用它。
**************************************** */
USE tempdb
GO

SET NOCOUNT ON 
DECLARE @1 TABLE(
ID INT PRIMARY KEY CLUSTERED ,
Nr nvarchar(50) UNIQUE NONCLUSTERED 
)

INSERT INTO @1 (ID , NR ) VALUES( 1,'10001');
INSERT INTO @1 (ID , NR ) VALUES( 2,'10002');
INSERT INTO @1 (ID , NR ) VALUES( 8,'10003');
INSERT INTO @1 (ID , NR ) VALUES( 3,'10004');
INSERT INTO @1 (ID , NR ) VALUES( 7,'10005');

SELECT * FROM @1  
GO    --批處理結束點

SELECT * FROM @1
-- 消息 1087,級別 15,狀態 2,第 2 行
-- 必須聲明表變量 "@1"。
/*
上面的查詢相當於一個會話,'GO'描述的一個批處理的結束點,在'GO'之前定義的表變量,
在'GO'之后調用是發生'必須聲明變量@1'的錯誤提示.
臨時表和表變量不同,臨時表的作用域是當前會話都有效,一直到會話結束或者臨時表被DROP的時候,也就是說可以跨當前會話的幾個批處理范圍.
*/

SELECT * FROM #1

USE tempdb
GO
IF OBJECT_ID('TEMPDB..#1') IS NOT NULL
    DROP TABLE #1
GO

CREATE TABLE #1 (
    ID INT ,
    OperationTime DATETIME DEFAULT(GETDATE()),
    CONSTRAINT PK_#1_id PRIMARY KEY (id)
)

SELECT * FROM #1
GO  --批處理結束點

SELECT * FROM #1

/*
上面可以看出在GO前后都可以查詢到臨時表 #1 .
在描述臨時表與表變量的作用域時,有個地方要注意的是,當 SP_EXECUTESQL 或EXECUTE 語句執行字符串時,字符串將作為它的自包含批處理執行.如果表變量在 SP_EXECUTESQL 或EXECUTE 語句之前定義,在 SP_EXECUTESQL 或EXECUTE 語句的字符串中無法調用外部定義的表變量.
*/
--EG:
USE tempdb
GO

SET NOCOUNT ON 
DECLARE @1 TABLE(
ID INT PRIMARY KEY CLUSTERED ,
Nr nvarchar(50) UNIQUE NONCLUSTERED 
)

INSERT INTO @1 (ID , NR ) VALUES( 1,'10001');
INSERT INTO @1 (ID , NR ) VALUES( 2,'10002');
INSERT INTO @1 (ID , NR ) VALUES( 8,'10003');
INSERT INTO @1 (ID , NR ) VALUES( 3,'10004');
INSERT INTO @1 (ID , NR ) VALUES( 7,'10005');

SELECT * FROM @1

EXECUTE (N'SELECT * FROM @1')
GO
/* 上述腳本,當執行到 EXECUTE (N'SELECT * FROM @1') 時候,同樣發生與之前單獨執行查詢語句一樣的錯誤,提示: '必須聲音變量@1'.
臨時表是可以在 SP_EXECUTESQL 或EXECUTE 語句執行字符串中被調用.
如:EXECUTE (N'SELECT * FROM #1')  */



-------******************* --------------


/* **************************************
E) 其它: 臨時表與表變量,還有其它的特征,如臨時表受事務回滾,而表變量不受事務回滾影響.對應事務方面,更為正確的說法是表變量的事務只在表變量更新期間存在.因此減少了表變量對鎖定和記錄資源的需求.
**************************************** */
USE tempdb
GO
SET NOCOUNT ON 

IF OBJECT_ID('#1') IS NOT NULL
    DROP TABLE dbo.#1
GO
CREATE TABLE #1(ID INT, Nr NVARCHAR(50))
DECLARE @1 TABLE(id INT, Nr NVARCHAR(50))

BEGIN TRAN /* 事務開始 */

INSERT INTO #1(ID ,Nr) 
SELECT TOP 1 ROW_NUMBER() OVER(ORDER BY A.OBJECT_ID) ,
    LEFT(A.name + B.name , 50 )
FROM SYS.all_objects AS A ,SYS.all_columns AS B 

INSERT INTO @1(ID ,Nr) 
SELECT TOP 1 ROW_NUMBER() OVER(ORDER BY A.OBJECT_ID) ,
    LEFT(A.name + B.name , 50 )
FROM SYS.all_objects AS A ,SYS.all_columns AS B 

ROLLBACK TRAN /* 回滾事務 */

SELECT * FROM #1
SELECT * FROM @1
go

/*
這里發現'ROLLBACK TRAN'之后,臨時表 #1 沒有數據插入,而表變量@1還有一條數據存在。說明表變量不受'Rollback Tran' 所影響,它的行為有類似於局部變量一樣。
另外 SQL SERVER對表變量不保留任何的統計信息,因為如此,我們在數據量大的時候使用表變量,發現比臨時表慢許多。
*/
參考:
http://blog.csdn.net/gulijiang2008/article/details/6091802
http://www.jb51.net/article/28788.htm
http://support.microsoft.com/kb/305977/zh-cn
http://www.51testing.com/html/78/n-816578.html


免責聲明!

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



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