Triggers after insert - sql-server

I have a table that has the following columns, and I cannot modify the schema (i.e. I can't modify the table or add an identity field).
What I want is to create a trigger to update one column to treat it as an identity field.
accountno firstname lastname keyField
jku45555 John Doe 123
Now what I want is when I insert the next record, I grab the KeyFieldId of the previous record and update the newly inserted record to be 124 (keep in mind this a Varchar field).
I need the best possible way of doing this, and like I said modifying the table is not an option. Thanks!

You want to do something like this... For a table named "Foo", with two columns, First Name and KeyFieldId (both varchar), this trigger will do what you want:
-------------------------------------------------------------------------
-- These lines will create a test table and test data
--DEBUG: CREATE TABLE Foo (FirstName varchar(20), KeyFieldId varchar(10))
--DEBUG: INSERT INTO Foo VALUES ('MyName', '145')
--
CREATE TRIGGER test_Trigger ON Foo
INSTEAD OF INSERT
AS
BEGIN
DECLARE #maxKeyFieldId int;
SELECT #maxKeyFieldId = MAX(CAST(KeyFieldId AS int)) FROM Foo;
WITH RowsToInsert AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY (CAST(KeyFieldId AS int))) AS RowNum
FROM inserted
) INSERT INTO Foo (FirstName, KeyFieldId)
SELECT FirstName, #maxKeyFieldId + RowNum
FROM RowsToInsert;
END
Things to note here:
Create an INSTEAD OF INSERT trigger
Find the "max" value of the INTEGER value of your KeyFieldID
Create a CTE that selects everything from the 'inserted' collection
Add a column to the CTE for a Row Number
Do the actual INSERT by adding row number to the max KeyFieldID

Related

Export data from Microsoft SQL Server using a query to target data

I know how to generate scripts to script insert lines allowing me to backup some data. I was wondering though if it was possible to write a query (using WHERE clause as an example) to target a very small subset of data in a very large table?
In the end I want to generate a script that has a bunch of insert lines and will allow for inserting primary key values (where it normally would not let you).
SSMS will not let you to have the INSERT queries for specific rows in a table. You can do this by using GenerateInsert stored procedure. For example :
EXECUTE dbo.GenerateInsert #ObjectName = N'YourTableName'
,#SearchCondition='[ColumnName]=ColumnValue';
will give you similar result for the filtered rows specified in the #SearchCondition
Let's say your table name is Table1 which has columns Salary & Name and you want the insert queries for those who have salary greater than 1000 whose name starts with Mr., then you can use this :
EXECUTE dbo.GenerateInsert #ObjectName = N'Table1'
,#SearchCondition='[Salary]>1000 AND [Name] LIKE ''Mr.%'''
,#PopulateIdentityColumn=1;
If I read your requirement correctly, what you actually want to do is simply make a copy of some data in your table. I typically do this by using a SELECT INTO. This will also generate the target table for you.
CREATE TABLE myTable (Column1 int, column2 NVARCHAR(50))
;
INSERT INTO myTable VALUES (1, 'abc'), (2, 'bcd'), (3, 'cde'), (4, 'def')
;
SELECT * FROM myTable
;
SELECT
*
INTO myTable2
FROM myTable WHERE Column1 > 2
;
SELECT * FROM myTable;
SELECT * FROM myTable2;
DROP TABLE myTable;
DROP TABLE myTable2;
myTable will contain the following:
Column1 column2
1 abc
2 bcd
3 cde
4 def
myTable2 will only have the last 2 rows:
Column1 column2
3 cde
4 def
Edit: Just saw the bit about the Primary Key values. Does this mean you want to insert the data into an existing table, rather than just creating a backup set? If so, you can issue SET IDENTITY_INSERT myTable2 ON to allow for this.
However, be aware that might cause issues in case the id values you are trying to insert already exist.

SQL trigger with IDENTITY_INSERT

