質問

以前どこかでやったことがありますが、確かにそう思います!

SQL Server 2000 テーブルがあり、更新時にフィールドへの変更を記録し、2 番目のログ テーブルに挿入する必要があります。私が使用している構造の簡略版は以下のとおりです。

MainTable
ID varchar(10) PRIMARY KEY
DESCRIPTION varchar(50)

LogTable
OLDID varchar(10)
NEWID varchar(10)

他のフィールドでは、次のようなものがうまく機能します。

Select i.DESCRIPTION As New, d.DESCRIPTION As Old 
From Inserted i
LEFT JOIN Deleted d On i.ID=d.ID

...しかし、ID が変更されると明らかに結合は失敗します。

テーブルを変更することはできません。このデータベースで私ができる唯一の権限は、トリガーを作成することです。

あるいは、私にタイムトラベルを教えてくれる人はいますか?過去に戻って、どうやってこれをしたのか当時の自分に問いかけてみましょう。乾杯 :)


編集:

ここでいくつかのことを明確にする必要があると思います。これは 実際には私のデータベースではありません, 、これは既存のシステムであり、このトリガーを作成すること以外はほとんど制御できません。

私の質問は、主キーが変更された場合に古い主キーを取得するにはどうすればよいですかということです。主キーを変更すべきではないとか、外部キーを追いかける必要はないなどと言われる必要はありません。それは私の問題ではありません:)

役に立ちましたか?

解決

トリガーで表示されるINSERTEDテーブルとDELETEDテーブルが同じ順序であることが保証されていると仮定することは可能ですか?

他のヒント

DECLARE @OldKey int, @NewKey int;

SELECT @Oldkey = [ID] FROM DELETED;
SELECT @NewKey = [ID] FROM INSERTED;

これは、単一の行がある場合にのみ機能します。それ以外の場合、「アンカー」はありません。古い行と新しい行をリンクします。そのため、>のトリガーを確認してください。 INSERTEDの1。

それは可能だとは思わない。テーブルに4つの行があるとします:

1  Val1
2  Val2
3  Val3
4  Val4

次のアップデートを発行します。

UPDATE MainTable SET
ID = CASE ID WHEN 1 THEN 2 WHEN 2 THEN 1 ELSE ID END
Description = CASE ID WHEN 3 THEN 'Val4' WHEN 4 THEN 'Val3' ELSE Description END

今、行1と行に何が起こったのかをどのように区別しますか? 2行3と& 4.さらに重要なこととして、それらの違いを説明できますか?どの列が更新されたかを示すものはすべて役に立ちません。

この場合、テーブルに追加のキーがあり(たとえば、説明は一意)、更新ルールで許可されている場合、両方のキーの同時更新を防ぐトリガーを記述できます。キーは、2つのテーブルを関連付けるために更新されていません。

複数行の挿入/更新を処理する必要があり、変更しないことが保証されている代替キーがない場合、これを確認できる唯一の方法は、INSTEAD OFトリガーを使用することです。たとえば、トリガーでは、元の挿入/更新コマンドを行ごとに1つのコマンドに分割し、挿入/更新する前に古いIDをそれぞれ取得できます。

SQL Serverのトリガー内では、削除と挿入の2つのテーブルにアクセスできます。これらは両方ともすでに言及されています。トリガーが実行されるアクションに応じて、それらがどのように機能するかを以下に示します。

操作を挿入

  • 削除-使用されていません
  • 挿入済み-テーブルに追加される新しい行が含まれています

操作の削除

  • deleted-テーブルから削除される行が含まれています
  • 挿入済み-使用されていません

更新操作

  • deleted-UPDATE操作前に存在していた行を含む
  • 挿入済み-UPDATE操作後に存在する行を含む

これらの関数は、テーブルのようなあらゆる面で機能します。したがって、次のような行ベースの操作を使用することは完全に可能です(DateChangedと同様に、操作は監査テーブルにのみ存在します):

INSERT INTO MyAuditTable
(ID, FirstColumn, SecondColumn, ThirdColumn, Operation, DateChanged)
VALUES
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-Before', GETDATE()
FROM deleted
UNION ALL
SELECT ID, FirstColumn, SecondColumn, ThirdColumn, 'Update-After', GETDATE()
FROM inserted

---- new ---- アプリケーションが変更できないID列をテーブルに追加すると、その新しい列を使用して、トリガー内の削除されたテーブルに挿入されたテーブルを結合できます。

ALTER TABLE YourTableName ADD
    PrivateID int NOT NULL IDENTITY (1, 1)
GO

----古い---- キー値を更新/変更しないでください。どうすればこれを行い、すべての外部キーを修正できますか?

行のセットを処理できないトリガーを使用することはお勧めしません。

キーを変更する必要がある場合は、適切な新しいキーと値で新しい行を挿入し、それがあなたのしていることであればSCOPE_IDENTITY()を使用します。古い行を削除します。古い行を新しい行のキーに変更したことをログに記録します。これは現在必要です。ログの変更されたキーに外部キーがないことを願っています...

テーブルMainTableに新しいID列(たとえば、correlationidなど)を作成し、この列を使用して挿入および削除されたテーブルを相関させることができます。 この新しい列は、既存のコードに対して透過的である必要があります。

INSERT INTO LOG(OLDID, NEWID)
SELECT deleted.id AS OLDID, inserted.id AS NEWID
FROM inserted 
INNER JOIN deleted 
    ON inserted.correlationid = deleted.correlationid

注意してください、ログテーブルに重複レコードを挿入できます。

