Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/26.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 确定SQL触发器中的旧主键_Sql Server_Sql Server 2000_Triggers - Fatal编程技术网

Sql server 确定SQL触发器中的旧主键

Sql server 确定SQL触发器中的旧主键,sql-server,sql-server-2000,triggers,Sql Server,Sql Server 2000,Triggers,我以前在什么地方做过,我肯定 我有一个SQL Server 2000表,需要在更新时记录对字段的更改,并将其插入第二个日志记录表中。我使用的结构的简化版本如下: MainTable ID varchar(10) PRIMARY KEY DESCRIPTION varchar(50) LogTable OLDID varchar(10) NEWID varchar(10) 对于任何其他领域,类似这样的工作都会非常有效: Select i.DESCRIPTION As New, d.DESCRI

我以前在什么地方做过,我肯定

我有一个SQL Server 2000表,需要在更新时记录对字段的更改,并将其插入第二个日志记录表中。我使用的结构的简化版本如下:

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被更改,连接将失败

我不能以这种方式修改表,我在这个数据库中唯一的能力就是创建触发器

或者,有没有人可以教我时间旅行,我会回到过去,问自己当时是怎么做到的?干杯:)


编辑:

我想我需要澄清一些事情。这实际上不是我的数据库,它是一个预先存在的系统,除了编写这个触发器之外,我几乎无法控制它

我的问题是,如果所述主键已更改,如何检索旧主键。我不需要被告知我不应该更改主键或关于追逐外键等。这不是我的问题:)

这仅在只有一行的情况下有效。否则,就没有链接新行和旧行的“锚”。因此,检查触发器中插入的>1。

--new---- 将应用程序无法更改的标识列添加到表中,然后可以使用该新列将插入的表连接到触发器中已删除的表:

ALTER TABLE YourTableName ADD
    PrivateID int NOT NULL IDENTITY (1, 1)
GO
----旧的---- 永远不要更新/更改键值。如何做到这一点并修复所有外键

我不建议使用不能处理一组行的触发器


如果必须更改键,请插入具有正确新键和值的新行,如果您正在执行此操作,请使用SCOPE_IDENTITY()。删除旧的行。记录已更改为新行的键的旧行,您现在应该拥有该键。我希望您的日志中更改的密钥上没有外键…

我认为这是不可能的。想象一下,如果表中有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行发生的情况。更重要的是,你能描述一下他们之间的区别吗?所有告诉你哪些列已经更新的信息都帮不了你


如果在这种情况下表上可能有一个额外的键(例如描述是唯一的),并且您的更新规则允许这样做,您可以编写触发器以防止同时更新两个键,然后,您可以使用尚未更新的键来关联这两个表。

如果您必须处理多个行插入/更新,并且没有保证不会更改的备用键,那么我可以看到的唯一方法是使用INSTEAD OF触发器。例如,在触发器中,您可以将原始的insert/update命令拆分为每行一个命令,在插入/更新之前获取每个旧id。

在SQL Server的触发器中,您可以访问两个表:deleted和inserted。这两个问题都已经提到了。以下是它们的工作方式,具体取决于触发器触发的动作:

插入操作

  • 已删除-未使用
  • 插入-包含要添加到表中的新行
删除操作

  • 已删除-包含从表中删除的行
  • 插入-未使用
更新操作

  • 已删除-包含更新操作之前存在的行
  • 已插入-包含更新操作后将存在的行
这些功能在各个方面都像表格。因此,完全可以使用基于行的操作,如以下操作(操作仅存在于审核表上,DateChanged也是如此):


是否可以假定触发器中呈现给您的插入表和删除表的顺序是相同的?

您可以在table MainTable上创建一个新的标识列(命名为例如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没有它;所以你必须想出另一种方法

一个快速但遗憾的是不可靠的版本是编写一个instead of update触发器,查看插入的行中是否有主键未在更新的表中找到,反之亦然。这将捕获大部分(但不是全部)错误:

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中强制使用的for-each-row触发器的等价物……但我认为在大规模更新中性能会很差,因为这不是SQL Server的本机行为。另外,这取决于一个假设,即我实际上无法在任何地方找到文档,因此我不愿意依赖它。但这种结构的代码在我的SQLServer2008R2安装中似乎可以正常工作。这篇文章结尾的脚本
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
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