I am running into a problem where my check constraints are correctly stopping commands from executing but my Identity column value increases. I guess this is because the check occurs after the statement runs and the transaction gets rolled back due to the check failing. This leaves the identity value incremented by 1.
Is there a way to run the constraint check before the SQL statement gets executed?
CREATE TABLE TestTable
(
Id INT IDENTITY(1,1) PRIMARY KEY(Id),
Name VARCHAR(100)
)
INSERT INTO TestTable VALUES ('Type-1'),('Type-2'),('Type-55'),('Type-009')
--Add a check constraint so nobody can edit this without doing serious work
ALTER TABLE TestTable WITH NOCHECK ADD CONSTRAINT [CHECK_TestTable_READONLY] CHECK(1=0)
--This fails with the constraint as expected
INSERT INTO TestTable VALUES('This will Fail')
INSERT INTO TestTable VALUES('This will again....')
--Check the Id, it was incremented...
SELECT (IDENT_CURRENT( 'TestTable' ) ) As CurrentIdentity
When I had to do the same thing in the past I created a trigger that just threw an exception on insert and delete. this has several advantages, most importantly is that it prevents updates and deletes and you could give a custom exception message explaining what you did there and why, its an extremely bad habit to just put illogical constraints and hope that 3 months from now people would understand whats going on there and know they should ask you about it. It also prevents the Id counter from being incremented if its that important. If it is important, I would also not use auto increment and just set the ID number manually, since even if you are using these triggers you could always have an accidental syntax error or any other error after you disabled them and tried to add a value.
create trigger PreventChanges
on TestTable
FOR INSERT, UPDATE, DELETE
as
begin
throw 51000, 'DO NOT change anything in that table unless you really have to! in order to do so pleasae talk to GER (or just disable and reenable this trigger)',1
and
It sounds like you're intending to use the identity column for something it's not meant for. But to answer your question, could you not just manually code up some SQL Server IF statements to test your data before the insert happens (perhaps in a stored procedure)? I wouldn't know how to make this dynamic to 'fit all constraints on any table', but the process would do what you want - prevent the INSERT from firing. Though, if your constraints change, then you would have to change the procedure too.
e.g.,
IF 1 = 0 -- or use any of your constraints here...
BEGIN
-- nest more IFs if you have multiple check-constraints...
INSERT INTO TestTable
VALUES ('This will not increase your identity number since 1 does not equal 0')
END
Related
Before each Update/Insert statement, should I :
IF...EXIST to test the primary key
Just let a transaction fail if primary key is already there (and rely on ##rowcount if I
have some logic related to primary key already being there)
TRY ... CATCH an error (raised by the Update/Insert statement itself or have a trigger test primary key and raise errors)
Other solutions ?
How do you write with primary key constraint ?
My preferred method for single-row upsert is:
BEGIN TRANSACTION;
UPDATE dbo.t WITH (HOLDLOCK, SERIALIZABLE)
SET ...
WHERE [key] = #key;
IF ##ROWCOUNT = 0
BEGIN
INSERT dbo.t ...
END
COMMIT TRANSACTION;
If you believe you will much more often be performing an insert, you can swap the logic around so you try that first:
BEGIN TRANSACTION;
INSERT dbo.t ...
SELECT #key, ...
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.t WITH (UPDLOCK, SERIALIZABLE)
WHERE [key] = #key
);
IF ##ROWCOUNT = 0
BEGIN
UPDATE dbo.t SET val = #val WHERE [key] = #key;
END
COMMIT TRANSACTION;
Some background:
Please stop using this UPSERT anti-pattern
Checking for potential constraint violations before entering TRY/CATCH
So, you want to use MERGE, eh?
What you describe is often called an "UPSERT" (in case you need a Google term for further research).
We use MERGE statements, since they allow us to specify both actions in one statement.
However, the syntax is a bit complex and there are some gotchas (don't forget to use HOLDLOCK, etc.), so we have abstracted away the actual SQL generation into an InsertOrUpdate(table, fieldsAndValuesToUpdate, keyFieldsAndValues) helper method in our source code. This also allows us to change the implementation later, if required.
When writing SQL code manually, I use IF...EXISTS (inside a transaction and also with HOLDLOCK), since it's easier to read and easier to write.
That depends on the situation.
Suppose you would write an insert with a where not exists clause and when ##rowcount = 0 you would do an update because this row already seems to exist.
If this is the most performant way to do it, that depends on your data.
if you would know that for example in 80% of the cases the insert would succeed, then this approach would actually perform very good.
If it seems that most of the times an update is needed, then you could turn the code around, do the update and then check the ##rowcount.
This only works off course if you can determine before you start if you will have mostly updates or mostly inserts.
The advantage of this method (certainly when you do update first) is that you do not need to check each row first with an if...exists first, you just do you insert/update and find out after if it worked or not. And because you know before that the insert or update will succeed most of the times, you gain performance
Suppose a table in SQLServer with this structure:
TABLE t (Id INT PRIMARY KEY)
Then I have a stored procedure, which is constantly being called, that works inserting data in this table among other kind of things:
BEGIN TRAN
DECLARE #Id INT = SELECT MAX(Id) + 1 FROM t
INSERT t VALUES (#Id)
...
-- Stuff that gets a long time to get completed
...
COMMIT
The problem with this aproach is sometimes I get a primary key violation because 2 or more procedure calls get and try to insert the same Id on the table.
I have been able to solve this problem adding a tablock in the SELECT sentence:
DECLARE #Id INT = SELECT MAX(Id) + 1 FROM t WITH (TABLOCK)
The problem now is sucessive calls to the procedure must wait to the completion of the transaction currently beeing executed to start their work, allowing just one procedure to run simultaneosly.
Is there any advice or trick to get the lock just during the execution of the select and insert sentence?
Thanks.
TABLOCK is a terrible idea, since you're serialising all the calls (no concurrency).
Note that with an SP you will retain all the locks granted over the run until the SP completes.
So you want to minimise locks except for where you really need them.
Unless you have a special case, use an internally generated id:
CREATE TABLE t (Id INT IDENTITY PRIMARY KEY)
Improved performance, concurrency etc. since you are not dependent on external tables to manage the id.
If you have existing data you can (re)set the start value using DBCC
DBCC CHECKIDENT ('t', RESEED, 100)
If you need to inject rows with a value preassigned, use:
SET IDENTITY_INSERT t ON
(and off again afterwards, resetting the seed as required).
[Consider whether you want this value to be the primary key, or simply unique.
In many cases where you need to reference a tables PK as a FK then you'll want it as PK for simplicity of join, but having a business readable value (eg, Accounting Code or OrderNo+OrderLine is completely valid) : that's just modelling]
I am new to triggers and I have created a trigger to check foreign key constraints across tables in different databases. I know this shouldn't be done however this is the only solution that I could find to resolve my issue with foreign key constraints. The trigger does work however it does not tell me what record caused the violation when the insert script has more than one record to insert. I am looking for a way that the trigger could tell me the record with 'x' primary key and 'y' foreign key was the one to fail. Currently, it runs thru the script comes across a violation, throws the RAISERROR, rolls back everything and nothing gets inserted in the database. Below is my script -
Create Trigger AV.fkConstraintTrigger ON [AQB_MON].[AV].[NAAQValue]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT *
FROM INSERTED AS I
WHERE NOT EXISTS (
SELECT *
FROM [AVData].[dbo].[SourceParameterTemplate] AS A
WHERE I.[SourceParameterTemplateID] = A.[SourceParameterTemplateID]
)
)
BEGIN
RAISERROR('Violation of foreign key constraint',16,1);
ROLLBACK;
END
END
UPDATE
I made changes to the script based on the comments below because I would rather have them displayed in the message then in a separate table. However when I do I get two 'Incorrect syntax near' errors. First being the '=' and the second is the last ')'. I cannot see what would cause them.
Create Trigger AV.testfkTrigger ON [AQB_MON].[AV].[NAAQValue]
FOR INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
Declare #SourceParameterTemplateID varchar(25)
IF EXISTS (
SELECT TOP 1 #SourceParameterTemplateID = [SourceParameterTemplateID]
FROM INSERTED AS I
WHERE NOT EXISTS (
SELECT *
FROM [AVData].[dbo].[SourceParameterTemplate] AS A
WHERE I.[SourceParameterTemplateID] = A.[SourceParameterTemplateID]
)
order by [SourceParameterTemplateID]
)
BEGIN
RAISERROR('Violation of foreign key constraint',16,1, #SourceParameterTemplateID);
ROLLBACK;
END
END
Putting the data into the error message is not very useful. What would make more sense is to create and exception table that stores the data and possibly any other useful fields like the time of the problem and the user who sent in the bad data.
If you temporarily put the info into a table variable, then you can insert to the exception table after the rollback as the table variable is not rolled back.
Then your application can look up the data in the table if an error is returned from the insert/update.
I have an insert statement in a stored procedure who's primary key is a serial id. I want to be able to populate an additional field in the same table during the same insert statement with the serial id used for the primary key. Is this possible?
Unfortunately this is a solution already in place... I just have to implement it.
Regards
I can't imagine a reason why you would want a copy of the key in another column. But in order to do it, I think you'll need to follow your update with a statement to get the value of the identity key, and then an update to put that value in the other column. Since you're already in a stored procedure, it's probably ok to have a few extra statements, instead of doing it in the very same one.
DECLARE #ID INT;
INSERT INTO TABLE_THINGY (Name, Address) VALUES ('Joe Blow', '123 Main St');
SET #ID = SCOPE_IDENTITY();
UPDATE TABLE_THINGY SET IdCopy = #Id WHERE ID = #ID
If it's important that this be done every single time, you might want to create a Trigger to do it; beware, however, that many people hate triggers because of the obfuscation and difficulty in debugging, among other reasons.
http://blog.sqlauthority.com/2007/03/25/sql-server-identity-vs-scope_identity-vs-ident_current-retrieve-last-inserted-identity-of-record/
I agree, it is odd that you would replicate the key within the same table but with that said you could use a trigger, thus making it have no impact to current insert statements.
The below trigger is "After Insert" so technically it happens milliseconds after the insert if you truly wanted it to happen at the same time you would use a FOR INSERT instead and just replicate the logic used to create the serial id field into the new field.
CREATE TRIGGER triggerName ON dbo.tableName
AFTER INSERT
AS
BEGIN
update dbo.tableName set newField = inserted.SerialId where serialId = inserted.SerialId
END
GO
You could have a computed column that just returns the id column.
CREATE TABLE dbo.Products
(
ProductID int IDENTITY (1,1) NOT NULL
, OtherProductID AS ProductID
);
Having said that, data should only live in one place and to duplicate it in the same table is just a wrong design.
No, you cannot use the same insert statement for identity Id and copy that auto generated Id to the same row.
Multi-Statement using OUTPUT inserted or Trigger is your best bet.
I'm inserting a large amount of rows into an empty table with a primary key constraint on one column.
If there is a duplicate key error, is there any way to find out the value of the key (or row) that caused the error?
Validating the data prior to the insert is sadly not something I can do right now.
Using SQL 2008.
Thanks!
Doing the count(*) / group by thing is something I'm trying to avoid, this is an insert of hundreds of millions of rows from hundreds of different DB's (some of which are on remote servers)...I don't have the time or space to do the insert twice.
The data is supposed to be unique from the providers, but unfortunately their validation doesn't seem to work correctly 100% of the time and I'm trying to at least see where it's failing so I can help them troubleshoot.
Thank you!
There's not a way of doing it that won't slow your process down, but here's one way that will make it easier. You can add an instead-of trigger on that table for inserts and updates. The trigger will check each record before inserting it and make sure it won't cause a primary key violation. You can even create a second table to catch violations, and have a different primary key (like an identity field) on that one, and the trigger will insert the rows into your error-catching table.
Here's an example of how the trigger can work:
CREATE TRIGGER mytrigger ON sometable
INSTEAD OF INSERT
AS BEGIN
INSERT INTO sometable SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 1 FROM inserted;
INSERT INTO sometableRejects SELECT * FROM inserted WHERE ISNUMERIC(somefield) = 0 FROM inserted;
END
In that example, I'm checking a field to make sure it's numeric before I insert the data into the table. You'll need to modify that code to check for primary key violations instead - for example, you might join the INSERTED table to your own existing table and only insert rows where you don't find a match.
The solution would depend on how often this happens. If it's <10% of the time then I would do the following:
Insert the data
If error then do Bravax's revised solution (remove constraint, insert, find dup, report and kill dup, enable constraint).
This means it's only costing you on the few times an error occurs.
If this is happening more often then I'd look at sending the boys over to see the providers :-)
Revised:
Since you don't want to insert twice, could you:
Drop the primary key constraint.
Insert all data into the table
Find any duplicates, and remove them
Then re-add the primary key constraint
Previous reply:
Insert the data into a duplicate of the table without the primary key constraint.
Then run a query on it to determine rows which have duplicate values for the rpimary key column.
select count(*), <Primary Key>
from table
group by <Primary Key>
having count(*) > 1
Use SSIS to import the data and have it check for this as part of the data flow. That is the best way to handle. SSIS can send the bad records to a table (that you can later send to the vendor to help them clean up their act) and process the good ones.
I can't believe that SSIS does not easily address this "reality", because, let's face it, oftentimes you need and want to be able to:
See if a record exists with a certain unique or primary key
If it does not, insert it
If it does, either ignore it or update it.
I don't understand how they would let a product out the door without this capability built-in in an easy-to-use manner. Like, say, set an attribute of a component to automatically check this.