挿入された行のIDを取得する最良の方法?


1119

IDENTITY挿入された行を取得する最良の方法は何ですか?

私が知っている@@IDENTITYIDENT_CURRENTSCOPE_IDENTITYが、それぞれに付属の長所と短所を理解していません。

誰かが違いを説明してくれませんか、それぞれをいつ使うべきですか?


5
INSERT INTO Table1(fields...) OUTPUT INSERTED.id VALUES (...)、または古いメソッド:INSERT INTO Table1(fields...) VALUES (...); SELECT SCOPE_IDENTITY();ExecuteScalar()を使用してc#で取得できます。
S.Serpooshan 2016年

4
それは他の答えよりも優れていますか?(また-なぜこれをコメントではなく回答として投稿しないのですか?)完全な回答を書いてください(そして、これが投稿されたものよりも優れたオプションである理由を説明してください-バージョン固有の場合は、そう言ってください)。
2016年

それはちょうど短い要約のようなものです。; D受け入れられた回答はOUTPUT句の構文に言及しておらず、サンプルがありません。また、他の投稿のサンプルはそれほどきれいではありません...
S.Serpooshan

2
@saeedserpooshan-それを編集してください。あなたはそれを行うことができますね。その回答がいつ投稿されたかを確認しますか?これは、OUTPUTSQL Server の句よりも古いものです。
2016年

回答:


1435
  • @@IDENTITY現在のセッションの任意のテーブルについて、すべてのスコープにわたって生成された最後のID値を返します。 スコープを超えているため、ここ注意する必要があります。現在のステートメントではなく、トリガーから値を取得できます。

  • SCOPE_IDENTITY()現在のセッションおよび現在のスコープ内の任意のテーブルに対して生成された最後のID値を返します。 一般的に何を使いたいか

  • IDENT_CURRENT('tableName')任意のセッションおよびスコープの特定のテーブルに対して生成された最後のID値を返します。これにより、上記の2つが必要なものではない場合(非常にまれ)に、値を取得するテーブルを指定できます。また、@ Guy Starbuckが述べたように、「レコードを挿入していないテーブルの現在のIDENTITY値を取得する場合に使用できます。」

  • ステートメントのOUTPUT句を使用すると、そのINSERTステートメントを介して挿入されたすべての行にアクセスできます。特定のステートメントにスコープが設定されているため、上記の他の関数よりも簡単です。ただし、これはもう少し冗長で(テーブル変数/一時テーブルに挿入してからクエリする必要があります)、ステートメントがロールバックされるエラーシナリオでも結果が得られます。つまり、クエリが並列実行プランを使用している場合、これがIDを取得するための唯一の保証された方法です(並列処理をオフにする以外)。ただし、トリガーの前に実行されトリガーによって生成された値を返すために使用することはできません。


48
SCOPE_IDENTITY()が誤った値を返すという既知のバグ:blog.sqlauthority.com/2009/03/24/…回避策は、マルチプロセッサパラレルプランでINSERTを実行しないか、OUTPUT句
KMを

3
ほとんどの場合、「ID」が必要になるたびに、挿入したレコードのキーを知りたいと思っていました。そのような場合は、OUTPUT句を使用します。他に何かが必要な場合は、bdukesの応答を読んで理解する努力をしてください。
ジェリー

3
ではoutput、あなたは結果を格納する一時テーブルを作成し、クエリを実行する必要はありません。ただ、オフのままinto出力句の一部を、それが結果セットに出力するでしょう。
spb 2014年

96
他のユーザーをパニックから救うために、SQL Server 2008 R2 Service Pack 1の累積的な更新5で上記のバグが修正されました
GaTechThomas

1
@niico、私は勧告は、それはそれがある、されていないのと同じだと思うOUTPUTので、限り、あなたはトリガを使用していないとエラーを処理しているとして、「最高」ですが、SCOPE_IDENTITY最も簡単であり、非常に稀に問題がある
bdukes

180

挿入されたIDを取得する最も安全で正確な方法は、output句を使用することだと思います。

たとえば(次のMSDN記事から引用)

USE AdventureWorks2008R2;
GO
DECLARE @MyTableVar table( NewScrapReasonID smallint,
                           Name varchar(50),
                           ModifiedDate datetime);
INSERT Production.ScrapReason
    OUTPUT INSERTED.ScrapReasonID, INSERTED.Name, INSERTED.ModifiedDate
        INTO @MyTableVar
VALUES (N'Operator error', GETDATE());

--Display the result set of the table variable.
SELECT NewScrapReasonID, Name, ModifiedDate FROM @MyTableVar;
--Display the result set of the table.
SELECT ScrapReasonID, Name, ModifiedDate 
FROM Production.ScrapReason;
GO

