如何在具有外键关系的两个表中插入值?

17

我创建了两张表:

  • 表格 tblStaff 包括列 id(主键,自增),name, age, address

  • 表格 tblRoleOfStaff 包括列 id(主键,自增),StaffId(外键参照 tblStaff 表格中的 id 列),RoleId

我有一个表单用于创建具有现有角色的新员工。 要插入的数据示例:

(name, age, address, roleId) = ('my name',20,'San Jose', 1)

我想在SQL Server 2014中编写存储过程,将新员工插入tblStaff表格,并将包含刚刚插入的staffId的新记录插入tbleRoleOfStaff表格。

我该怎么做?

如果我的问题与其他问题重复,我非常抱歉。我是SQL的初学者。 感谢任何帮助。


SQL Server没有“自动增量”。你确定你知道你正在使用哪个数据库吗? - Gordon Linoff
2
感谢Gordon Linoff,我正在使用SQL Server Management Studio,并将Id列设置为int类型,然后手动设置标识增量值为1。 - Ca Pham Van
3
在SQL Server中,这通常被称为"identity"列;在MySQL中,它是"auto increment"列;在Postgres中,它是"serial"列。它们都是同一种东西,但每个数据库的语法不同。 - Gordon Linoff
6个回答

19

使用 SCOPE_IDENTITY() 函数在第二个 insert 语句中将值插入到 tblRoleOfStuff 表中的 StaffId 列。例如:

insert into tblStaff values
(@name, @age, @address)

insert into tblRoleOfStuff values
(scope_identity(), @roleid)

编辑

这个答案有太多的评论,所以我想解释一下。

如果OP保证不使用任何触发器,他可以使用@@IDENTITY(不好的做法),这已经足够满足他的需求,但最好使用SCOPE_IDENTITY()(较好的做法)。

像@@IDENTITY一样,SCOPE_IDENTITY()将返回当前会话中创建的最后一个标识值,但它也将限制范围为您当前的作用域。换句话说,它将返回您明确创建的最后一个标识,而不是由触发器或用户定义函数创建的任何标识。

SCOPE_IDENTITY()将确保您获得当前操作的标识,而不是来自另一个连接或上次处理的标识。

为什么不使用IDENT_CURRENT?因为

IDENT_CURRENT没有限制范围和会话,它仅限于指定的表。IDENT_CURRENT返回在任何会话和任何范围内为特定表生成的标识值。

因此你可能会获取到最近的标识符,但不是当前的标识符。是的,OP也可以使用它,但在这种情况下(就像仅使用@@IDENTITY一样),这是一个不好的做法。

使用OUTPUT确实是一个好的做法,但对于只有一个标识符的情况来说过于复杂。如果OP需要在一次操作中处理多行数据,则需要使用OUTPUT


1
大多数情况下,@@identity 可能会得到你想要的结果,但是请搜索 @@Identity、SCOPE_IDENTITY 等。很容易编写触发器或多语句存储过程,这将混淆你真正想获取的标识。 - Matt
3
我建议使用**SCOPE_IDENTITY()**而不是其他任何东西(例如@@IDENTITY)来获取新插入的标识值。请参阅此博客文章以了解为什么要这样做 - marc_s
@marc_s,我仍然坚持使用OUTPUT子句,即使微软表示SCOPE_IDENTITY()存在不正确值的潜在可能性,还有请注意,本答案最初说的是@@IDENTITY。这个问题绝对是重复的!https://support.microsoft.com/en-us/kb/2019779 我明白在OP的特定示例中,SCOPE_IDENTITY()或@@IDENTITY应该足够了,但是事情会发生变化,比如其他人编写了一个触发器,他不知道等等。虽然我认为甚至那也是可能的,但我从未见过有人编写触发器来更改IDENTITY列并更改插入的输出值。 - Matt
1
@VahidFarahmandian,是的,对于一个繁忙的系统来说并不好(我已经在上面的评论中写过了),但这里有一个带有4个字段的简单表格,SCOPE_IDENTITY已经足够满足此目的。 - gofr1
@Matt,这个问题在2008 R2中已经解决了,而OP使用的是SQL Server 2014,所以他可以毫无问题地使用SCOPE_IDENTITY()/@@IDENTITY。我在我的答案中添加了关于触发器的信息。谢谢你的注意! - gofr1
显示剩余3条评论

