No way to use TSQL Output with normal foreign key

2019-06-20 18:30发布

问题:

The following snippet fails with error:

The target table 'dbo.forn' of the OUTPUT INTO clause cannot be on either side of a (primary key, foreign key) relationship. Found reference constraint 'FK_forn_prim'."

I can only use output by disabling foreign key constraints? How can this be done?

IF OBJECT_ID ('dbo.forn') IS NOT NULL
begin
    alter table dbo.forn drop constraint FK_forn_prim
    DROP TABLE dbo.forn;
end
IF OBJECT_ID ('dbo.prim') IS NOT NULL
    DROP TABLE dbo.prim;
go

CREATE TABLE dbo.prim (c1 int PRIMARY KEY);
CREATE TABLE dbo.forn (c1 int CONSTRAINT FK_forn_prim FOREIGN KEY (c1) REFERENCES dbo.prim(c1));
go

INSERT INTO dbo.prim
    OUTPUT inserted.c1 INTO dbo.forn
SELECT 1;

回答1:

According to technet

There is no guarantee that the order in which the changes are applied to the table and the order in which the rows are inserted into the output table or table variable will correspond.

Based on this there are a number of restrictions, among which is the one you ran into above, but if all you want is to record the inserted value in a second table I don't see why you would need the foreign key relationship you define above.

If you do need the foreign key relationship you could accomplish the same thing by using a trigger like this.

    IF OBJECT_ID ('dbo.forn') IS NOT NULL
begin
    alter table dbo.forn drop constraint FK_forn_prim
    DROP TABLE dbo.forn;
end
IF OBJECT_ID ('dbo.prim') IS NOT NULL
    DROP TABLE dbo.prim;
go

CREATE TABLE dbo.prim (c1 int PRIMARY KEY);
CREATE TABLE dbo.forn (c1 int CONSTRAINT FK_forn_prim FOREIGN KEY (c1) REFERENCES dbo.prim(c1));
go

CREATE TRIGGER InsertRecord
   ON  dbo.Prim
   AFTER Insert
AS 
BEGIN
    SET NOCOUNT ON;
    Insert into dbo.forn Select * from inserted;
END
GO

INSERT INTO dbo.prim SELECT 1;


回答2:

Normally you output to a table variable or temp table, then use that to insert to the final table.



回答3:

You're correct, output statements won't work on tables under foreign key constraints.

Another option would be to temporarily disable the constraints while you insert the data. Though this shouldn't be the norm, it works well for one time loads of data.

ALTER TABLE dbo.forn
NOCHECK CONSTRAINT FK_forn_prim
GO

INSERT INTO dbo.prim
    OUTPUT inserted.c1 INTO dbo.forn
SELECT 1;

ALTER TABLE dbo.forn
CHECK CONSTRAINT FK_forn_prim
GO


回答4:

The documentation does indicate that the output table may not participate in foreign key constraints (or check constraints, nor may it have enabled triggers defined on it--but those cases aren't considered here). The Msg 332 error is the manifestation of that.

(THIS IS NOT RECOMMENDED. See update below)

However, I have found that this limitation is subverted if the foreign key constraint is disabled and re-enabled with NOCHECK/CHECK even after the constraint is re-enabled. In other words: one disable-enable cycle is enough to make the FK constraint "invisible" to the output-into-foreign-key-constraint prohibition. Note the modification below:

IF OBJECT_ID ('dbo.forn') IS NOT NULL
begin
    alter table dbo.forn drop constraint FK_forn_prim
    DROP TABLE dbo.forn;
end
IF OBJECT_ID ('dbo.prim') IS NOT NULL
    DROP TABLE dbo.prim;
go

CREATE TABLE dbo.prim (c1 int PRIMARY KEY);
-- note change here:
CREATE TABLE dbo.forn (c1 int );
alter TABLE dbo.forn with nocheck add CONSTRAINT FK_forn_prim FOREIGN KEY (c1)     REFERENCES dbo.prim(c1);
alter TABLE dbo.forn check CONSTRAINT FK_forn_prim ;
-- end change
go

INSERT INTO dbo.prim
    OUTPUT inserted.c1 INTO dbo.forn
select 1;

The following fails (constraint violation) so you know the DRI is still working:

INSERT INTO dbo.prim
    OUTPUT inserted.c1 + 1 INTO dbo.forn
select 2;

Given the prohibition in the docs, I grant this seems dubious. I have a question pending on this point, though it hasn't been well-received.

UPDATE

The previously mentioned question was answered, and the new information invalidates this answer. Leaving this up as a warning to others, in case they stumble upon this seemingly fortuitous hole.

The upshot is: alter TABLE dbo.forn check CONSTRAINT FK_forn_prim ; reenables the constraint but leaves it in an "untrusted" state, so it cannot be fully utilized by the SQL engine for index optimization and such. It is not recommended. The correct way to reenable is

alter table dbo.forn with check check CONSTRAINT FK_forn_prim;


标签: tsql