SQL 输出子句是不是可以返回未插入的列?

Posted

技术标签:

【中文标题】SQL 输出子句是不是可以返回未插入的列?【英文标题】:Is it possible to for SQL Output clause to return a column not being inserted?SQL 输出子句是否可以返回未插入的列? 【发布时间】:2012-06-08 13:23:46 【问题描述】:

我对我的数据库进行了一些修改,我需要将旧数据迁移到新表中。为此,我需要填写一个表格 (ReportOptions),从原始表格 (Practice) 中获取数据,并填写第二个中间表格 (PracticeReportOption)。

ReportOption (ReportOptionId int PK, field1, field2...)
Practice (PracticeId int PK, field1, field2...)
PracticeReportOption (PracticeReportOptionId int PK, PracticeId int FK, ReportOptionId int FK, field1, field2...)

我进行了查询以获取从 Practice 移动到 ReportOptions 所需的所有数据,但我无法填充中间表

--Auxiliary tables
DECLARE @ReportOption TABLE (PracticeId int /*This field is not on the actual ReportOption table*/, field1, field2...)
DECLARE @PracticeReportOption TABLE (PracticeId int, ReportOptionId int, field1, field2)

--First I get all the data I need to move
INSERT INTO @ReportOption
SELECT P.practiceId, field1, field2...
  FROM Practice P

--I insert it into the new table, but somehow I need to have the repation PracticeId / ReportOptionId
INSERT INTO ReportOption (field1, field2...)
OUTPUT @ReportOption.PracticeId, --> this is the field I don't know how to get
       inserted.ReportOptionId
  INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT field1, field2
  FROM @ReportOption

--This would insert the relationship, If I knew how to get it!
INSERT INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT PracticeId, ReportOptionId
  FROM @ReportOption

如果我可以在 OUTPUT 子句中引用不在目标表上的字段,那就太好了(我想我不能,但我不确定)。关于如何满足我的需求的任何想法?

【问题讨论】:

您可以在OUTPUT 子句中返回插入行的表的任何列。因此,即使您没有在INSERT 语句中为给定列提供值,您仍然可以在OUTPUT 子句中指定该列。但是,您不能从其他表返回 SQL 变量或列。 @marc_s 感谢您的回复,但我在目标表中没有我需要的字段(我需要 PracticeId,它不在 ReportOption 上) 【参考方案1】:

您可以通过使用MERGE 而不是插入来做到这一点:

所以替换这个

INSERT INTO ReportOption (field1, field2...)
OUTPUT @ReportOption.PracticeId, --> this is the field I don't know how to get
       inserted.ReportOptionId
  INTO @PracticeReportOption (PracticeId, ReportOptionId)
SELECT field1, field2
  FROM @ReportOption

MERGE INTO ReportOption USING @ReportOption AS temp ON 1 = 0
WHEN NOT MATCHED THEN
    INSERT (field1, field2)
    VALUES (temp.Field1, temp.Field2)
    OUTPUT temp.PracticeId, inserted.ReportOptionId, inserted.Field1, inserted.Field2
    INTO @PracticeReportOption (PracticeId, ReportOptionId, Field1, Field2);

关键是在合并搜索条件中使用永远不会为真 (1 = 0) 的谓词,因此您将始终执行插入,但可以访问源表和目标表中的字段。


这是我用来测试它的整个代码:

