Sql Server中如何準確獲得標識值

2021-08-22 13:57:30 字數 3693 閱讀 8104

sql server有三種不同的函式可以用來獲得含有標識列的表裡最後生成的標識值:

@@identity函式可以返回所有範圍內當前連線插入最後所生成的標識值(包括任何呼叫的儲存過程和觸發器)。這個函式不止可以適用於表。函式返回的值是最後表插入行生成的標識值。

最後是ident_current函式,它可以用於所有範圍和所有連線,獲得最後生成的表標識值。跟前面兩個函式不同的是,這個函式只用於表,並且使用[資料表名]作為乙個引數。

我們可以舉例項來演示上述函式是如何運作的。

首先,我們建立兩個簡單的例表:乙個代表客戶表,乙個代表審計表。建立審計表的目的是為了跟蹤資料庫裡插入和刪除資訊的所有記錄。

以下是引用片段:

createtabledbo.customer

(customeridintidentity(1,1)primarykey)

go createtabledbo.auditlog

(auditlogidintidentity(1,1)primarykey,

customeridint,actionchar(1),

changedatedatetimedefaultgetdate())

go然後,我們還要建立乙個儲存過程和乙個輔助觸發器,這個儲存過程將在資料庫表裡插入新的客戶行,並返回生成的標識值,而觸發器則會向審計表插入行:

以下是引用片段:

createproceduredbo.p_insertcustomer@customeridintoutput

as setnocounton

insertintodbo.customerdefaultvalues

select@customerid=@@identity

go createtriggerdbo.tr_customer_logondbo.customer

forinsert,delete

as ifexists(select'x'frominserted)

insertintodbo.auditlog(customerid,action)

selectcustomerid,'i'

frominserted

else

ifexists(select'x'fromdeleted)

insertintodbo.auditlog(customerid,action)

selectcustomerid,'d'

fromdeleted

go現在我們可以執行程式,建立客戶表的第一行了:

以下是引用片段:

declare@customeridint

execdbo.p_insertcustomer@customeridoutput

select@customeridascustomerid

執行後返回了我們需要的第乙個客戶的值,並記錄了插入審計表的條目。到目前為止,資料顯示沒有任何問題。

假設由於先前溝通出現了偏差,乙個客戶服務代表現在需要從資料庫裡刪除掉這個新增的客戶。我們現在就來把新插入的客戶行刪除掉:

以下是引用片段:

deletefromdbo.customerwherecustomerid=1

現在,客戶工作表為空表,而審計工作表裡則有兩行——第一行是記錄第一次插入行,第二行是記錄刪除客戶記錄。

現在我們再往資料庫裡增加第二個客戶資訊並檢測一下獲得的標識值:

以下是引用片段:

declare@customeridint

execdbo.p_insertcustomer@customeridoutput

select@customeridascustomerid

哇!看看出現了什麼情況!如果我們現在再看客戶工作表,就會發現雖然建立了客戶2,但是我們的程式返回的標識值為3!到底出了什麼問題呢?回想一下,前面講過@@identity函式的作用範圍,它會返回主程式呼叫的任何儲存過程或觸動任何觸發器最後生成的標識值,取決於哪乙個在函式被呼叫前最後生成標識值。在我們的例子裡,初始範圍是p_insertcustomer,然後是觸發器用來記錄插入條目的tr_customer_log。因此我們返回獲得的標識值是審計工作表裡觸發器插入生成的標識值,而不是我們想要的客戶工作表裡的生成的標識值。

在sql server 2000之前的版本,@@identity函式是獲得標識值的唯一方法。由於會出現這樣的儲存過程/觸發器問題,sql server開發團隊在sql server 2000中引入了 scope_identity()和ident_current這兩個函式來解決這個問題。所以在舊的sql server版本裡,要解決這個問題比較麻煩。如果是sql server6.5版本,我建議可以去掉標識列,然後建立乙個可以包含下乙個需要使用的值的輔助表,可以達到標識列的作用效果。不過這個辦法也不是什麼高明的辦法。

現在我們來修改一下儲存過程來使用scope_identity()函式,並重新執行程式來新增第三個客戶條目:

以下是引用片段:

alterproceduredbo.p_insertcustomer@customeridintoutput

as setnocounton

insertintodbo.customerdefaultvalues

select@customerid=scope_identity()

go declare@customeridint

execdbo.p_insertcustomer@customeridoutput

select@customeridascustomerid

我們返回的標識值還是3,不過這次我們獲得的標識值是正確的,因為我們新增了第三個客戶條目。如果我們檢查一下審計工作表,就會發現裡面已經有第四個條目記錄新插入的客戶記錄。由於函式scope_identity()只作用於當前範圍,只返回當前執行程式的值,這樣就避免了發生剛才那樣的問題。

前面講過,函式@@identity和函式scope_identity()不止用於表,不像函式ident_current那樣可以用表作為引數。使用@@identity和scope_identity()這兩個函式的話在設定**時需要加倍小心,才能夠從所需要的表裡獲得正確的標識值。從表面上來看,放棄這兩個函式,只使用函式ident_current並指定表是更安全的辦法。這樣可以避免出現獲得錯誤標識值的情況,對吧?記得先前說過函式ident_current不僅會跨範圍,而且它還會跨連線。也就是說,使用這個函式生成的值不僅僅限於你的連線所執行的程式,它的涵蓋範圍還包括整個資料庫所有的連線。因此,即使是在規模較小的oltp環境裡,它也會出現不能準確返回所需值的問題。這樣就可能發生類似前面@@identity函式/觸發器的資料損壞問題。

我的建議是函式scope_identity()是三個函式裡最安全的函式,應該設定為預設函式。使用這個函式,你可以放心地新增觸發器和次儲存過程,無需擔心意外損壞資料。而另外兩個函式可以保留應付特殊的情況,當遇到需要使用這兩個函式的特殊情況時,建議記錄它們的使用情況並進行測試。

小技巧:

if exists(select * from syscolumns where id=object_id(n'test1') and columnproperty(id,name,'isidentity')=1)

print n'有自增列'

else

print n'沒有自增列'

select b.name,a.* from syscolumns a,sysobjects b where a.id=b.id and columnproperty(a.id,a.name,'isidentity')=1

android TextView 如何動態獲取寬度

1.當textview的屬性是wrap content時,在介面還沒顯示時,是無法獲取其顯示後的長和寬的,那麼怎麼獲取呢?可以通過測量的方法預先測量出來。很簡單。textview tv findviewbyid r.id.tv name tv name settext str int spec vi...

SQL SERVER 中如何使用鎖

多個使用者同時對資料庫的併發操作時會帶來以下資料不一致的問題 併發控制的主要方法是封鎖,鎖就是在一段時間內禁止使用者做某些操作以避免產生資料不一致 sql server支援的鎖粒度可以分為為行 頁 鍵 鍵範圍 索引 表或資料庫獲取鎖 一.為什麼要引入鎖 多個使用者同時對資料庫的併發操作時會帶來以下資...

如何在DataFrame中通過索引高效獲取資料?

今天是pandas資料處理專題的第四篇文章,我們一起來聊聊dataframe中的索引。資料對齊 我們可以計算兩個dataframe的加和,pandas會自動將這兩個dataframe進行資料對齊,如果對不上的資料會被置為nan not a number 首先我們來建立兩個dataframe impo...