I have two tables: Table1 is all the companies, Table2 is companies whose name start with A.
Table1 company (companyId int, companyName varchar(50), companySize int)
Table2 companyStartWithA (companyId int, companyName varchar(50), companySize int)
What I want to do is to create a trigger so that when I insert/update/delete something in Table1, it will automatically do the same in Table2
My code:
CREATE TRIGGER A_TRG_InsertSyncEmp
ON company
AFTER INSERT
AS
BEGIN
INSERT INTO companyStartWithA
SELECT *
FROM INSERTED
WHERE inserted.companyName LIKE 'A%'
END
And I get an error:
An explicit value for the identity column in table 'companyStartWithA' can only be specified when a column list is used and IDENTITY_INSERT is ON.
What can I do?
Thanks
The problem is the fact that you're not explicitly specifying the column in the INSERT statement, and using a SELECT * to fill the data. Both are big no-no's - you should always explicitly specify the column that you want to insert into, and you should always explicitly specify the columns that you want to select. Doing so will fix this problem:
CREATE TRIGGER A_TRG_InsertSyncEmp
ON company
AFTER INSERT
AS
BEGIN
INSERT INTO companyStartWithA (companyName, companySize)
SELECT companyName, companySize
FROM INSERTED
WHERE inserted.companyName LIKE 'A%'
END
But as Sean Lange absolutely correctly commented - this should really be just a view rather than a separate table.....
CREATE VIEW dbo.CompanyStartsWithA
AS
SELECT companyId, companyName, companySize
FROM dbo.Company
WHERE Name LIKE 'A%'
and then you don't need any messy triggers or anything - just insert into dbo.Company and all companies with a name that starts with an A will be visible in this view....

SQL Server query - how to insert selected values into another table

Here's what I am trying to do.
This is the select statement
select Id
from tblUsersPokemons
where UserId = 1 and PokemonPlace = 'bag'
Now I want to insert those returned Ids into another table like this:
foreach all returned Ids
insert into tblNpcBattleUsersPokemons values (Id, 1)
How can I do that ?
Like this:
insert into tblNpcBattleUsersPokemons
select Id, 1 from tblUsersPokemons where UserId=1 and PokemonPlace='bag'
This can be done in a single sql call
insert into tblNpcBattleUsersPokemons (id, [whatever the name of the other column is])
select Id, 1 from tblUsersPokemons where UserId=1 and PokemonPlace='bag'
I've used the longhand here because it does not make any assumption about the ordering of columns in the destination table, which can change over time and invalidate your insert statement.
You can insert the set retrieved by a SELECT into another existing table using the INSERT...SELECT syntax.
For example:
INSERT INTO tblNpcBattleUsersPokemons (Id, Val) -- not sure what the second column name was
SELECT Id FROM tblUsersPokemons WHERE UserID = 1 and PokemonPlace='bag';

Subquery returned more than 1 value. This is not permitted

