【问题标题】:Insert into multiple tables with shared keys使用共享键插入多个表
【发布时间】:2025-09-30 16:05:02
【问题描述】:

我需要将多个用户和电话号码插入电话簿。我在 .csv 文件中有用户及其电话号码,我需要将此数据添加到共享 FK uniqeidentifier 的多个表中。标识符是在用户表中创建用户时生成的。

  1. 我怎样才能做到这一点?
  2. 我怎样才能在最 有效的方法可行吗?

我尝试过的以及我想做的(但这不起作用):

CREATE TABLE Users(
UserID uniqeidentifier NOT NULL  
   DEFAULT newid(),
Firstname nvarchar (25),
Lastname nvarchar(25)
)
GO

CREATE TABLE Phonebook(
Number nvarchar (25),
UserID uniqeidentifier
)
GO

CREATE TABLE #TempImportedUsers(
firstname nvarchar(25),
lastname nvarchar(25),
phonenumber nvarchar(25)
)
GO

BULK INSERT #TempImportedUsers
    FROM 'D:\import.csv'
    WITH
    (
    FIRSTROW = 2,
    FIELDTERMINATOR = ',',
    ROWTERMINATOR = '\n',
    Tablock
    )
    GO

INSERT INTO Users (Firstname, Lastname)
OUTPUT #TempImportedUsers.phonenumber,Inserted.UserID INTO Phonebook(Number, UserID)
SELECT
    firstname,
    lastname
FROM
    #TempImportedUsers

D:\import.csv 的内容:

Firstname,Lastname,Number
Foo,Bar,3311664499
Baz,Qux,8844331166

显然上述方法不起作用,因为OUTPUT #TempImportedUsers.phonenumber,Inserted.UserID INTO Phonebook(Number, UserID) 无效。

我怎样才能实现这种导入?我必须使用游标吗?

【问题讨论】:

  • 这是一次性导入还是需要重复执行的操作。无论哪种方式,如果您在临时表中有一个列作为主键,这会容易得多。
  • 如果您认为这会有所帮助,我绝对可以在临时表中添加一列。不过,我看不出这有什么帮助,因为 *.csv 中的导入数据不包含任何键。如果我可以根据 OUTPUT 子句执行“UPDATE #temptable SET id”之类的操作,我会明白这一点,但我认为这是不可能的。如果您有任何想法,请给出一个代码示例。
  • 或者您是否建议我为临时表中的每个用户创建自己的 uniqeidentifier,然后使用该标识符将用户和电话号码添加到他们的表中?我想这可能会奏效,除非在#temptable 中生成我自己的标识符有任何潜在的不利影响?
  • 我不知道我会使用唯一标识符。这似乎有点矫枉过正,除非您要将其持久化到您的表格中。我不能真正“给出代码”,因为这里没有太多定义。没有表格、样本数据等...
  • @SeanLange 我的想法是将其保留到实际表中,是的。我已经编辑了我的问题以包含示例数据,以及从那里开始的表格。如果您需要任何进一步的信息来提出解决方案,请告诉我。

标签: sql sql-server tsql import insert


【解决方案1】:

您可以通过将 UserID 生成放入您的#temp 表来绕过系统来完成此操作。下面是一个导入的例子,

最简单的方法是使用OPENROWSET(bulk, ...) 而不是批量插入,并使用临时表。对于这个例子,这里是临时表:

CREATE TABLE #TempImportedUsers(
    UserId uniqueIdentifier DEFAULT NEWID(),
    firstname nvarchar(25),
    lastname nvarchar(25),
    phonenumber nvarchar(25)
);

这里是相关的格式文件(XML格式是最简单的方式):

