SQL Server : primary key violation on occasion despite prior deletion - sql

I have a procedure that is supposed to replace an entry in my table by first deleting it based on an id and then inserting it with new values.
It looks like this, plus additional values used for the insert statement that I have left out.
ALTER PROCEDURE [dbo].[DemoProc]
#id BIGINT,
...
AS
IF EXISTS (SELECT id FROM demoTable WHERE id = #id)
DELETE demoTable
WHERE id = #id
INSERT INTO demoTable (id, ...)
VALUES (#id, ...)
RETURN 0
Now every now and again, I'm getting an error log that alerts me of a primary key violation that originates from this procedure. I also end up with the entry not being inserted at all. Any ideas?

Related

How to automatically update second table with some same information after insert into first table

When I enter a new record in one table, I need to have some of the information from the first table be automatically added to the second table. I unsuccessfully tried triggers to do this.
My primary table looks like this:
CREATE TABLE demographics (
person_local_id BIGSERIAL UNIQUE PRIMARY KEY,
first_name VARCHAR(50)...[other lines]
);
I set up the child table like this:
CREATE TABLE pedigree (
pedigree_id BIGSERIAL PRIMARY KEY NOT NULL,
person_local_id BIGSERIAL NOT NULL,
person_sex VARCHAR(10),
father VARCHAR(10) DEFAULT 0,
mother VARCHAR(10) DEFAULT 0,
FOREIGN KEY (person_local_id) REFERENCES demographics (person_local_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
My approach was to create a trigger on the demographics primary table such that any time a record was added to it, a corresponding record would be added to the pedigree table consisting of just the person_local_id. I added a foreign key on the pedigree table that referenced the column in the demographics that I need to copy over to the pedigree table in that column.
Then I created a trigger, but it doesn't work. I tried this with and without the word "EXECUTE".
CREATE TRIGGER into_pedigree AFTER INSERT OR UPDATE ON demographics
FOR EACH ROW EXECUTE INSERT INTO pedigree (person_local_id) SELECT (NEW.person_local_id) FROM NEW;
I keep getting syntax errors but I can't identify the error:
ERROR: syntax error at or near "INSERT"
LINE 2: FOR EACH ROW EXECUTE INSERT INTO pedigree (person_local_id) ...
^
I also tried this, adding the name:
CREATE TRIGGER into_pedigree ON identify_relatives_database.demographics
AFTER INSERT
AS
BEGIN
INSERT INTO pedigree (person_local_id) VALUES (INSERTED.person_local_id)
END;
But I get the error message:
ERROR: syntax error at or near "ON"
LINE 1: CREATE TRIGGER into_pedigree ON demographics
^
I appreciate your assistance.
You may try this.
CREATE TRIGGER [dbo].[Customer_INSERT]
ON [dbo].[demographics]
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CustomerId INT
SELECT #CustomerId = INSERTED.person_local_id FROM INSERTED
IF EXISTS ( SELECT * FROM PEDIGREE WHERE person_local_id = #CustomerId)
BEGIN
--- here col is the required column name need to be modified,
--- since you are inserting person_local_id from base table which is auto generated and not suppose to be change in any condition
UPDATE PEDIGREE SET COL = INSERTED.COL WHERE person_local_id = #CustomerId
END
ELSE
BEGIN
INSERT INTO pedigree (person_local_id)
VALUES(#CustomerId)
END
END
Although I don't find anything related to your update part. Since you are inserting primary key from base table as foreign key in child table, so for normalization it is not going to changed in any condition. So i don't think you need update part in your trigger Hence your required trigger will be:
CREATE TRIGGER [dbo].[Customer_INSERT]
ON [dbo].[demographics]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO pedigree (person_local_id)
SELECT INSERTED.person_local_id FROM INSERTED
END
You should try this :
CREATE TRIGGER into_pedigree ON demographics
FOR INSERT
AS
insert into pedigree (person_local_id)
values(inserted.person_local_id);
PRINT 'AFTER INSERT trigger fired.'
GO

Use trigger to act as foreign key across 2 different servers

I'm trying to replicate a foreign key across two servers using trigger. I know using trigger across 2 servers is not the best practice but my company on gives me read-only access to their database which I need to relate to my application.
I have DB1 which is my local database and it is attached to DB2 using linked server. I want trigger to check if a specific ID from a DB2_table on DB2 exists before executing an INSERT on DB1_table where the ID from DB2_table will act as a foreign key.
CREATE TRIGGER trigger_DB1_Table_Insert
#ID
BEFORE INSERT ON DB1_Table
AS
BEGIN
if exists(Select ID from DB2_Table where ID = #ID)
--execute insert
END
GO
I would actually recommend using a check constraint instead of a trigger. Check Constraints are designed to enforce data integrity and are a semantically better option. The example below creates some working tables, then creates a function which will check if the record exists in the other table.
The check constraint then uses the function and returns an error if the value doesn't exist.
CREATE TABLE dbo.OtherTable(
id INT
)
CREATE TABLE dbo.a(
id INT IDENTITY(1,1)
,OtherTableId INT
)
GO
INSERT INTO OtherTable (id) VALUES (1), (2), (3)
GO
-- Function will check if the ID exists in the other table
CREATE FUNCTION dbo.CheckOtherTableId(
#OtherTableId INT
)
RETURNS BIT
AS BEGIN
RETURN
(
SELECT
CASE
WHEN EXISTS
(
SELECT 1
FROM OtherTable
WHERE id = #OtherTableId
)
THEN 1
ELSE 0
END
)
END
GO
-- Add check constraint
ALTER TABLE a WITH CHECK
ADD CONSTRAINT [CK_OtherTable] CHECK (1=dbo.CheckOtherTableId(OtherTableId))
GO
-- Test should work
INSERT INTO a (OtherTableId) values (1)
Go
-- Test should fail
INSERT INTO a (OtherTableId) values (8)
GO

Primary Key Error while inserting record into table

I am passing datatable as input parameter to a stored procedure. I have created custom type for it.
Here is my stored procedure:
CREATE PROCEDURE [dbo].[Proc_AddEmployee]
#tblEmp EmpType READONLY,
#Code int
AS
BEGIN
INSERT INTO Employee ([Name], [Lname], [Code])
SELECT
[Name], [Lname], #Code
FROM #tblEmp
Here it fetch record from datatable and insert into Employee table.
Table Employee has primary key (combination Name and Lname).
Table Employee:
Nmae LName Code
Rashmi Hirve 89
Rani Mohite 7
DataTable :
Nmae LName
Rani Mohite
Swati More
Reshma Gawade
Problem appears when I try to add record (Rani, Mohite) from datatable to table Employee.
It causes a primary key error at first record and does not proceed further.
I want like this if error come skip that record fetch next record and insert that. There are 8000 records, which I want to pass from datatable to Employee table.
If I checked not exist, then insert will take long time to execute query.How to handle that?
Adding a check for EXISTS on the INSERT statement should not have a significant effect on performance.
INSERT INTO Employee ([Name] ,[Lname] ,[Code])
SELECT [Name] ,[Lname] ,#Code
FROM #tblEmp AS t
WHERE NOT EXISTS
( SELECT 1
FROM Employee AS e
WHERE e.Name = t.Name
AND e.Lname = t.Lname
);
This is fairly safe, but still vulnerable to a race condition. I think the safest way to do the insert, with is to use MERGE with the locking hint HOLDLOCK:
MERGE Employee WITH(HOLDLOCK) AS t
USING #tbl AS s
ON s.Name = t.Name
AND s.LName = t.LName
WHEN NOT MATCHED THEN
INSERT ([Name] ,[Lname] ,[Code])
VALUES (s.Name, s.Lname, #Code);
If the table has a primary key that is not set to auto generate then it will error when you try to insert a record without the key. You will need to either set the primary key field as an identity seed or you can include the primary key with the insert.

Select from another table within an Insert Trigger

I am maintaining an audit table, where in I have a parent table and it's child table.I want to insert the primary key of the parent audit table into it's child audit table.
Should I be declaring a "before insert" instead of a "for insert" trigger. Here's my code:
CREATE trigger [trgAudtblChild] On [tblChild]
for Insert
as
BEGIN
declare #serNo bigint
declare #expSerNo int
declare #postPercent numeric (12, 2)
declare #prdSAPid varchar (50)
declare #lastUpdatedBy int
declare #lastUpdatedOn smalldatetime
SELECT
--#serno = serno,
#expSerNo = expSerNo ,
#postPercent = postPercent ,
#prdSAPid = prdSAPid ,
#lastUpdatedBy = lastUpdatedBy ,
#lastUpdatedOn = lastUpdatedOn
FROM INSERTED
select #serno = max(at_serno) from AT_tblParent
insert into AT_tblChild(serNo, expSerNo, postPercent
, prdSAPid, lastUpdatedBy, lastUpdatedOn
, change_column_index) values(
#serNo, #expSerNo, #postPercent
, #prdSAPid, #lastUpdatedBy, #lastUpdatedOn
, 'INSERTED')
End
Return
The above code, does not work and puts the table into transaction.
Before Trigger - When you want to Intercept the data before it actually gets Inserted in Table.
For Trigger - Your record is Inserted but can still modify it.
The only difference is that about record is actually Inserted or not.
Back to the original Query
In you above mentioned situation, you should not use Before Trigger. Consider a case, when your Parent Table record Insertion in under some Transaction and same Transaction gets Rollbacked. In that case, It will crash for the Foreign key constraint. Because you will try to Reference a Foreign key Record of Parent Table into Child Table during Insertion which does not exist in Parent Table.

Update value on insert into table in SQL Server

I am working with SQL Server - on inserting into a table, I have a unique constraint on a table column id. There is a possibility that when inserting, the value going into the id column is 0. This will cause an error.
Is it possible to update this id to another value during the insert if the id value is 0? This is to prevent the error and to give it a valid value.
Possibly a trigger?
A trigger is one way, but you may want to use a filtered index (CREATE UNIQUE INDEX, not as a table constraint) to ignore zero value. This way, you don't have to worry about what value to put there
Alternatively, if you want to populate it from another column, you can have a computed column with a unique constraint.
ALTER TABLE whatever
ADD ComputedUniqueCol = CASE WHEN Id = 0 THEN OtherCol ELSE Id END
If that's your primary key you can specify it as IDENTITY. Then it should generate a value for itself based on seed and increment (the default is seed=1 and default=1) so you don't have to worry about it.
CREATE TABLE MyTable
(
ID int PRIMARY KEY IDENTITY,
...
)
create an "instead of" trigger and check for the value on the ID.
CREATE trigger checkID
on YOUR_TABLE
instead of insert
as
begin
declare #id int
select #id=id from inserted
if (#id==0) begin
--DO YOUR LOGIC HERE AND THEN INSERT
end else begin
insert into DESTINATION_TABLE (VALUES)
SELECT VALUES FROM INSERTED
end
end