7

因为看起来您正在逐行讨论一些人可能会告诉您使用系统变量,例如@@IDENTITY或其他一些变量,但为了更加确信,我建议使用insert语句的OUTPUT子句。 这种方法的好处是它可以轻松地适应处理多行。

DECLARE @Output AS TABLE (StaffId INT)

INSERT INTO tblStaff (name, age, address)
OUTPUT inserted.Id INTO @Output (StaffId)
VALUES (@name, @age, @address)

DECLARE @StaffId INT
SELECT @StaffId = StaffId FROM @Output

INSERT INTO tblRoleOfStaff (StaffId, RoleId)
VALUES (@StaffId,@RoleId)

不要使用@@IDENTITY的原因是,如果与您相关联的另一个操作被执行,例如触发器将另一行插入到另一个表中或更新数据库中的另一条记录,则可能会出现问题。当触发器修改相同的表时,SCOPE_IDENTITY也存在类似的缺陷。IDENT_CURRENT也有缺点。搜索互联网以了解更多信息,这里有大量优秀的资源。


2
您可以使用第一条插入语句的output
declare @tmp table(id int)
insert tblStaff (name, age, address)
OUTPUT inserted.Id INTO @tmp (id)
values (@name, @age, @address)

declare @roleId int = 1 --or whatever
insert tblRoleOfStaff (staffId,roleId)
select id, @roleId
from @tmp

您也可以同时插入多个角色。
create table Roles (roleId int identity(1,1) primary key, 
RoleName varchar(50),
isDefaultRole bit default 0
)
--mark some roles as default (`isDefaultRole = 1`)
--the 2nd insert will be
insert tblRoleOfStaff (staffId,roleId)
select id, roleId
from @tmp
cross join Roles
where isDefaultRole = 1

1

如果您每次只需要插入一个员工行,可以按照以下步骤操作:

begin try
 begin tran
  insert into tblStaff (name, age, address) values('my name',20,'San Jose');
  insert into tbleRoleOfStaff (StaffId, RoleId) values (SCOPE_IDENTITY(), 1); 
 commit
end try
begin catch
  IF @@trancount > 0 ROLLBACK;
end catch

-1

试试这个:

Create Procedure Pro_XXX()
AS
BEGIN

INSERT INTO tblStaff (name, age, address, roleId) VALUES ('my name',20,'San Jose', 1);

INSERT INTO tbleRoleOfStaff VALUES (staffId, roleId) VALUES (IDENT_CURRENT('tblStaff'),0)

END

请注意区分 IDENT_CURRENTSCOPE_IDENTITY@@IDENTITY 之间的差异。了解更多信息,请阅读 这里

1
Pinal Dave在你提供的那篇文章中实际上建议使用SCOPE_IDENTITY:为了避免以后添加触发器可能出现的问题,在你的T SQL语句或存储过程中始终使用SCOPE_IDENTITY()来返回最近添加行的标识。 - marc_s

-2

在插入到第一张表后使用类似以下的语句

DECLARE @staffId INT
SET @staffId = (SELECT TOP 1 id from tblStaff order by id desc)

INSERT INTO tblRoleOfStaff (staffId,roleId) VALUES (@staffId, 2)

2
这个在繁忙系统中不会起作用!你不能依赖于“事实”,即最后插入的行具有最高的Id - 在你读取该值之前可能已经发生了其他插入! - marc_s
同意。但考虑到表结构和这是一个尝试使用SQL的新手,我没有担心系统繁忙。我的错。 - Sam
@Sam,虽然很明显这在繁忙的系统上不起作用,但巧合也可能出现,使系统对用户“崩溃”。最好还是以正确的方式解释一下,对吧? - Sj03rs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接