CREATE TABLE ReportOption (ReportOptionID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
CREATE TABLE Practice (PracticeID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
CREATE TABLE PracticeReportOption (PracticeReportOptionID INT IDENTITY(1, 1), PracticeID INT, ReportOptionID INT, Field1 INT, Field2 INT)

INSERT INTO Practice VALUES (1, 1), (2, 2), (3, 3), (4, 4)


MERGE INTO ReportOption r USING Practice p ON 1 = 0
WHEN NOT MATCHED THEN
    INSERT (field1, field2)
    VALUES (p.Field1, p.Field2)
    OUTPUT p.PracticeId, inserted.ReportOptionId, inserted.Field1, inserted.Field2
    INTO PracticeReportOption (PracticeId, ReportOptionId, Field1, Field2);

SELECT  *
FROM    PracticeReportOption

DROP TABLE ReportOption
DROP TABLE Practice
DROP TABLE PracticeReportOption 

更多阅读,我所知道的关于这个主题的所有信息的来源是Here

【讨论】:

谢谢,这就是诀窍!我打算使用一个假的临时字段,但这要优雅得多。 太棒了!这个技巧是金子!添加到集合的第一行! 太棒了!我希望它不必使用偶尔出错的 MERGE 命令,但否则它非常优雅。 请注意。我使用了一个合并语句,该语句在过去一年中随着使用量的增长而增长。我们在保存期间开始出现超时,结果是因为合并语句总是锁定表,我们每 4 分钟有 35-160 秒的表锁定时间。我必须重建几个合并语句以使用插入/更新并将它们更新的行数限制为每次插入/更新 500 以避免表锁定。我估计这张非常重要的表每天被锁定近 2 1/2 小时,这导致了从缓慢保存到超时的所有事情。 另外,对于许多人来说,一个交易破坏者是 MERGE 中有很多未修复的错误,这些错误在奇怪的条件下会出现。例如。请参阅 Aaron Bertrand 的 this article。微软拒绝修复其中一些,我的秘密怀疑是 MS 弃用了他们的整个 MS Connect 服务,只是为了让我们忘记他们不想修复的 MERGE 语句中的所有错误。【参考方案2】:

也许使用 MS SQL Server 2005 或更低版本的人会发现此答案很有用。


MERGE 仅适用于 SQL Server 2008 或更高版本。 为了休息,我找到了另一种解决方法,它可以让您创建某种映射表。

以下是 SQL 2005 的分辨率:

DECLARE @ReportOption TABLE (ReportOptionID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
DECLARE @Practice TABLE(PracticeID INT IDENTITY(1, 1), Field1 INT, Field2 INT)
DECLARE @PracticeReportOption TABLE(PracticeReportOptionID INT IDENTITY(1, 1), PracticeID INT, ReportOptionID INT, Field1 INT, Field2 INT)

INSERT INTO @Practice (Field1, Field2) VALUES (1, 1)
INSERT INTO @Practice (Field1, Field2) VALUES (2, 2)
INSERT INTO @Practice (Field1, Field2) VALUES (3, 3)
INSERT INTO @Practice (Field1, Field2) VALUES (4, 4)

INSERT INTO @ReportOption (field1, field2)
    OUTPUT INSERTED.ReportOptionID, INSERTED.Field1, INSERTED.Field2 INTO @PracticeReportOption (ReportOptionID, Field1, Field2)
    SELECT Field1, Field2 FROM @Practice ORDER BY PracticeID ASC;


WITH CTE AS ( SELECT PracticeID, ROW_NUMBER() OVER ( ORDER BY PracticeID ASC ) AS ROW FROM @Practice )
UPDATE M SET M.PracticeID = S.PracticeID 
    FROM @PracticeReportOption AS M
    JOIN CTE AS S ON S.ROW = M.PracticeReportOptionID

    SELECT * FROM @PracticeReportOption

主要技巧是我们使用源表和目标表中的有序数据填充映射表两次。 更多详情:Merging Inserted Data Using OUTPUT in SQL Server 2005

【讨论】:

这不能解决我的问题。我需要Output 我的Insert 到输出Table,其中包括来自目标TableIdentity 值和来自源@987654329 的非Insert-ed 值(PK) @ (顺便说一句,所以我可以(在不同的批次中)使用该输出 Table 在源 Table 中填充 ColumnIdentity 值)。如果没有Merge,我想我必须:a)开始Transaction,b)获得下一个Identity,c)插入到临时Table,计算Identity,d)设置Identity_Insert, e) Insert 从 temp Table 到目标 Table,f) 清除 Identity_Insert

以上是关于SQL 输出子句是不是可以返回未插入的列?的主要内容,如果未能解决你的问题,请参考以下文章

如果 WHERE 子句包含未索引的列,是不是使用索引

具有 CSV 值的列上的 JPA 查询 IN 子句未返回确切结果

PL-SQL:ORA-00904 - 标识符无效 - Select and Pivot 子句中的列

两者都在模型子句中的列之间引用

SQL语法 之 操作语句

SQL 语句的解析过程