OUTPUT 句 の紹介
SQL Server の MVP を受賞したということで、
SQLWorld★大阪#10 で SQL Server 2012 Express の紹介をしました - お だ のスペース
で紹介している T-SQL をもう少し掘り下げて説明していこうかと思います。
最初は、OUTPUT 句の紹介を。
SQL Server 2005 から追加されました。
OUTPUT 句 (Transact-SQL)
INSERT/UPDATE/DELETE/(MERGE SQL Server 2008 から追加) で追加/更新/削除 された行の情報を取得出来ます。
使いどころとしては、ID 列(Identity) や 計算列 の値を取得したり、更新操作で影響を受けたデータを次の処理で使ったり…等が主だと思います。
それでは、クエリを見ながら解説を。
use [テスト] -- 準備 create table [マスタ] ( [Id] int identity(1,1) not null primary key, [名前] nvarchar(10) not null, [名前長さ] as len([名前]) ) go insert into [マスタ] values ('さとう') insert into [マスタ] values ('たなか') insert into [マスタ] values ('すずき') go
テーブルを作成して、テストデータを INSERT します。
insert into [マスタ] output inserted.* values ( 'あべ' ) select @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('マスタ') update [マスタ] set [名前] = '旧姓たなか' output deleted.*, inserted.* where [名前] = 'たなか' delete from [マスタ] output deleted.* where [名前] = 'すずき'
OUTPUT 句では、INSERTED/DELETED で影響を受けた列を取得します。
- INSERTED:INSERT/UPDATE で使用可能。追加/更新された後のデータを取得する際に使用する接頭辞
- DELETED:UPDATE/DELETE で使用可能。更新/削除される前のデータを取得する際に使用する接頭辞
INSERT では、追加された行のデータを
UPDATE では、更新される前と更新後のデータを
DELETE では、削除される前のデータを
それぞれ取得することが出来ます。
INSERT した結果の ID 列や計算列の値、UPDATE した場合に計算列がどう変わったのかも取得出来ます。
OUTPUT 句すげー!
-- 複数件の同時更新も可能 insert into [マスタ] output inserted.* select [名前] from [マスタ] select @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('マスタ') update [マスタ] set [名前] = '****' output deleted.*, inserted.* delete from [マスタ] output deleted.*
複数件をまとめて更新する場合でも、使用することが出来ます。
INSERT された ID 列(Identity) の結果を取得する方法として、
@@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('
@@IDENTITY (Transact-SQL)
SCOPE_IDENTITY (Transact-SQL)
IDENT_CURRENT (Transact-SQL)
これらは最後の1行しか返しませんので、上のようなクエリ(INSERT-SELECT)や SQL Server 2008 で追加された VALUES をカンマ区切りで複数個渡すクエリ(INSERT VALUES (), (), ...))で複数行の追加を行うと最後の1行の ID 値しか取得することは出来ません。
OUTPUT 句 を利用すると、複数行の ID 値も取得する事が出来ます。
OUTPUT 句すげー!
-- INTO を使って テーブル/テーブル変数 に結果を入れることも可能 declare @更新結果 table ( [Seq] int identity(1,1), [Action] nchar(3), [変更前_Id] int, [変更前_名前] nvarchar(10), [変更前_名前長さ] int, [変更後_Id] int, [変更後_名前] nvarchar(10), [変更後_名前長さ] int ) insert into [マスタ] output 'INS', inserted.* into @更新結果 ( [Action], [変更後_Id], [変更後_名前], [変更後_名前長さ] ) values ('さとう'), ('たなか'), ('すずき') insert into [マスタ] output 'INS', inserted.* into @更新結果 ( [Action], [変更後_Id], [変更後_名前], [変更後_名前長さ] ) values ( 'あべ' ) update [マスタ] set [名前] = '旧姓たなか' output 'UPD', deleted.*, inserted.* into @更新結果 where [名前] = 'たなか' delete from [マスタ] output 'DEL', deleted.* into @更新結果 ( [Action], [変更前_Id], [変更前_名前], [変更前_名前長さ] ) where [名前] = 'すずき' insert into [マスタ] output 'INS', inserted.* into @更新結果 ( [Action], [変更後_Id], [変更後_名前], [変更後_名前長さ] ) select [名前] from [マスタ] update [マスタ] set [名前] = '****' output 'UPD', deleted.*, inserted.* into @更新結果 delete from [マスタ] output 'DEL', deleted.* into @更新結果 ( [Action], [変更前_Id], [変更前_名前], [変更前_名前長さ] ) select * from @更新結果 go
OUTPUT 句で取得出来る結果を テーブルやテーブル変数に追加する事も出来ます。
これを利用することで、
- 更新されたデータだけを対象に次の処理を行う
- 履歴テーブルに退避
等々が出来ます。
OUTPUT 句すげー!
SQL Server 2005 以降を利用しているなら、SCOPE_IDENTITY() は使わずに OUTPUT 句を利用しましょう!
今回は、MERGE については省きます。MERGE の時に OUTPUT 句も絡めて説明する予定です。
ちなみに C# から、1件 INSERT した結果の IDENTITY 列を取るコードはこんな感じ
トランザクション/例外処理は省略
using (var conn = new SqlConnection("〜")) using (var cmd = new SqlCommand(@"insert into [マスタ] output inserted.[Id] values ( 'たなか' ) ", conn)) { conn.Open(); // ExecuteNonQuery ではなく、ExecuteScalar を使用して、結果を受け取る var id = cmd.ExecuteScalar(); Console.WriteLine(id); conn.Close(); }
ストアドだけではなく、C# 等から発行するアドホックなクエリでも当然使えますので、使えるところでは迷わず OUTPUT 句を使いましょう!
クエリ全文
use [テスト] -- 準備 create table [マスタ] ( [Id] int identity(1,1) not null primary key, [名前] nvarchar(10) not null, [名前長さ] as len([名前]) ) go insert into [マスタ] output inserted.* values ('さとう'), ('たなか'), ('すずき') select @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('マスタ') go -- マスタのデータ変更 insert into [マスタ] output inserted.* values ( 'あべ' ) select @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('マスタ') update [マスタ] set [名前] = '旧姓たなか' output deleted.*, inserted.* where [名前] = 'たなか' delete from [マスタ] output deleted.* where [名前] = 'すずき' -- 複数件の同時更新も可能 insert into [マスタ] output inserted.* select [名前] from [マスタ] select @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT('マスタ') update [マスタ] set [名前] = '****' output deleted.*, inserted.* delete from [マスタ] output deleted.* -- INTO を使って テーブル/テーブル変数 に結果を入れることも可能 declare @更新結果 table ( [Seq] int identity(1,1), [Action] nchar(3), [変更前_Id] int, [変更前_名前] nvarchar(10), [変更前_名前長さ] int, [変更後_Id] int, [変更後_名前] nvarchar(10), [変更後_名前長さ] int ) insert into [マスタ] output 'INS', inserted.* into @更新結果 ( [Action], [変更後_Id], [変更後_名前], [変更後_名前長さ] ) values ('さとう'), ('たなか'), ('すずき') insert into [マスタ] output 'INS', inserted.* into @更新結果 ( [Action], [変更後_Id], [変更後_名前], [変更後_名前長さ] ) values ( 'あべ' ) update [マスタ] set [名前] = '旧姓たなか' output 'UPD', deleted.*, inserted.* into @更新結果 where [名前] = 'たなか' delete from [マスタ] output 'DEL', deleted.* into @更新結果 ( [Action], [変更前_Id], [変更前_名前], [変更前_名前長さ] ) where [名前] = 'すずき' insert into [マスタ] output 'INS', inserted.* into @更新結果 ( [Action], [変更後_Id], [変更後_名前], [変更後_名前長さ] ) select [名前] from [マスタ] update [マスタ] set [名前] = '****' output 'UPD', deleted.*, inserted.* into @更新結果 delete from [マスタ] output 'DEL', deleted.* into @更新結果 ( [Action], [変更前_Id], [変更前_名前], [変更前_名前長さ] ) select * from @更新結果 go -- 後片付け drop table [マスタ] go
*1:それぞれ微妙に動きが違うので、使い方誤らないで!