ALTER TRIGGER t1
ON dbo.Customers
FOR INSERT
AS
BEGIN TRANSACTION
/* variables */
DECLARE
#maxid bigint
SELECT #customerid = id FROM inserted
SET IDENTITY_INSERT dbo.new_table ON
DECLARE
#maxid bigint
SELECT #maxid = MAX(ID) FROM new_table
INSERT INTO new_table (ID, ParentID, Foo, Bar, Buzz)
SELECT ID+#maxid, ParentID+#maxid, Foo, Bar, Buzz FROM initial_table
SET IDENTITY_INSERT dbo.new_tableOFF
/* execute */
COMMIT TRANSACTION
GO
fails with:
SQL Server Subquery returned more than 1 value. This is not permitted
when the subquery follows =, !=, <, <= , >, >= or when the subquery is
used as an expression
How to fix it?
What I am trying to do is
insert id and parentid, each INCREASED by #maxid
from initial_table
into new_table
thnx
new_table
id (bigint)
parentid (bigint - linked to id)
foo | bar | buzz (others are nvarchar, not really important)
initial table
id (bigint)
parentid (bigint - linked to id)
foo | bar | buzz (others are nvarchar, not really important)
You are battling against a few errors I suspect.
1.
You are inserting values that violate a unique constraint in new_table.
Avoid the existence error by joining against the table you are inserting into. Adjust the join condition to match your table's constraint:
insert into new_table (ID, ParentID, Foo, Bar, Buzz)
select ID+#maxid, ParentID+#maxid, Foo, Bar, Buzz
from initial_table i
left
join new_table N on
i.ID+#maxid = n.ID or
i.ParentID+#maxid = n.ParentId
where n.ID is null --make sure its not already there
2.
Somewhere, a subquery has returned multiple rows where you expect one.
The subquery error is either in the code that inserts into dbo.Customer (triggering t1), or perhaps in a trigger defined on new_table. I do not see anything in the posted code that would throw the subquery exception.
Triggers (aka, landmines) inserting into tables that have triggers defined on them is a recipe for pain. If possible, try to refactor some of this logic out of triggers and into code you can logically follow.
First you have to assume there will be more than one record in inserted or deleted. You should not ever set a value in inserted or deleted table to a scalar varaible in a SQL server trigger. It will cause a problem if the insert includes more than one record and sooner or later it will.
Next you should not ever consider setting identity insert on in a trigger. What were you thinking? If you have an identity field then use that, don't then try to manually create a value.
Next the subquery issue is associated apparently with the other trigger where you are also assuming only one record at a time would be processed. I would suspect that you will need to examine every trigger in your database and fix this basic problem.
Now when you run this part of the code:
INSERT INTO new_table (ID, ParentID, Foo, Bar, Buzz)
SELECT ID+#maxid, ParentID+#maxid, Foo, Bar, Buzz FROM initial_table
You are trying to insert all records in the table not just the ones in inserted. So since your trigger on the other table is incorreclty written, you are hitting an error which is actually hiding the error you will get when you try to insert 2000 records with the same PK into the new table or worse if you don't have a PK, it will happily insert them all every time you insert one record.
You have a trigger containing the statement:
SELECT #customerid = id FROM inserted
The inserted table contains a row for each row that was inserted (or updated for UPDATE triggers). A statement executed that inserted more than one row, the trigger fired, and your assumption was exposed.
Recode the trigger to operate on rowsets, not a single row.
while using a sub query in d any kind of select, try to tune your query so that the sub query only returns 1 value and not multiple.
If multiple are needed then restructure the query in such a way that the table becomes part of main query.
I am giving example of SQL:
Select col1, (select col2 from table2 where table2.col3=table1.col4) from table1;
If col2 returns multiple rows then the query fails then re-write it to:
Select col1, col2 from table1,table2 where table2.col3=table1.col4;
I hope you get the point.
You shouldn't SELECT it, you should SET it.
SET #maxid = MAX(ID) FROM another_table

Copy table rows using OUTPUT INTO in SQL Server 2005

I have a table which I need to copy records from back into itself. As part of that, I want to capture the new rows using an OUTPUT clause into a table variable so I can perform other opertions on the rows as well in the same process. I want each row to contain its new key and the key it was copied from. Here's a contrived example:
INSERT
MyTable (myText1, myText2) -- myId is an IDENTITY column
OUTPUT
Inserted.myId,
Inserted.myText1,
Inserted.myText2
INTO
-- How do I get previousId into this table variable AND the newly inserted ID?
#MyTable
SELECT
-- MyTable.myId AS previousId,
MyTable.myText1,
MyTable.myText2
FROM
MyTable
WHERE
...
SQL Server barks if the number of columns on the INSERT doesn't match the number of columns from the SELECT statement. Because of that, I can see how this might work if I added a column to MyTable, but that isn't an option. Previously, this was implemented with a cursor which is causing a performance bottleneck -- I'm purposely trying to avoid that.
How do I copy these records while preserving the copied row's key in a way that will achieve the highest possible performance?
I'm a little unclear as to the context - is this in an AFTER INSERT trigger.
Anyway, I can't see any way to do this in a single call. The OUTPUT clause will only allow you to return rows that you have inserted. What I would recommend is as follows:
DECLARE #MyTable (
myID INT,
previousID INT,
myText1 VARCHAR(20),
myText2 VARCHAR(20)
)
INSERT #MyTable (previousID, myText1, myText2)
SELECT myID, myText1, myText2 FROM inserted
INSERT MyTable (myText1, myText2)
SELECT myText1, myText2 FROM inserted
-- ##IDENTITY now points to the last identity value inserted, so...
UPDATE m SET myID = i.newID
FROM #myTable m, (SELECT ##IDENTITY - ROW_NUMBER() OVER(ORDER BY myID DESC) + 1 AS newID, myID FROM inserted) i
WHERE m.previousID = i.myID
...
Of course, you wouldn't put this into an AFTER INSERT trigger, because it will give you a recursive call, but you could do it in an INSTEAD OF INSERT trigger. I may be wrong on the recursive issue; I've always avoid the recursive call, so I've never actually found out. Using ##IDENTITY and ROW_NUMBER(), however, is a trick I've used several times in the past to do something similar.

Resources