3
はい、これは正しい方法です
。SQLServer

1
@HLGEM SQL Server 2005にはMSDNページOUTPUTがあり、SQL Server 2000以前のバージョンのように見えないようです
bdukes

6
わー!出力節が揺れる:)それは私の現在の仕事を簡単にするでしょう。以前にその声明を知りませんでした。君たちありがとう!
SwissCoder、

8
挿入されたIDを取得するための非常に簡潔な例については、stackoverflow.com
Luke

INTOをOUTPUTとともに使用することをお勧めします。参照:blogs.msdn.microsoft.com/sqlprogrammability/2008/07/11/… (ここのコメントから:stackoverflow.com/questions/7917695/…
shlgug

112

私は他の人たちと同じことを言っているので、誰もが正しいので、私はそれをより明確にしようとしています。

@@IDENTITYクライアントのデータベースへの接続によって挿入された最後のもののIDを返します。
ほとんどの場合、これは問題なく機能しますが、トリガーが実行され、わからない新しい行が挿入され、必要な行ではなく、この新しい行からIDが取得される場合があります。

SCOPE_IDENTITY()この問題を解決します。データベースに送信したSQLコードに最後に挿入したもののIDを返します。トリガーが実行されて余分な行が作成されても、誤った値が返されることはありません。やったー

IDENT_CURRENT誰かが挿入した最後のIDを返します。他のアプリが偶然に別の行を挿入した場合、その行のIDではなく、その行のIDを取得します。

安全にプレイしたい場合は、常にを使用してくださいSCOPE_IDENTITY()。あなたがこだわり、@@IDENTITY後で誰かがトリガーを追加することを決めた場合、すべてのコードが壊れます。


64

新しく挿入された行のIDを取得する最良の(読み取り:安全)方法は、次のoutput句を使用することです。

create table TableWithIdentity
           ( IdentityColumnName int identity(1, 1) not null primary key,
             ... )

-- type of this table's column must match the type of the
-- identity column of the table you'll be inserting into
declare @IdentityOutput table ( ID int )

insert TableWithIdentity
     ( ... )
output inserted.IdentityColumnName into @IdentityOutput
values
     ( ... )

select @IdentityValue = (select ID from @IdentityOutput)

5
SQLサーバーのクラスタリングは高可用性機能であり、並列処理には影響しません。scope_identity()とにかく単一行の挿入(の最も一般的なケース)が並列プランを取得することは非常にまれです。そして、このバグはこの回答の1年以上前に修正されました。
マーティンスミス

並列処理とはどういう意味ですか。
user1451111 2017年

@MartinSmithクライアントは、サーバークラスターでダウンタイムを許容してこの問題を修正するCUをインストールすることを望んでいなかった(冗談ではない)ため、唯一の解決策は、のoutput代わりに使用するすべてのSQLを書き換えることでしたscope_identity()。回答のクラスタリングに関するFUDを削除しました。
Ian Kemp

1
ありがとう、これは私が見つけた唯一の例であり、変数の出力から値を出力するのではなく、出力から値を使用する方法を示しています。
ショーンレイ

26

追加

SELECT CAST(scope_identity() AS int);

あなたの挿入SQLステートメントの終わりに、そして

NewId = command.ExecuteScalar()

それを取得します。


18

Entity Frameworkを使用する場合、内部的にこのOUTPUT手法を使用して、新しく挿入されたID値を返します

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID ]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

出力結果は一時テーブル変数に格納され、テーブルに結合されて、テーブルから行の値を返します。

注:EFが一時テーブルを実際のテーブルに内部結合する理由はわかりません(どのような状況で2つが一致しない場合があります)。

しかし、それがEFの役割です。

この手法(OUTPUT)は、SQL Server 2008以降でのみ使用できます。

編集 -参加の理由

Entity Frameworkが単にOUTPUT値を使用するのではなく、元のテーブルに結合する理由は、EFもこの手法を使用しrowversionて新しく挿入された行を取得するためです。

Timestamp属性を使用 して、エンティティフレームワークモデルで楽観的同時実行を使用できます。🕗

public class TurboEncabulator
{
   public String StatorSlots)

   [Timestamp]
   public byte[] RowVersion { get; set; }
}

これを行うと、Entity Frameworkはrowversion新しく挿入された行のを必要とします。

DECLARE @generated_keys table([Id] uniqueidentifier)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID INTO @generated_keys
VALUES('Malleable logarithmic casing');

SELECT t.[TurboEncabulatorID], t.[RowVersion]
FROM @generated_keys AS g 
   JOIN dbo.TurboEncabulators AS t 
   ON g.Id = t.TurboEncabulatorID 