もちろん、誰もテーブルの主キーを変更すべきではありません。しかし、トリガーが (部分的に) 行うべきことは、人々がやるべきでないことをしないようにすることです。主キーへの変更をインターセプトして停止するトリガーを作成するのは、Oracle や MySQL では簡単な作業ですが、SQL Server ではまったく簡単ではありません。

もちろん、あなたができるようにしたいことは、単純に次のようなことを行うことです。

if exists
  (
  select *
    from inserted changed
           join deleted old
   where changed.rowID = old.rowID
     and changed.id != old.id
  )
... [roll it all back]

人々が ROWID に相当する SQL Server を検索するのはこのためです。まあ、SQL Server にはそれがありません。したがって、別のアプローチを考え出す必要があります。

高速ではありますが、残念なことに完璧ではありません。このバージョンでは、挿入された行のいずれかに更新されたテーブルに見つからない主キーがあるかどうか、またはその逆を確認する更新トリガーの代わりに記述します。これにより、すべてではありませんが、ほとんどのエラーが検出されます。

if exists
  (
  select *
    from inserted lost
           left join updated match
             on match.id = lost.id
   where match.id is null
  union
  select *
    from deleted new
           left join inserted match
             on match.id = new.id
    where match.id is null
  )
  -- roll it all back

しかし、これでもまだ次のようなアップデートをキャッチできません...

update myTable
   set id = case
              when id = 1 then 2 
              when id = 2 then 1
              else id
              end

ここで、挿入テーブルと削除テーブルを同時にカーソル移動すると適切に一致する行が得られるような方法で、挿入テーブルと削除テーブルが順序付けされていると仮定してみました。そしてこれはうまくいくようです。実際には、このトリガーを、Oracle で使用可能で MySQL では必須の各行トリガーと同等のものにします。しかし、これは SQL Server のネイティブな動作ではないため、大規模な更新で​​はパフォーマンスが低下すると思います。また、これは実際にどこにも文書化されていない仮定に依存しているため、依存することに消極的です。しかし、そのように構造化されたコードは、私の SQL Server 2008 R2 インストール環境では正しく動作するようです。この投稿の最後にあるスクリプトでは、高速ではあるが防爆ではないソリューションの動作と、2 番目の擬似 Oracle ソリューションの動作の両方を強調しています。

私の仮定が文書化され、Microsoft によって保証されている場所を誰かが教えてくれたら、私はとても感謝するでしょう...

begin try
  drop table kpTest;
end try
begin catch
end catch
go

create table kpTest( id int primary key, name nvarchar(10) )
go

begin try
  drop trigger kpTest_ioU;
end try
begin catch
end catch
go

create trigger kpTest_ioU on kpTest
instead of update
as
begin
  if exists
    (
    select *
      from inserted lost
             left join deleted match
               on match.id = lost.id
     where match.id is null
    union
    select *
      from deleted new
             left join inserted match
               on match.id = new.id
      where match.id is null
    )
      raisError( 'Changed primary key', 16, 1 )
  else
    update kpTest
       set name = i.name
      from kpTest
             join inserted i
               on i.id = kpTest.id
    ;
end
go

insert into kpTest( id, name ) values( 0, 'zero' );
insert into kpTest( id, name ) values( 1, 'one' );
insert into kpTest( id, name ) values( 2, 'two' );
insert into kpTest( id, name ) values( 3, 'three' );

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- This throws an error, appropriately
update kpTest set id = 5, name = 'FIVE' where id = 1
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- This allows the change, inappropriately
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
     , name = UPPER( name )
go

select * from kpTest

/*
0   ZERO
1   TWO   -- WRONG WRONG WRONG
2   ONE   -- WRONG WRONG WRONG
3   THREE
*/

-- Put it back
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
     , name = LOWER( name )
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

drop trigger kpTest_ioU
go

create trigger kpTest_ioU on kpTest
instead of update
as
begin
  declare newIDs cursor for select id, name from inserted;
  declare oldIDs cursor for select id from deleted;
  declare @thisOldID int;
  declare @thisNewID int;
  declare @thisNewName nvarchar(10);
  declare @errorFound int;
  set @errorFound = 0;
  open newIDs;
  open oldIDs;
  fetch newIDs into @thisNewID, @thisNewName;
  fetch oldIDs into @thisOldID;
  while @@FETCH_STATUS = 0 and @errorFound = 0
    begin
      if @thisNewID != @thisOldID
        begin
          set @errorFound = 1;
          close newIDs;
          deallocate newIDs;
          close oldIDs;
          deallocate oldIDs;
          raisError( 'Primary key changed', 16, 1 );
        end
      else
        begin
          update kpTest
             set name = @thisNewName
           where id = @thisNewID
          ;
          fetch newIDs into @thisNewID, @thisNewName;
          fetch oldIDs into @thisOldID;
        end
    end;
  if @errorFound = 0
    begin
      close newIDs;
      deallocate newIDs;
      close oldIDs;
      deallocate oldIDs;
    end
end
go

-- Succeeds, appropriately
update kpTest
   set name = UPPER( name )
go

select * from kpTest;

/*
0   ZERO
1   ONE
2   TWO
3   THREE
*/

-- Succeeds, appropriately
update kpTest
   set name = LOWER( name )
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/


-- Fails, appropriately
update kpTest 
   set id = case   
              when id = 1 then 2
              when id = 2 then 1
              else id
              end
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- Fails, appropriately
update kpTest 
   set id = id + 1
go

select * from kpTest;

/*
0   zero
1   one
2   two
3   three
*/

-- Succeeds, appropriately
update kpTest 
   set id = id, name = UPPER( name )
go

select * from kpTest;

/*
0   ZERO
1   ONE
2   TWO
3   THREE
*/

drop table kpTest
go
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top