Constraint violation on parallel run - sql

I created a function that will serves as primary key for my table
CREATE FUNCTION dbo.NewCustomerPK()
RETURNS VARCHAR (10)
AS
BEGIN
DECLARE #LastCustID VARCHAR(10)
DECLARE #newID INT
DECLARE #charID CHAR(10)
SELECT
#LastCustID = MAX(CustID)
FROM
dbo.TestCust
IF (#LastCustID IS NULL)
BEGIN
SET #LastCustID = 'CUST000001'
END
ELSE
BEGIN
SET #newID = RIGHT(#LastCustID, 6) + 1
SET #charID = 'CUST' + RIGHT(('0000000' + CONVERT(VARCHAR(6), #newID)), 6)
SET #LastCustID = #charID
END
RETURN #LastCustID
END
CREATE TABLE dbo.TestCust
(
CustID VARCHAR(10) PRIMARY KEY NOT NULL DEFAULT dbo.NewCustomerPK(),
Name VARCHAR(50)
)
And tried to insert a test data
DECLARE #Counter INT = 1,
#Stopper INT = 500000
WHILE(#Counter <= #Stopper)
BEGIN
INSERT INTO dbo.TestCust(NAME)
VALUES('test'+CONVERT(VARCHAR(6), #Counter))
SET #Counter = #Counter + 1
END
It works fine but when I try a parallel run(Running the loop data insertion in the new window) it cause a Primary Constraint Violation Error

Another way to do this is to use an identity column in combination with a calculated column.
create table dbo.TestCust
(
ID int identity not null,
CustID as isnull('CUST'+right('0000000' + convert(varchar(6), ID), 6), ''),
Name varchar(50),
constraint PK_TestCust_CustID primary key clustered (CustID)
);
The isnull around the calculation of CustID is there to make sure the column will never have NULL values and that makes it possible to use CustID as a primary key.
Not sure if it is possible to fix your current design. Perhaps using isolation level serializable when adding new rows would do the trick.

CREATE TRIGGER tr_Group ON TargetTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.targetTable
SELECT dbo.NewCustomerPK(),<other fields>
FROM INSERTED
END

Related

Is there any way to exec a stored procedure for all selected data rows?

I'm setting up a storekeeping program in which I have 2 tables, one for products and another for materials.
In the products table, each product has several materials. Is there any way to select these rows and decrement materials availability?
I tried to use a foreach loop but I couldn't implement it and store each rows data
CREATE TABLE materials
(
materialID INT PRIMARY KEY IDENTITY,
materialName NVARCHAR(100) NULL,
materialAmount INT NULL,
)
CREATE TABLE productStack
(
ID INT PRIMARY KEY IDENTITY,
productsID INT NULL,
materialID INT NULL,
amount INT NULL
)
GO;
CREATE PROCEDURE updateMaterials
(#ID INT,
#AMOUNT INT)
AS
BEGIN
UPDATE materials
SET materialAmount = (materialAmount - #AMOUNT)
WHERE materialID = #ID
END
You could use a temp table and while loop such as :
SELECT * INTO #TEMP_A FROM PRODUCTSTACK
DECLARE #ID INT,#AMOUNT INT
WHILE EXISTS(SELECT * FROM #TEMP_A)
BEGIN
SET #ID = (SELECT TOP 1 ID FROM PRODUCTSTACK)
SET #AMOUNT = (SELECT TOP 1 AMOUNT FROM PRODUCTSTACK WHERE ID = #ID)
EXEC UPDATEMATERIALS #ID,#AMOUNT
DELETE FROM #TEMP_A WHERE ID = #ID
END
As we have no sample to base this on, this is a guess. Like I said, however, seems like a table-type parameter would do this:
CREATE TYPE dbo.MaterialAmount AS table (ID int, Amount int);
GO
CREATE PROC dbo.UpdateMaterials #Materials dbo.MaterialAmount READONLY AS
BEGIN
UPDATE M
SET materialAmount = MA.Amount
FROM dbo.materials M
JOIN #Materials MA ON M.materialID = MA.ID;
END;
GO
--Example of usage:
DECLARE #Materials dbo.MaterialAmount;
INSERT INTO #Materials
VALUES(1,100),
(5,20);
EXEC dbo.UpdateMaterials #Materials;

INSERT statement conflicting with FOREIGN KEY constraint, but there's no conflict apparent

I'm not sure why I'm getting this error, because no data is being inserted into the column mentioned in the error. It's a PK field set to IDENTITY, so the value is auto-filled with each record added. The function is supposed to insert a record into an audit table, called by a trigger on the source table. Here's the error and my code.
Msg 547, Level 16, State 0, Procedure tblTriggerAuditRecord_TTeamPlayers, Line 33 [Batch Start Line 344]
The INSERT statement conflicted with the FOREIGN KEY constraint "Z_TTeamPlayers_TTeams_FK". The conflict occurred in database "dbSQL1", table "dbo.Z_TTeams", column 'intTeamAuditID'.
--Problematic Code
DELETE FROM TTeamPlayers
DELETE FROM TTeams
WHERE strTeam = 'Reds'
SELECT * FROM TTeams
SELECT * FROM Z_TTeams
=========================
-- both these tables are updated when a DELETE is run. I have the FK constraint set to CASCADE so that when it's deleted out of the child table Z_TTeamPlayers, the parent record is deleted.
CREATE TABLE Z_TTeamPlayers
(
intTeamPlayerAuditID INTEGER IDENTITY NOT NULL
,intTeamAuditID INTEGER NOT NULL
,intPlayerAuditID INTEGER NOT NULL
,UpdatedBy VARCHAR(50) NOT NULL
,UpdatedOn DATETIME NOT NULL
,strAction VARCHAR(10) NOT NULL
,strModified_Reason VARCHAR(1000)
--,CONSTRAINT PlayerTeam_UQ UNIQUE ( intTeamID, intPlayerID )
,CONSTRAINT Z_TTeamPlayers_PK PRIMARY KEY ( intTeamPlayerAuditID )
)
CREATE TABLE Z_TTeams
(
intTeamAuditID INTEGER IDENTITY NOT NULL
,intTeamID INTEGER NOT NULL
,strTeam VARCHAR(50) NOT NULL
,strMascot VARCHAR(50) NOT NULL
,UpdatedBy VARCHAR(50) NOT NULL
,UpdatedOn DATETIME NOT NULL
,strAction VARCHAR(10) NOT NULL
,strModified_Reason VARCHAR(1000)
,CONSTRAINT Z_TTeams_PK PRIMARY KEY ( intTeamAuditID )
)
==============================
ALTER TABLE Z_TTeamPlayers ADD CONSTRAINT Z_TTeamPlayers_TTeams_FK
FOREIGN KEY ( intTeamAuditID ) REFERENCES Z_TTeams ( intTeamAuditID ) ON DELETE CASCADE
==============================
-- --------------------------------------------------------------------------------
-- Create Trigger for Z_TTeamPlayers
-- --------------------------------------------------------------------------------
GO
CREATE TRIGGER tblTriggerAuditRecord_TTeamPlayers on TTeamPlayers
AFTER UPDATE, INSERT, DELETE
AS
DECLARE #Now DATETIME
DECLARE #Modified_Reason VARCHAR(1000)
DECLARE #Action VARCHAR(10)
SET #Action = ''
-- Defining if it's an UPDATE, INSERT, or DELETE
BEGIN
IF (SELECT COUNT(*) FROM INSERTED) > 0
IF (SELECT COUNT(*) FROM DELETED) > 0
SET #Action = 'UPDATE'
ELSE
SET #Action = 'INSERT'
ELSE
SET #Action = 'DELETE'
END
SET #Now = GETDATE() -- Gets current date/time
IF (#Action = 'INSERT')
BEGIN -- Begin INSERT info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT I.intTeamID, I.intPlayerID, SUSER_NAME(), GETDATE(), #Action, I.strModified_Reason
FROM INSERTED as I
INNER JOIN TTeamPlayers as T ON T.intTeamPlayerID = I.intTeamPlayerID
END -- End Insert Info
ELSE
IF (#Action = 'DELETE')
BEGIN -- Begin INSERT info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT D.intTeamID, D.intPlayerID, SUSER_SNAME(), GETDATE(), #Action, ''
FROM DELETED as D
END -- End Delete Info
ELSE -- #Action = 'UPDATE'
BEGIN --begin UPDATE info get modified reason
IF EXISTS (SELECT TOP 1 I.strModified_Reason FROM INSERTED as I, TPlayers as T WHERE I.intPlayerID = T.intPlayerID
AND I.strModified_Reason <> '')
BEGIN -- beging insert of UPDATE info
INSERT INTO Z_TTeamPlayers(intTeamAuditID, intPlayerAuditID, UpdatedBy, UpdatedOn, strAction, strModified_Reason)
SELECT I.intTeamID, I.intPlayerID, SUSER_SNAME(), GETDATE(), #Action, I.strModified_Reason
FROM TTeamPlayers as T
INNER JOIN INSERTED as I ON T.intPlayerID = I.intPlayerID
-- set modified reason column back to empty string
UPDATE TPlayers SET strModified_Reason = NULL
WHERE intPlayerID IN (SELECT TOP 1 intPlayerID FROM INSERTED)
END
ELSE
BEGIN -- begin if no modified reasson supplied
PRINT 'Error and rolled back, please enter modified reason'
ROLLBACK
END
END
z_TTeamPlayers.intTeamAuditID references your audit table's primary key. In your code you are inserting that value into z_TTeamPlayers... INSERT INTO Z_TTeamPlayers(intTeamAuditID... while it doesn't exist (as a primary key) in your audit table yet... thus it fails.
Here is a demo.
I get that you are trying to audit, but i'm not sure your business logic on teams and players. You seem to have your design a bit backwards. You could always use versioning in SQL Server.
As a guess you probably want a design similar to this instead

Alter table to add incrementally primary key records where ID values are null

I have a table in sql server with ID field as the primary key. In the rows of the ID field, some have primary key values while some rows do not have primary key values because the ID column allows null. Now I want to run a query to insert values incrementally into the rows that are null so that they can have primary key values. I have tried using an ALTER command but no head way
because you didn't provide any table structure description and we don't know if there are any business key or some unique combinations of data exists to identify a row without primary key then the easiest way, imho, is to use update cursor:
begin tran
-- rollback
-- commit
select * from [Table_1] where id is null
declare #Id int, #i int = 0
,#MaxId int
set #MaxId = (select Max(Id) from [Table_1] )
declare Update_cur cursor local
for select Id from [Table_1] where id is null
for update of Id
open Update_cur
fetch next from Update_cur into #Id
while ##FETCH_STATUS = 0 begin
set #i += 1
update [Table_1] set Id = #MaxId + #i where CURRENT OF Update_cur
fetch next from Update_cur into #Id
end
close Update_cur
deallocate Update_cur
select * from [Table_1] order by Id
P.S. don't forget to commit or rollback transaction after performing tests
You can DROP that column and ADD again with Auto Increment value.
ALTER TABLE your_table DROP COLUMN ID
ALTER TABLE your_table ADD ID INT IDENTITY(1,1)
This will generate all values from the start and as a result you will lose existing value (upto 6).

Delete and recreate identity column and fetch the newly created values in an update statement

I have a four tables called plandescription, plandetail and analysisdetail.
The table plandescription has the columns DetailQuestionID which is the primary and identity column and a QuestionDescription column.
The table plandetail consists of the column PlanDetailID which the primary and identity column, DetailQuestionID which is the foreign key attribute of plandescription table and a planID column
The third table analysisdetail consists of a analysisID which the primary and identity column, PlanDetailID which is the foreign key attribute of plandetail table and a scenario.
Below is the schema of the three tables
I have a two web form that will insert, update and delete data into these three tables in a two transaction. One web form will perform CRUD operations in plandescription and plandetail table. When the user inserts QuestionDescription and planid in this web form, I will insert the QuestionDescription Value in the plandescription table and will generate a DetailQuestionID value and this value is fed to the plandetail table with the planid. Here I will generate a PlanDetailID.
Once this transaction is done, I will show the second web form in which the user enters the scenario and this will be mapped with the plandescription using the PlanDetailID.
This schema cannot be changes as this is the client requirement.
When I insert values I don’t have any problem. However when I update existing data, I need to delete existing PlanDetailID in the plandetail table and recreate PlanDetailID data for that DetailQuestionID and planID. This is because, the user will be adding or deleting a planID associated with the QuestionDescription.
Once I recreate PlanDetailID for that DetailQuestionID and planID, I need to update the old PlanDetailID with the new PlanDetailID in the third table analysisdetail for the associated analysisID.
I created a #Temp table called #DetailTable to insert the values analysisID, planid and old PlanDetailID and new PlanDetailID so that I can have them in update statement once I delete the data from plandetail table for that PlanDetailID.
Then I deleted the plandetailid from the plandetail table and recreate PlanDetailID for that DetailQuestionID. During my recreation I fetched the new PlanDetailID’s created into another temp table called #InsertedRows
After this I am running a while loop to update the temp table #DetailTable with the newly created PlanDetailID for the appropriate planID’s.
The problem is here. When I have the same number of planID’s for example 2 planID’s 1,2 I will have only two old PlanDetailID and new PlanDetailID for that planID and analysisID.
But When I add a new PlanID or remove a existing planID I am getting null value for that newly added or deleted planID.
This is affecting my update statement of analysisdetail table as PlanDetailID cannot be null.
I tried to remove the Null value from the #DetailTable by running the update statement of analysisdetail in a while loop however its not working.
Can any one help me to solve this? Below is the code that I created.
DECLARE #cID INT = 8
DECLARE #DQID INT = 1380
/*------- I need the query to run for the below three data.
Here i'm updating my Pids that already exists in my database*/
DECLARE #Pids VARCHAR(MAX) = '2,4,5'
---DECLARE #Pids VARCHAR(MAX) = '2,4'
---DECLARE #Pids VARCHAR(MAX) = '1,2,4'
CREATE TABLE #DetailTable (
Id INT IDENTITY(1, 1)
,analID INT
,PlanID INT
,OldPlanID INT
,NewPlanID INT
)
INSERT INTO #DetailTable (
analID
,PlanID
,OldPlanID
) (
SELECT analID
,cfpd.PlanID
,cfpd.PlanDID FROM [dbo].[AnalDetail] rd INNER JOIN [dbo].[PlanDetail] cfpd ON rd.PlanDID = cfpd.PlanDID WHERE cfpd.DQID = #DQID
)
---- Delete previous functionalplan id
DELETE
FROM dbo.PlanDetail
WHERE DQID = #DQID;
---- Insert New plandetail id for the category
CREATE TABLE #InsertedRows (
Id INT IDENTITY(1, 1)
,NewPlanDID INT
,PlanID INT
)
INSERT INTO dbo.plandetail (
DQID
,planid
)
OUTPUT inserted.PlanDID
,inserted.planid
INTO #InsertedRows
SELECT #DQID
,data
FROM dbo.fndatasplit(#functionalPids, ',');
--- Get Latest PlanDID
DECLARE #loop INT
SET #loop = 1
DECLARE #NewPlanDID AS INT
DECLARE #FPlanId AS INT
WHILE (
#loop <= (
SELECT Count(*)
FROM #InsertedRows
)
)
BEGIN
IF EXISTS (
SELECT FunctionPlan
FROM #DetailTable
)
BEGIN
SELECT #FPlanId = PlanID
FROM #InsertedRows
WHERE ID = #loop
SELECT #NewPlanDID = newPlanDID
FROM #InsertedRows
WHERE ID = #loop
UPDATE #DetailTable
SET NewPlanID = #NewPlanDID
WHERE PlanID = #FPlanId
SET #loop = #loop + 1
END
END
--- Update AnalDetail Table with New PlanDetail
DECLARE #intFlag INT
SET #intFlag = 1
DECLARE #analID INT
DECLARE #NewPlanID INT
WHILE (
#intFlag <= (
SELECT Count(*)
FROM #DetailTable
WHERE NewPlanID IS NOT NULL
)
)
BEGIN
SELECT #analID = analID
FROM #DetailTable
WHERE ID = #intFlag
SELECT #NewPlanID = NewPlanID
FROM #DetailTable
WHERE ID = #intFlag
UPDATE dbo.AnalDetail
SET PlanDID = #NewPlanID
WHERE analID = #analID
SET #intFlag = #intFlag + 1
END
SELECT *
FROM #DetailTable
SELECT *
FROM #InsertedRows
SELECT *
FROM AnalDetail
--- Function DataSplit
/****** Object: UserDefinedFunction [dbo].[fnDataSplit] Script Date: 25-07-2015 12:21:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnDataSplit]
(
#RowData nvarchar(2000),
#SplitOn nvarchar(5)
)
RETURNS #RtnValue table
(
Id int identity(1,1),
Data nvarchar(100)
)
AS
BEGIN
Declare #Cnt int
Set #Cnt = 1
While (Charindex(#SplitOn,#RowData)>0)
Begin
Insert Into #RtnValue (data)
Select
Data = ltrim(rtrim(Substring(#RowData,1,Charindex(#SplitOn,#RowData)-1)))
Set #RowData = Substring(#RowData,Charindex(#SplitOn,#RowData)+1,len(#RowData))
Set #Cnt = #Cnt + 1
End
Insert Into #RtnValue (data)
Select Data = ltrim(rtrim(#RowData))
Return
END
Found the answer myself after exploring things in google.
I created a temp table and fetched the identity values of the inserted rows using the OUTPUT command to the temp table.
from the temp table is got the new identity values.
below is the code
INSERT INTO dbo.plandetail (
DQID
,planid
)
OUTPUT inserted.PlanDID
,inserted.planid
INTO #InsertedRows
SELECT #DQID
,data
FROM dbo.fndatasplit(#functionalPids, ',');

Check CONSTRAINT Not work in SQL Server

I have to restrict to insert duplicate data in my table with condition
Here is SQL Server Table
CREATE TABLE [dbo].[temptbl](
[id] [numeric](18, 0) IDENTITY(1,1) NOT NULL,
[DSGN] [varchar](500) NULL,
[RecordType] [varchar](1000) NULL
)
I want to put condition on RecordType, if RecordType is 'SA' Than check CONSTRAINT (means if DSGN = 0 and RecordType = 'SA' Exist than i don't want to insert that data.
if DSGN = 1 and RecordType = 'SA' Not Exist than i want to insert that data.
If RecordType is other than 'SA' than insert any data
For that i create constraint but it is not work
ALTER TABLE temptbl WITH CHECK ADD CONSTRAINT chk_Stuff CHECK (([dbo].[chk_Ints]([DSGN],[RecordType])=(0)))
GO
ALTER FUNCTION [dbo].[chk_Ints](#Int_1 int,#Int_2 varchar(20))
RETURNS int
AS
BEGIN
DECLARE #Result INT
BEGIN
IF #Int_2 = 'SA'
BEGIN
IF NOT EXISTS (SELECT * FROM [temptbl] WHERE DSGN = #Int_1 AND RecordType = #Int_2)
BEGIN
SET #Result = 0
END
ELSE
BEGIN
SET #Result = 1
END
END
ELSE
BEGIN
SET #Result = 0
END
END
RETURN #Result
END
But it is not working. please suggest me
Ditch the function and the check constraint:
CREATE UNIQUE INDEX IX_temptbl_SA ON temptbl (DSGN) WHERE RecordType='SA'
This is known as a filtered index.
Your check constraint wasn't working as you thought it would because when a check constraint is evaluated for any particular row, that row is already visible within the table (within the context of that transaction) and so each row was effectively blocking its own insertion.
Use With NOCheck for only new data. Here is the link provided.
https://technet.microsoft.com/en-us/library/ms179491(v=sql.105).aspx