WHERE @@ROWCOUNT > 0

そして、これを取得するためにTimetsampあなたがすることはできません使用OUTPUT句を。

これは、テーブルにトリガーがあるTimestamp場合、OUTPUTが間違っているためです。

  • 最初の挿入。タイムスタンプ:1
  • OUTPUT句はタイムスタンプを出力します:1
  • トリガーは行を変更します。タイムスタンプ:2

テーブルにトリガーがある場合、返されるタイムスタンプは正しくありません。したがって、別のを使用する必要ありますSELECT

また、不適切な行バージョンに苦しんでも構わないとしても、別の行を実行するもう1つの理由SELECTrowversion、テーブル変数にを出力できないことです。

DECLARE @generated_keys table([Id] uniqueidentifier, [Rowversion] timestamp)

INSERT INTO TurboEncabulators(StatorSlots)
OUTPUT inserted.TurboEncabulatorID, inserted.Rowversion INTO @generated_keys
VALUES('Malleable logarithmic casing');

これを行う3番目の理由は、対称性のためです。UPDATEトリガーを使用してテーブルに対してを実行する場合、句使用できませんOUTPUT。やるしようUPDATEとすることはOUTPUTサポートされておらず、エラーになります。

これを行う唯一の方法は、フォローアップSELECTステートメントを使用することです。

UPDATE TurboEncabulators
SET StatorSlots = 'Lotus-O deltoid type'
WHERE ((TurboEncabulatorID = 1) AND (RowVersion = 792))

SELECT RowVersion
FROM TurboEncabulators
WHERE @@ROWCOUNT > 0 AND TurboEncabulatorID = 1

2
整合性を確保するためにそれらを一致させることを想像します(たとえば、楽観的同時実行モードでは、テーブル変数から選択しているときに、誰かが挿入行を削除した可能性があります)。また、あなたを愛してくださいTurboEncabulators:)
zaitsman 2017年

16

MSDN

@@ IDENTITY、SCOPE_IDENTITY、IDENT_CURRENTは、テーブルのIDENTITY列に挿入された最後の値を返すという点で同様の関数です。

@@ IDENTITYおよびSCOPE_IDENTITYは、現在のセッションの任意のテーブルで生成された最後のID値を返します。ただし、SCOPE_IDENTITYは現在のスコープ内でのみ値を返します。@@ IDENTITYは特定のスコープに限定されません。

IDENT_CURRENTはスコープとセッションによって制限されません。指定されたテーブルに限定されます。IDENT_CURRENTは、任意のセッションおよびスコープの特定のテーブルに対して生成されたID値を返します。詳細については、IDENT_CURRENTを参照してください。

  • IDENT_CURRENTは、テーブルを引数として取る関数です。
  • テーブルにトリガーがあると、@@ IDENTITYは混乱する結果を返す可能性があります
  • ほとんどの場合、SCOPE_IDENTITYはヒーローです。

14

@@ IDENTITYは、現在のSQL接続を使用して挿入された最後のIDです。これは、新しいレコードに挿入されたIDが必要なだけの挿入ストアドプロシージャから戻るのに適した値であり、後でさらに行が追加されたかどうかは気にしません。

SCOPE_IDENTITYは、現在のSQL接続を使用して、現在のスコープに挿入された最後のIDです。つまり、挿入後にトリガーに基づいて2番目のIDENTITYが挿入された場合、SCOPE_IDENTITYには反映されず、実行した挿入のみが反映されます。 。正直なところ、私はこれを使用する理由がありませんでした。

IDENT_CURRENT(テーブル名)は、接続またはスコープに関係なく、最後に挿入されたIDです。これは、レコードを挿入していないテーブルの現在のIDENTITY値を取得する場合に使用できます。


2
この目的で@@ identityを使用しないでください。後でトリガーを追加すると、データの整合性が失われます。@@ identiyは非常に危険な行為です。
HLGEM、

1
「<< not >>があるテーブルの値は、レコードを挿入していません。」本当に?
Abdul Saboor 2013

13

SQL Serverの他のバージョンと話すことはできませんが、2012年には、直接出力することで問題なく動作します。一時テーブルを気にする必要はありません。

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES (...)

ちなみに、この手法は複数の行を挿入するときにも機能します。

INSERT INTO MyTable
OUTPUT INSERTED.ID
VALUES
    (...),
    (...),
    (...)

出力

ID
2
3
4

後でかかわらず、それを使用したい場合、私はあなたが一時テーブル必要想像
JohnOsborne

@JohnOsborne必要に応じて一時テーブルを使用することもできますが、私の要件はの要件ではないということですOUTPUT。一時テーブルが必要ない場合、クエリははるかに単純になります。
MarredCheese