<?xml version="1.0"?>
<BCPFORMAT xmlns="http://schemas.microsoft.com/sqlserver/2004/bulkload/format" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <RECORD>
  <FIELD ID="2" xsi:type="CharTerm" TERMINATOR="," MAX_LENGTH="50" COLLATION="SQL_Latin1_General_CP1_CI_AS"/>
  <FIELD ID="3" xsi:type="CharTerm" TERMINATOR="," MAX_LENGTH="50" COLLATION="SQL_Latin1_General_CP1_CI_AS"/>
  <FIELD ID="4" xsi:type="CharTerm" TERMINATOR="\n" MAX_LENGTH="50" COLLATION="SQL_Latin1_General_CP1_CI_AS"/>
 </RECORD>
 <ROW>
  <COLUMN SOURCE="2" NAME="firstname" xsi:type="SQLNVARCHAR"/>
  <COLUMN SOURCE="3" NAME="lastname" xsi:type="SQLNVARCHAR"/>
  <COLUMN SOURCE="4" NAME="phonenumber" xsi:type="SQLNVARCHAR"/>
 </ROW>
</BCPFORMAT>

这个 XML 格式文件有一个技巧——它忽略了表的第 1 列。这会导致自动生成 UUID。

这是我使用的示例文件:

First,Last,Phone
Bob,Smith,333-444-6666
Bill,Smith,222-333-5555
Alice,Restaurant,91-03045-2222

这是加载 #temp 表的 INSERT 查询:

INSERT INTO #TempImportedUsers (firstname, lastname, phonenumber)
SELECT *
FROM OPENROWSET(bulk 'D:\import.csv',
formatfile='D:\formatfile.xml', FIRSTROW=2) as t;

SELECT * FROM #TempImportedUsers 查询的结果是:

|UserId                              |firstname|lastname  |phonenumber  |
|------------------------------------|---------|----------|-------------|
|9514B27F-1C6B-4B18-BE63-4E7744B9484B|Bob      |Smith     |333-444-6666 |
|108AA33C-829B-4E7E-BE62-C88F1F77853A|Bill     |Smith     |222-333-5555 |
|9C6AD6FD-6F26-471E-A9B8-CE5F8F4B1266|Alice    |Restaurant|91-03045-2222|

希望这会有所帮助。

【讨论】:

  • 哈哈,你以几乎相同的解决方案击败了我 23 秒!但是,在我最终使用的内容中,我只是为 #temptable 中的 UserID 提供了一个 NULL 值,并让 DEFAULT newid() 生成一个新 ID。仍然使用批量插入,没有额外的 xml。这两种不同的方法有什么优缺点吗?
  • 唯一显着的区别是使用XML版本时不需要修改CSV。
【解决方案2】:

当 cmets 提出建议时,我突然意识到,一个简单的解决方法是生成我自己的 Guid/uniqeidentifier,因为无论如何这就是用户和电话簿表中使用的内容。

以防万一会有任何重复的 guid,我将其全部包装在 BEGIN TRANSACTION 语句中以处理整个导入的回滚。 最后选择导入的用户并删除#temptable。

CREATE TABLE #TempImportedUsers(
firstname nvarchar(25),
lastname nvarchar(25),
phonenumber nvarchar(25),
guid uniqueidentifier DEFAULT newid()
)
GO

BULK INSERT #TempImportedUsers
    FROM 'D:\import.csv'
    WITH
    (
    FIRSTROW = 2,
    FIELDTERMINATOR = ',',
    ROWTERMINATOR = '\n',
    Tablock
    )
    GO

BEGIN TRANSACTION [Tran1]
    BEGIN TRY
INSERT INTO Users (Firstname, Lastname, UserID)
SELECT
    firstname,
    lastname,
    guid
FROM
    #TempImportedUsers

INSERT INTO Phonebook(Number, UserID)
SELECT
    number,
    guid
FROM
    #TempImportedUsers
COMMIT TRANSACTION [Tran1]
    END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION [Tran1]
END CATCH

SELECT * FROM #TempImportedUsers
DROP TABLE #TempImportedUsers

import.csv 已更新,为新的 #temptable 列提供 NULL 值:

Firstname,Lastname,Number,Guid
Foo,Bar,3311664499,
Baz,Qux,8844331166,

在我最初的问题中,我问了 2 个问题;如何实现这一点以及如何以最有效的方式做到这一点。 这解决了如何实现它,但我不知道是否有更有效的方法(资源效率或书面行数) - 所以我将问题暂时搁置。

【讨论】: