SQL循环表将记录插入新表,然后获取新ID并插入其他表

Posted

技术标签:

【中文标题】SQL循环表将记录插入新表,然后获取新ID并插入其他表【英文标题】:SQL loop table inserting records into new table, then taking new ID and inserting into other table 【发布时间】:2019-03-26 13:38:05 【问题描述】:

我正在迁移一个旧的遗留系统,该系统大部分是平面数据库表,列太多。每个新设置都需要一个新列等,因此表格变得非常大。我正在尝试将此结构更改为关系结构,并且正在努力将旧的现有数据迁移到新结构中。这是我们拥有的旧表的示例:

CREATE TABLE [dbo].[User_OLD](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FullName] [nvarchar](50) NULL, 
    [Email] [nvarchar](50) NULL,
    [Setting1] [bit] NULL,
    [Setting1Value] [int] NULL,
    [Setting2] [bit] NULL,
    [Setting2Value] [int] NULL,
    [Setting3] [bit] NULL,
    [Setting3Value] [int] NULL,
    CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC));

这个表正在被迁移到多个表中,这里是一个例子:

CREATE TABLE [dbo].[User_NEW](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FullName] [nvarchar](50) NULL,
    [Email] [nvarchar](50) NULL,
    CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC));

CREATE TABLE [dbo].[UserSetting](
    [Id] [int] IDENTITY(1,1) NOT NULL,  
    [UserId] [int] NOT NULL,    
    [SettingName] [varchar](250) NOT NULL,  
    [SettingValue] [varchar](250) NOT NULL,         
    [CreatedOn] [datetime] NOT NULL,    
    CONSTRAINT [PK_UserSetting] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT FK_UserSetting_User FOREIGN KEY ([UserId]) REFERENCES User_NEW(Id));

所以问题是我需要从 User_OLD 获取记录并将其值插入 User_NEW,然后我需要获取 User_NEW.Id 并将其插入到 UserSetting 表中,相应的 Setting1、Setting1Value 进入新表列 SettingName 和 SettingValue。

如果你能帮助我编写一个可以实现的脚本,我将非常感激!

【问题讨论】:

如果你想在这个问题上找到我的“granadacoder”答案的话:***.com/questions/17662432/… 【参考方案1】:

处理此问题的最佳方法是不使用 SCOPE_IDENTITY 或其他强制您为此使用 RBAR 的方法。对您的新表稍作调整会使其变得更简单。

CREATE TABLE [dbo].[User_NEW](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FullName] [nvarchar](50) NULL,
    [Email] [nvarchar](50) NULL,
    UserID_OLD int not null
    CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC));

注意到新列 UserID_OLD 了吗?您可以在单个语句中插入所有用户,然后通过加入此表开始规范化过程。

insert User_NEW
(
    FullName
    , Email
    , UserID_OLD
)
select FullName
    , Email
    , Id
from User_OLD

insert UserSetting
(
    UserId
    , SettingName
    , SettingValue
)
select Setting1
    , Setting1Value
    , un.Id
from User_OLD u
join User_NEW un on un.UserID_OLD = u.Id

然后您只需对所有属性/值组合重复此插入。并在迁移完成后删除 UserID_OLD 列。

但是,您需要意识到您所拥有的是实体属性值设计,并且这种类型的东西有很多陷阱。一方面,您现在已将所有内容存储在 varchar 中,因此您无法在数据库级别验证数据。这里还有一个性能定时炸弹,因为所有内容都必须不断地转换为正确的数据类型。你必须小心你的转换,否则你会得到转换错误。 EAV 模式看起来非常棒,但在实践中它经常是有问题的。

【讨论】:

这看起来很酷,谢谢!感谢您提供有关 EAV 的信息,我不知道模式名称。我知道这不是最理想的情况,但它是时间相关的项目。出于好奇,有哪些替代模式的想法? 这是一种非常强大的混合方法。主要是关系数据,在这里和那里都有一点 EAV。我在您的指导中看到的最大问题是您丢失了非规范化表中的数据类型。 啊,是的,这实际上只是我正在做的一个极其简化的版本。我这样做只是为了了解如何将数据拆分到单独的表中。这不是我通过 EAV 找到的理想解决方案,但它将是最快的工程设计,并且至少会看到每次添加新字段时不必更改数据库的好处。感谢您的帮助。 赞成这个“基于集合”的答案。其他答案,请停止提供 RBA 解决方案。【参考方案2】:

