Given this table:
CREATE TABLE dbo.Target (
TargetId int identity(1, 1) NOT NULL,
Color varchar(20) NOT NULL,
Action varchar(10) NOT NULL, -- of course this should be normalized
Code int NOT NULL,
CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);
In two slightly different scenarios I want to insert rows and return the values from the identity column.
Scenario 1
INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES
('Blue', 'New', 1234),
('Blue', 'Cancel', 4567),
('Red', 'New', 5678)
) t (Color, Action, Code)
;
Scenario 2
CREATE TABLE #Target (
Color varchar(20) NOT NULL,
Action varchar(10) NOT NULL,
Code int NOT NULL,
PRIMARY KEY CLUSTERED (Color, Action)
);
-- Bulk insert to the table the same three rows as above by any means
INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;
Question
Can I rely on the returned identity values from the dbo.Target
table insert to be returned in the order they existed in the 1) VALUES
clause and 2) #Target
table, so that I can correlate them by their position in the output rowset back to the original input?
For Reference
Here is some trimmed-down C# code that demonstrates what's happening in the application (scenario 1, soon to be converted to use SqlBulkCopy
):
public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
var targetList = targets.ToList();
const string insertSql = @"
INSERT dbo.Target (
CoreItemId,
TargetDateTimeUtc,
TargetTypeId,
)
OUTPUT
Inserted.TargetId
SELECT
input.CoreItemId,
input.TargetDateTimeUtc,
input.TargetTypeId,
FROM
(VALUES
{0}
) input (
CoreItemId,
TargetDateTimeUtc,
TargetTypeId
);";
var results = Connection.Query<DbTargetInsertResult>(
string.Format(
insertSql,
string.Join(
", ",
targetList
.Select(target => $@"({target.CoreItemId
}, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
}', {(byte) target.TargetType
})";
)
)
)
.ToList();
return targetList
.Zip( // The correlation that relies on the order of the two inputs being the same
results,
(inputTarget, insertResult) => new Target(
insertResult.TargetId, // with the new TargetId to replace null.
inputTarget.TargetDateTimeUtc,
inputTarget.CoreItemId,
inputTarget.TargetType
)
)
.ToList()
.AsReadOnly();
}
Best Answer
No, you can't rely on anything to be guaranteed without an actual documented guarantee. The documentation explicitly states there is no such guarantee.
This would rely on a lot of undocumented assumptions
An example of point two failing (assuming clustered PK of
(Color, Action)
) can be seen if you add 600 rows to theVALUES
clause. Then the plan has a sort operator before the insert so losing your original order in theVALUES
clause.There is a documented way of achieving your goal though and this is to add a numbering to the source and use
MERGE
instead ofINSERT
@a_horse_with_no_name
Yes you could. Ordering guarantees in SQL Server… states that
So you could use
This would guarantee that the identity values are assigned in order of
t.SourceId
but not that they are output in any particular order or that the identity column values assigned have no gaps (e.g. if a concurrent insert is attempted).