T-SQL Output Clause: How to access the old Identity ID - sql

I have a T-SQL statement that basically does an insert and OUTPUTs some of the inserted values to a table variable for later processing.
Is there a way for me to store the old Identity ID of the selected records into my table variable. If I use the code below, I get "The multi-part identifier "a.ID" could not be bound." error.
DECLARE #act_map_matrix table(new_act_id INT, old_ID int)
DECLARE #new_script_id int
SET #new_script_id = 1
INSERT INTO Act
(ScriptID, Number, SubNumber, SortOrder, Title, IsDeleted)
OUTPUT inserted.ID, a.ID INTO #act_map_matrix
SELECT
#new_scriptID, a.Number, a.SubNumber, a.SortOrder, a.Title, a.IsDeleted
FROM Act a WHERE a.ScriptID = 2
Thanks!

I was having your same problem and found a solution at http://sqlblog.com/blogs/adam_machanic/archive/2009/08/24/dr-output-or-how-i-learned-to-stop-worrying-and-love-the-merge.aspx
Basically it hacks the MERGE command to use that for insert so you can access a source field in the OUTPUT clause that wasn't inserted.
MERGE INTO people AS tgt
USING #data AS src ON
1=0 --Never match
WHEN NOT MATCHED THEN
INSERT
(
name,
current_salary
)
VALUES
(
src.name,
src.salary
)
OUTPUT
src.input_surrogate,
inserted.person_id
INTO #surrogate_map;

You'll have to join back #act_map_matrix onto Act to get the "old" value.
It's simply not available in the INSERT statement
edit: one hopes that #new_scriptID and "scriptid=2" could be the join column

Related

Insert data from Table A to Table B, then Update Table A with Table B ID

I currently have two tables like the following:
Table A
TableAId
TableAPrivateField
CommonField1
CommonField2
CommonField..
TableBGeneratedId
1
datadatadata
datadatadata2
datadatadata3
d...
NULL
2
datadatadata5
datadatadata6
datadatadata7
d...
NULL
...
Table B
TableBId
CommonField1
CommonField2
CommonField..
...
What i want to do is insert into TableB some record fetched from TableA, and then update the column [TableBGenerateId] of TableA with the corresponding new Id from the inserted record in TableB.
I tried with declaring a Table Value Parameter and then use it with the OUTPUT clause, but i can't find a way to relate back to the original TableAId of the row that acted as the source for the insert
something like that:
DECLARE #InsertedTableB TABLE (
TableBId INT PRIMARY KEY
);
INSERT INTO TableB
OUTPUT inserted.TableBId INTO #InsertedTableB
SELECT CommonField1, CommonField2,..
FROM TableA
WHERE TableAPrivateField = 'MyCondition';
WITH NumberedTableA AS(
SELECT TableAId, ROW_NUMBER() OVER(ORDER BY TableAId) AS RowNum
FROM TableA
WHERE TableAPrivateField = 'MyCondition'
),
NumberedInsert AS(
SELECT TableBId, ROW_NUMBER() OVER(ORDER BY TableBId) AS RowNum
FROM #InsertedTableB
)
UPDATE TableA
SET GeneratedTableBId = NumberedInsert.TableBId
FROM TableA
JOIN NumberedTableA ON Table.TableAId = NumberedTableA.TableAId
JOIN NumberedInsert ON NumberedTableA.RowNum = NumberedTable.RowNum
My problem is that even thought the query works i have no guaranties that the order of the fetched records will be the same, so i would risk linking back the wrong Ids. I tried to figure out some different solutions, but the closest one i found was to temporarily add a column to TableB containing TableAId and then perform the update, but i disliked it because this operation needs to be executed frequently and it would be too performance demanding. Adding the column permanently also isn't an acceptable solution sadly.
Anyone has any suggestion on how solve this?
If you use MERGE rather than INSERT (but still only ever insert with the MERGE by using a condition that will never be met e.g. 1=0), you can capture both the ID from TableA, and the new ID from tableB in the OUTPUT clause and insert this to your table variable. Then use this table variable to update tableA:
DECLARE #InsertedTableB TABLE (TableBId INT PRIMARY KEY, TableAId INT NOT NULL);
MERGE INTO dbo.TableB AS b
USING
( SELECT TableAId, CommonField1, CommonField2
FROM dbo.TableA
WHERE TableAPrivateField = 'MyCondition'
) AS a
ON 1 = 0 -- <<<< Always false so will never match and only ever insert
WHEN NOT MATCHED THEN
INSERT (CommonField1, CommonField2)
VALUES (a.CommonField1, a.CommonField2)
OUTPUT inserted.TableBId, a.TableAId INTO #InsertedTableB (TableBId, TableAId);
UPDATE a
SET GeneratedTableBId = b.TableBId
FROM dbo.TableA AS a
INNER JOIN #InsertedTableB AS b
ON b.TableAId = a.TableAId;
Example on db<>fiddle
Whenever I post any answer that in anyway condones the use of MERGE it is met with at least one comment highlighting all of the bugs with it, so to pre-empt that: There are a lot of issues with using MERGE in SQL Server - I do not believe that any of those risks will apply in this scenario if you are (a) forcing an insert and (b) using a table as the target. So while I will always avoid MERGE where I can by using multiple statements, this is one scenario where I don't avoid it because I don't think there is a cleaner solution available without using MERGE. It is anecdotal, but I have used this method for years and have never once encountered an issue.