10

常に使用SCOPE_IDENTITY()は、他の何かの必要があることはありません。


13
なく、かなり決してなく、99倍から100倍のうち、あなたはSCOPE_IDENTITY()を使用します。
CJM

他に何を使用したことがありますか?
erikkallen 2009年

11
INSERT-SELECTで複数の行を挿入する場合、OUTPUT句
KM

1
@KM:はい、ただし、scope_identityと@@ identityとident_currentを参照しました。OUTPUTは完全に異なるクラスであり、多くの場合便利です。
erikkallen 2010年

2
この質問に対するOrryの(stackoverflow.com/a/6073578/2440976)回答を確認してください-並列処理で、ベストプラクティスとして、彼の設定に従うのが賢明です...素晴らしいです!
ダン・B

2

を作成し、uuidそれを列に挿入します。次に、uuidを使用して行を簡単に識別できます。これが、実装できる唯一の100%機能するソリューションです。他のすべてのソリューションは複雑すぎるか、同じエッジケースで機能しません。例えば:

1)行を作成する

INSERT INTO table (uuid, name, street, zip) 
        VALUES ('2f802845-447b-4caa-8783-2086a0a8d437', 'Peter', 'Mainstreet 7', '88888');

2)作成された行を取得する

SELECT * FROM table WHERE uuid='2f802845-447b-4caa-8783-2086a0a8d437';

uuidデータベースにのインデックスを作成することを忘れないでください。したがって、行はより速く検出されます。
フランク・ロス

node.jsの場合、このモジュールを使用してuuid:を作成できますhttps://www.npmjs.com/package/uuidconst uuidv4 = require('uuid/v4'); const uuid = uuidv4()
フラン

GUIDはID値ではなく、単純な整数と比較していくつかのバックドローがあります。
アレハンドロ

1

挿入する行のIDを保証するもう1つの方法は、ID値を指定し、SET IDENTITY_INSERT ON次にを使用することOFFです。これにより、IDの値が正確にわかるようになります。値が使用されていない限り、これらの値をID列に挿入できます。

CREATE TABLE #foo 
  ( 
     fooid   INT IDENTITY NOT NULL, 
     fooname VARCHAR(20) 
  ) 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (1, 
             'one'), 
            (2, 
             'Two') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

INSERT INTO #foo 
            (fooname) 
VALUES      ('Three') 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

-- YOU CAN INSERT  
SET IDENTITY_INSERT #foo ON 

INSERT INTO #foo 
            (fooid, 
             fooname) 
VALUES      (10, 
             'Ten'), 
            (11, 
             'Eleven') 

SET IDENTITY_INSERT #foo OFF 

SELECT @@Identity            AS [@@Identity], 
       Scope_identity()      AS [SCOPE_IDENTITY()], 
       Ident_current('#Foo') AS [IDENT_CURRENT] 

SELECT * 
FROM   #foo 

これは、別のソースからデータをロードしたり、2つのデータベースからデータをマージしたりする場合などに、非常に役立つテクニックです。


0

これは古いスレッドですが、サーバーを再起動し後のID値のギャップのような、古いバージョンのSQL ServerのIDENTITY列のいくつかの落とし穴を回避する新しい方法があります。シーケンスはSQL Server 2016以降で使用できます。これは、TSQLを使用してSEQUENCEオブジェクトを作成する新しい方法です。これにより、SQL Serverで独自の数値シーケンスオブジェクトを作成し、その増分方法を制御できます。

次に例を示します。

CREATE SEQUENCE CountBy1  
    START WITH 1  
    INCREMENT BY 1 ;  
GO  

次に、TSQLで次のシーケンスIDを取得するには、次のようにします。

SELECT NEXT VALUE FOR CountBy1 AS SequenceID
GO

CREATE SEQUENCEおよびNEXT VALUE FORへのリンクは次のとおりです


シーケンスには、ギャップ(実際には問題ではない)のように、まったく同じアイデンティティの問題があります。
アレハンドロ

-1

Insertステートメントの後に、これを追加する必要があります。そして、データが挿入されているテーブル名について確認してください。挿入ステートメントによって現在影響を受けている行はありません。

IDENT_CURRENT('tableName')

2
このまったく同じ提案が以前に何度か回答されたことに気づきましたか?
TT。

はい。しかし、私は自分の方法で解決策を説明しようとしています。
Khan Ataur Ra​​hman 2017

そして、他の誰かがinsertステートメントとIDENT_CURRENT()呼び出しの間に行を挿入した場合、他の誰かが挿入したレコードのIDを取得します。上記のほとんどの返信で述べたように、ほとんどの場合、SCOPE_IDENTITY()を使用する必要があります。
トロンスター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.