可以通过输出子句获取新插入的id

declare @OutputTbl table (ID INT)
declare @NewUserID int

insert into User_NEW (FullName, Email)
output inserted.Id into @OutputTbl(ID)
VALUES ('john doe', 'john@somewhere.com')

select @NewUserID = ID from @OutputTbl

现在您可以使用 @NewUserID 作为所有插入客户表的键

小心使用 SCOPE_IDENTITY() 它可以为您提供另一个您期望的 ID,例如,当有一个触发器插入另一个表时,就会发生这种情况。 此外,通过使用输出子句,您可以捕获更多字段,而不仅仅是 Id

另请参阅@@IDENTITY, SCOPE_IDENTITY(), OUTPUT and other methods of retrieving last identity

【讨论】:

SCOPE_IDENTITY 有哪些错误?我认为这些功能中没有任何已知的错误。但很多时候人们不理解@@IDENTITY 并错误地使用它。 @SeanLange 我链接的帖子中的答案提到了错误,这就是我写这个的原因。我同意当您获得由另一个表中的触发器插入的行的 ID 时,这不是一个错误,而是这个函数的工作原理。我会改变我的答案 有趣。提到的错误是一个不再存在的 MS Connect 链接。将不得不对此进行一些调查。直到今天才听说过。 :) 似乎是并行性发挥作用的时候。无论如何,输出语句将是要走的路。 :) 很高兴你发布了这个。我过去多次使用 SCOPE_IDENTITY 没有问题,但我没有意识到潜在的问题。这是我第一次看到 OUTPUT 子句。 @Hopper 在这里学习,我每周都在这里学习新东西。很高兴能帮到你【参考方案3】:

如果您正在寻找一个允许您从新插入的行中获取密钥以便您可以在后续代码中使用它的过程,那么这就是您正在寻找的 - SCOPE_IDENTITY()。

https://docs.microsoft.com/en-us/sql/t-sql/functions/scope-identity-transact-sql?view=sql-server-2017

Declare PassedKey Integer; 

INSERT INTO User_New values (...) 
SET PassedKey = SCOPE_IDENTITY()

INSERT INTO User_Setting values (PassedKey, ...)

【讨论】:

【参考方案4】:
    DECLARE @FullName nvarchar(50), 
    @Email nvarchar(50),
    @Setting1 bit,
    @Setting1Value int,
    @Setting2 bit,
    @Setting2Value int,
    @Setting3 bit,
    @Setting3Value int 

    DECLARE @recentId INT

    DECLARE C CURSOR FOR 
        SELECT [FullName] , [Email] ,[Setting1], [Setting1Value], [Setting2], [Setting2Value], [Setting3], [Setting3Value]
        FROM @User_OLD

        OPEN C
    FETCH NEXT FROM C INTO @FullName, @Email ,@Setting1 ,@Setting1Value ,@Setting2 ,@Setting2Value ,@Setting3 ,@Setting3Value  

    WHILE @@FETCH_STATUS = 0 
    BEGIN

        INSERT INTO @User_NEW(FullName, Email)
        VALUES(@FullName, @Email);

        SELECT @recentId = SCOPE_IDENTITY()

        INSERT INTO @UserSetting(UserId, SettingName, SettingValue, CreatedOn)
        VALUES(@recentId, @Setting1 , @Setting1Value , GETDATE())

        INSERT INTO @UserSetting(UserId, SettingName, SettingValue, CreatedOn)
        VALUES(@recentId, @Setting2 , @Setting2Value , GETDATE())

        INSERT INTO @UserSetting(UserId, SettingName, SettingValue, CreatedOn)
        VALUES(@recentId, @Setting3 , @Setting3Value , GETDATE())

        FETCH NEXT FROM C INTO @FullName, @Email ,@Setting1 ,@Setting1Value ,@Setting2 ,@Setting2Value ,@Setting3 ,@Setting3Value 

    END

    CLOSE C
    DEALLOCATE C    

【讨论】:

以上是关于SQL循环表将记录插入新表,然后获取新ID并插入其他表的主要内容,如果未能解决你的问题,请参考以下文章

sql语句将查询结果作为新表插入

数以千万计的插入索引表性能/策略(Sql Server >= 2005)

如何使用openxml获取新插入行的ID?

如何用SQL语句删除两个表中相同的记录?

(Sqlserver)将在查询结果插入新表中的时候添加一个自动增长列么 SQL语句怎么写

在SQL中怎么删除两个表中相同的数据