How can I auto increment the primary key in SQL Server 2016 merge insert without sequences?

I am writing a query to import data from one table to a new table. I need to insert records that do not exist in the new table, and update records that do exist. I am trying to use a MERGE "upsert" method.
I have some unique problems due to the client's database and application structure. The table I am inserting into has a Unique ID field that increments by 1 for each new row, but the table does not do the auto incrementating; the insert statement needs to pull the highest ID in the target table and add 1 for the new record.
From my research, I can't figure out how to do that with MERGE. I do not database permissions to create a sequence. I have tried a lot of things, but currently my query looks like:
MERGE
dbo.targetTable as target
USING
dbo.sourceTable AS source
ON
target.account_no = source.account_ID
WHEN NOT MATCHED THEN
INSERT (
ID,
FIELD1,
FIELD2,
FIELD3
) VALUES (
(SELECT MAX(ID) + 1 FROM dbo.targetTable),
'field1',
'field2',
'field3'
)
The problem I am then running into with this code is that it appears to only run the select statement for the new ID once. That is, if the highest ID in the target table was 10, it would insert every new record with ID 11. That won't work as I'm getting a
Violation of PRIMARY KEY constraint. Cannot insert duplicate key in object error. I've been doing a ton of googling and trying different things but haven't been able to figure this one out. Any help is appreciated, thank you.
EDIT: For clarification, the unique ID column does not auto-populate. If I do not insert a value for the ID column, I get
Cannot insert the value NULL into column 'ID', table 'dbo.targetTable'; column does not allow nulls. UPDATE fails.
And again, as I mentioned originally I do not have permissions to create sequences. It just throws an error and says I do not have permission to do that.
I agree that changing the ID column to auto-increment automatically would be perfect, but I do not have the capability to modify the table like that either.
If you don't need the IDs to be consecutive, you can add the last available ID to a ROW_NUMBER() to generate new, non-repeated IDs.
BEGIN TRANSACTION
DECLARE #NextAvailableID INT = (SELECT ISNULL(MAX(ID), 0) FROM dbo.targetTable WITH (TABLOCKX))
;WITH SourceWithNewIDs AS
(
SELECT
S.*,
NewID = #NextAvailableID + ROW_NUMBER() OVER (ORDER BY S.account_ID)
FROM
dbo.sourceTable AS S
)
MERGE
dbo.targetTable as target
USING
SourceWithNewIDs AS source
ON
target.account_no = source.account_ID
WHEN NOT MATCHED THEN
INSERT (
ID,
FIELD1,
FIELD2,
FIELD3
) VALUES (
NewID,
'field1',
'field2',
'field3'
)
COMMIT
Keep in mind that this example is missing the proper error handling with rollback and the lock used to retrieve the max ID will block all other operations until commited or rollbacked.
If you need the new rows to have consecutive IDs then you can use this same approach with a regular INSERT (with WHERE NOT EXISTS...) instead of a MERGE (will have to write the UPDATE separately).
This is just a different way without using a Merge. Permissions aren't required for temp tables, so I would use one to hold the account numbers that need to be inserted, with an identity field to help with traversal. A while loop can traverse the identity, inserting the values with respect to the source table's account_no- into the target table. Since the insert is done in a loop, the MAX function should grab the target table's MAX(account_no) correctly on each loop.
DECLARE #tempTable TABLE (pkindex int IDENTITY(1,1) PRIMARY KEY, account_no int)
DECLARE #current int = 1
,#endcount int = 0
--account_no's that should be inserted
INSERT INTO #tempTable(account_no)
SELECT account_no
FROM sourceTable
WHERE account_no NOT IN (SELECT account_no FROM targetTable)
SET #endcount = (SELECT COUNT(*) FROM #tempTable)
--looping condition, should select the MAX(ID) with each subsequent loop
WHILE (#endcount > 0) AND (#current <= #endcount)
BEGIN
INSERT INTO dbo.targetTable(ID, FIELD1, FIELD2, FIELD3)
SELECT (SELECT MAX(T2.ID) + 1 FROM dbo.targetTable T2) AS MAXID
,S.field1
,S.field2
,S.field3
FROM #tempTable T INNER JOIN sourceTable S ON T.account_no = S.account_no
WHERE T.pkindex = #current --traversing temp table by its identity
SET #current += 1
END

SQL column duplicate value count

I have a Student table. Currently it has many columns like ID, StudentName, FatherName, NIC, MotherName, No_Of_Childrens, Occupation etc.
I want to check the NIC field on insert time. If it is a duplicate, then count the duplicated NIC and and add the count number in No_of_Children column.
What is the best way to do that in SQL Server?
It sounds like you want an UPSERT. The most concise way to accomplish that in SQL (that I know) is through a MERGE operation.
declare #students table
(
NIC int
,No_Of_Childrens int
);
--set up some test data to get us started
insert into #students
select 12345,1
union select 12346,2
union select 12347,2;
--show before
select * from #students;
declare #incomingrow table(NIC int,childcount int);
insert into #incomingrow values (12345,2);
MERGE
--the table we want to change
#students AS target
USING
--the incoming data
#incomingrow AS source
ON
--if these fields match, then the "when matched" section happens.
--else the "when not matched".
target.NIC = source.NIC
WHEN MATCHED THEN
--this statement will happen when you find a match.
--in our case, we increment the child count.
UPDATE SET NO_OF_CHILDRENS = no_of_childrens + source.childcount
WHEN NOT MATCHED THEN
--this statement will happen when you do *not* find a match.
--in our case, we insert a new row with a child count of 0.
INSERT (nic,no_of_childrens) values(source.nic,0);
--show the results *after* the merge
select * from #students;

Storing Multiple Insert IDs

After scouring the site (and others...), I cannot find an example of an insert command allowing me to store the "RETURNING" values to a table, CTE, etc. This is what I'd like to do:
WITH insert_rows AS (
INSERT INTO employers (column1, column2, insert_date)
SELECT distinct tc.column1, 'any text', now()
FROM _tmp_employer_updates tc
LEFT JOIN employers e ON e.column1 = tc.column1
WHERE e.column1 IS NULL -- Only insert non-existing employer names
RETURNING employer.row_uuid, employer.column1, employer.column2;
)
SELECT * FROM insert_rows; -- table of returning values
Is there anyway to get an insert command to store it's "returning" values to a table using a CTE? When I try the example above I get:
ERROR: syntax error at or near "INSERT"
LINE 1: ... _tmp_inserted_employers AS WITH insert_rows AS ( INSERT INT...
Thanks in advance...
Remove ; after returning ..., remove alias employer before columns in returning (or change it to employers). Otherwise your query looks good.
Here's an example on sql fiddle.

Looking for SQL constraint: SELECT COUNT(*) from tBoss < 2

I'd like to limit the entries in a table. Let's say in table tBoss. Is there a SQL constraint that checks how many tuples are currently in the table? Like
SELECT COUNT(*) from tBoss < 2
Firebird says:
Invalid token.
Dynamic SQL Error.
SQL error code = -104.
Token unknown - line 3, column 8.
SELECT.
You could do this with a check constraint and a scalar function. Here's how I built a sample.
First, create a table:
CREATE TABLE MyTable
(
MyTableId int not null identity(1,1)
,MyName varchar(100) not null
)
Then create a function for that table. (You could maybe add the row count limit as a parameters if you want more flexibility.)
CREATE FUNCTION dbo.MyTableRowCount()
RETURNS int
AS
BEGIN
DECLARE #HowMany int
SELECT #HowMany = count(*)
from MyTable
RETURN #HowMany
END
Now add a check constraint using this function to the table
ALTER TABLE MyTable
add constraint CK_MyTable__TwoRowsMax
check (dbo.MyTableRowCount() < 3)
And test it:
INSERT MyTable (MyName) values ('Row one')
INSERT MyTable (MyName) values ('Row two')
INSERT MyTable (MyName) values ('Row three')
INSERT MyTable (MyName) values ('Row four')
A disadvantage is that every time you insert to the table, you have to run the function and perform a table scan... but so what, the table (with clustered index) occupies two pages max. The real disadvantage is that it looks kind of goofy... but everything looks goofy when you don't understand why it has to be that way.
(The trigger solution would work, but I like to avoid triggers whenever possible.)
Does your database have triggers? If so, Add a trigger that rolls back any insert that would add more than 2 rows...
Create Trigger MyTrigName
For Insert On tBoss
As
If (Select Count(*) From tBoss) > 2
RollBack Transaction
but to answer your question directly, the predicate you want is to just put the select subquery inside parentheses. like this ...
[First part of sql statement ]
Where (SELECT COUNT(*) from tBoss) < 2
To find multiples in a database your best bet is a sub-query for example: (Note I am assuming you are looking to find duplicated rows of some sort)
SELECT id FROM tBoss WHERE id IN ( SELECT id FROM tBoss GROUP BY id HAVING count(*) > 1 )
where id is the possibly duplicated column
SELECT COUNT(*) FROM tBoss WHERE someField < 2 GROUP BY someUniqueField