Cannot insert duplicate key error message for update query - sql-server

I am trying to update data using this update query....not inserting. but i am getting this error message. How to fix this
Msg 2627, Level 14, State 1, Procedure
BF_TR_BUDCONTRACTS_HISTORY_INSERTUPDATE, Line 67 Violation of UNIQUE
KEY constraint 'UK_BudContractHis'. Cannot insert duplicate key in
object 'dbo.BUDCONTRACTS_HISTORY'. The duplicate key value is (10567,
4377, 228, 1). The statement has been terminated.
Update BUDCONTRACTS
set BUDCONTRACTS.VERSIONID = 1
,BUDCONTRACTS.STATUSID = 1
,BUDCONTRACTS.ImportedRecord = 1
,BUDCONTRACTS.CUSTOMERID = BCUST.CustomerID
,BUDCONTRACTS.LastModifiedUserID = 'Import'
,BUDCONTRACTS.LASTMODIFIEDDATETIME = GETDATE()
FROM BUDCONTRACTS BCON
INNER JOIN External_Blk_Itm_Contracts EBIC WITH(NOLOCK) ON BCON.ContractName = EBIC.ContractName AND EBIC.ContractName = '00-000'
INNER JOIN BUDTERMINALS BT WITH(NOLOCK) ON BT.MBFTERMINALNAME = EBIC.TerminalName AND BT.TERMINALID = BCON.TERMINALID
INNER JOIN BudCustomers BCUST WITH(NOLOCK) ON BCUST.LegalName = EBIC.CustomerName AND BCUST.CustomerID = BCON.CUSTOMERID
AND (
(BCON.STATUSID <> 1)
OR (BCON.ImportedRecord <> 1)
OR (ISNULL(BCON.CUSTOMERID,'') <> ISNULL(BCUST.CustomerID,'')
))

I had the same problem and it turned out I didn't have enough keys in the update statement to only update 1 record when I ran the query it returned more than 1 row and the update failed. This was good as I had changed the clustered index for the extra field.

Here is a better way to write this update.
Update BCON
set VERSIONID = 1
, STATUSID = 1
, ImportedRecord = 1
, CUSTOMERID = BCUST.CustomerID
, LastModifiedUserID = 'Import'
, LASTMODIFIEDDATETIME = GETDATE()
FROM BUDCONTRACTS BCON
INNER JOIN External_Blk_Itm_Contracts EBIC ON BCON.ContractName = EBIC.ContractName AND EBIC.ContractName = '00-000'
INNER JOIN BUDTERMINALS BT ON BT.MBFTERMINALNAME = EBIC.TerminalName AND BT.TERMINALID = BCON.TERMINALID
INNER JOIN BudCustomers BCUST ON BCUST.LegalName = EBIC.CustomerName AND BCUST.CustomerID = BCON.CUSTOMERID
AND
(
BCON.STATUSID <> 1
OR
BCON.ImportedRecord <> 1
)
--OR ISNULL(BCON.CUSTOMERID,'') <> ISNULL(BCUST.CustomerID,'' This is pointless here because you already joined where these values are equal
Now for the actual issue. You are not referencing dbo.BUDCONTRACTS_HISTORY in this query so it is obvious there is an UPDATE trigger on BUDCONTACTS. That is where the problem is happening. It is probably trying to insert a row into the history table but there is already a row with the key value so it is unable to insert that row. If you can post the trigger code and the table definition for dbo.BUDCONTRACTS_HISTORY we can help you straighten out the trigger.

Related

Update multiple tables with multiple rows with trigger

I have these tables Users, UserPackages, Packages, Refers.
Users table has columns UserRef_NO (his own unique number), RefNo (if he registered using someones reference number) and Refarning (where he/she gets earning if someone registered using his refNo and bought a package and package status changed to 'True').
UserPackages table has columns U_ID (foreign key to Users), P_ID (foreign key to Packages), PackageStatus (true if bought false if not), BuyDate (generated as soon as PackageStatus changes to true), ExpiryDate (calculated from the join of Packages table and BuyDate where validity is total days of Packages)
Packages table has columns Price, ReferComission (in percentage), Validity (in days).
EDIT:- Third Edit this works fine for single row updates but fails for multi row updates saying subquery returned more then one value
alter TRIGGER [dbo].[HeavyTriggerTest] ON [dbo].[UserPackages]
after update
as
update UP set
BuyDate = GETDATE(), Expirydate = dateadd(dd, P.Validitiy, getdate())
from dbo.UserPackages UP
inner join Inserted I on I.P_ID = UP.P_ID and I.U_ID = UP.U_ID
inner join dbo.Packages P on P.PID = I.P_ID
where UP.PackageStatus = 'True';
;
with firstCte(Name,UID,PName,Price,ReferComission,ReferredBy)
as
(
select distinct Users.Name,Users.ID,Packages.PName,Packages.Price,Packages.ReferCommission,(select DISTINCT ID from Users
where Users.UserRef_No=Refers.RefOf )
from Users inner join UserPackages on UserPackages.U_ID=Users.ID
inner join Packages on Packages.PID=UserPackages.P_ID
inner join Refers on Users.Ref_No=Refers.RefOf
inner join Inserted I on I.U_ID = UserPackages.U_ID and I.P_ID = UserPackages.P_ID
and UserPackages.PackageStatus='true' AND UserPackages.U_ID=i.U_ID
AND Refers.RefOf=(SELECT users.Ref_No where UserPackages. U_ID=i.U_ID)
)
update Users set RefEarning+= Price*ReferComission/100 from firstCte where ID=ReferredBy ;
update Users set Active='True' where ID=(select U_ID from inserted) and Active='False'
and here's the single update query which i tried to replace with above last two updates but it gives wrong results plus it also doesn't work for multiple row updates
update Users set RefEarning+=(
case when ID=firstCte.ReferredBy then firstCte.Price*ReferComission/100 else RefEarning end)
,Active=case when ID=(select U_ID from inserted) and Active='false' then 'True'
when firstCte.ReferredBy=(select U_ID from inserted) then 'true' else Active end
from firstCte
Your second query, first update, you need to join Inserted on as:
update UP set
BuyDate = GETDATE(), Expirydate = dateadd(dd, P.Validitiy, getdate())
from dbo.UserPackages UP
inner join Inserted I on I.P_IP = UP_P_ID and I.U_ID = UP.U_ID
inner join dbo.Packages P on P.PID = I.P_ID
where UP.PackageStatus = 'True';
Note the table aliases which I recommended to you in your last question - they make it much easier to read through a query.
Also note its best practice to schema qualify your tables.
Second query, second update:
with firstCte ([Name], [UID], PName, Price, ReferComission, ReferredBy)
as
(
select U.[Name], U.ID, P.PName, P.Price, P.ReferCommission
, max(U.ID) over () as referedby
from Users U
inner join UserPackages UP on UP.U_ID = U.ID
inner join Packages P on P.PID = UP.P_ID
inner join Refers R on R.RefOf = U.Ref_No
inner join Inserted I on I.U_ID = UP.U_ID and I.P_ID = UP.P_ID
where UP.PackageStatus='true'
)
update U set
Active = 'True'
, RefEarning += Price*ReferComission/100
from Users U
inner join firstCte on ReferredBy = U.id
where Active = 'False';
Note the window function max to avoid repeating the query in a sub-query.
Note joining the CTE to the Users table to perform the update.

SQL Server Cannot Update Table with Subqueries

I'm trying to update a temporary table called #deletedRecords which looks like this:
With the data from a table called log that looks like this:
The KeyValue in the log table is the same as the ID in #deletedRecords.
There is a column in #deletedRecords for every FieldName for any particular key value.
I tried to extract the values using the following query:
UPDATE #deletedRecords
SET PatientName = (SELECT ACL.OldValue WHERE ACL.FieldName = 'CptCode'),
ChargeNotes = (SELECT ACL.OldValue WHERE ACL.FieldName = 'ChargeNotes'),
Units = (SELECT ACL.OldValue WHERE ACL.FieldName = 'Units'),
ChargeStatusID = (SELECT ACL.OldValue WHERE ACL.FieldName = 'Units')
FROM Log ACL
JOIN #deletedRecords DR ON ACL.KeyValue = DR.ID
WHERE ACL.TableName = 'BillingCharge'
AND ACL.EventType = 'DELETE'
However when I run the query all of the columns to be updated in #deletedRecords are null. Can somebody please help explain what I'm missing?
Thanks in advance.
EDIT:
In response to #Yogesh Sharma's answer, I elected to use the CTE method. I would this that using the values from the CTE to join to additional tables and extract their values during the update.
e.g. The Log table doesn't contain an old value for the StatusName but it does contain the ChargeStatusID which could be used to join to another table that contains that information such as this table ChargeStatus:
Thus I modified #Yogesh Sharma's code to the following:
WITH cte AS
...
UPDATE d
SET d.PatientName = c.PatientName
, d.StatusName = cs.StatusName
FROM #deletedBillingChargeTemp d
JOIN cte c ON c.KeyValue = d.chargeID
JOIN ChargeStatus cs ON c.ChargeStatusID = cs.ChargeStatusID
However, once I add that secondary join, all of the updated values return to null as they were before #Yogesh Sharma's suggestions were implemented.
Your query does not work because the UPDATE is executed multiple times for each row in DR, considering only the conditions specified in the last three rows of your query (not the ones specified in the subqueries). The values that remain in the table are the ones that correspond to the ACL row used in the last execution (and the order of execution cannot be controlled). If for ACL row used in the last execution the subqueries return NULL, you will get a NULL result.
See the example in the https://learn.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql topic, where it says "The results of an UPDATE statement are undefined if the statement includes a FROM clause that is not specified in such a way that only one value is available for each column occurrence that is updated, that is if the UPDATE statement is not deterministic.".
You should rewrite your query like this:
UPDATE #deletedRecords
SET PatientName = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'CptCode' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
),
ChargeNotes = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'ChargeNotes' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
),
Units = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'Units' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
),
ChargeStatusID = (
SELECT ACL.OldValue FROM Log ACL
WHERE ACL.FieldName = 'Units' AND ACL.KeyValue = DR.ID
AND ACL.TableName = 'BillingCharge' AND ACL.EventType = 'DELETE'
)
FROM #deletedRecords DR
You would required to do some conditional aggregation for log table and do the JOINs in order to update the temporary table #deletedRecords records
So, the conditional approach could be achieve via CTE or Subquery
WITH cte AS
(
SELECT KeyValue,
MAX(CASE WHEN FieldName = 'CptCode' THEN OldValue END) PatientName,
MAX(CASE WHEN FieldName = 'ChargeNotes' THEN OldValue END) ChargeNotes,
...
FROM Log
WHERE TableName = 'BillingCharge' AND EventType = 'DELETE'
GROUP BY KeyValue
)
UPDATE d
SET d.PatientName = c.PatientName,
...
FROM #deletedRecords d
INNER JOIN cte c ON c.KeyValue = d.ID
The other way is to update your temporary table via correlation approach
UPDATE d
SET d.PatientName = (SELECT TOP 1 OldValue FROM Log WHERE KeyValue = d.ID AND
TableName = 'BillingCharge' AND EventType = 'DELETE' AND FieldName = 'CptCode'),
d.ChargeNotes= (SELECT TOP 1 OldValue FROM Log WHERE KeyValue = d.ID AND
TableName = 'BillingCharge' AND EventType = 'DELETE' AND FieldName = 'ChargeNotes'),
...
FROM #deletedRecords d
If your updated columns are NULL, these are it's possible causes:
Since you are doing a INNER JOIN, records might not be joining correctly by their joining column. Make sure both tables have the same values on the joining columns.
Since you are filtering in a WHERE clause, records might not fulfill your TableName and EventType filters. Make sure there are records that sucessfully INNER JOIN between them and they have the supplied TableName and EventType.
The values you are asigning are NULL. Make sure your subqueries return a not null value.
Table reference is off. When updating a table in SQL Server, always use the updating table alias if you are using one.
Use
UPDATE DR SET
YourColumn = Value
FROM
Log ACL
JOIN #deletedRecords DR ON -...
Instead of
UPDATE #deletedRecords SET
YourColumn = Value
FROM
Log ACL
JOIN #deletedRecords DR ON -...
Make sure you are NOT checking the variable table values on another batch, script or procedure. Variable tables scope are limited to current batch or procedure, while temporary tables remain as long as the session is alive.
Make sure that there isn't another statement that is setting those values as NULL after your update. Also keep an eye on your transactions (might not be commited or rolled back).

Merge table of linked table contains value sql

I need to merge a as seen below, but I only want to do this if the table dbo.Problem has the value 1 in the IsValid column. dbo.Problem is linked to dbo.delivery by dbo.Problem.ID = dbo.Delivery.IssueID.
merge [dbo].[Delivery] as D
using [ReportSvr].[Report].[dbo].[Delivery] as LinkD
on ( D.[ID] = LinkD.[ID])
WHEN NOT MATCHED BY TARGET
THEN INSERT(
[ID]
,[IssueID]
,[column2]
,[column3]
)
VALUES(
[ID]
,[IssueID]
,[column]
,[column]
);
GO
Is tried something like;
select d.IssueID, i.IsValid
From Delivery d
left join (Select* From Issue where IsValid = 1) i
on d.IssueID = i.ID
since first step must be to be able to the wanted rows.
Output:
IssueID IsValid
1 1
2 NULL
6 NULL
7 1
8 1
9 1
10 NULL
As you see I failed terribly....
I'm new (second day) to SQL, sorry if this is an easy task I should know about.
I hope you are able to help me.
Using an INNER JOIN will only show deliveries that have a matching record in the Issue table. An INNER JOIN will only show records where there's a match on the join field. In your attempt you're using a LEFT JOIN which will show everything in the left table, [Delivery] ("left" makes sense if you show the entire query on a single line) and matching records in the right table, [Issue].
SELECT d.IssueID, i.IsValid
FROM Delivery d
INNER JOIN Issue i
ON d.IssueID = i.ID
WHERE i.IsValid = 1
Thanks for helping me out, I managed to solved the problem with the help from Russell Fox. I just wanted to post the answer.
merge [dbo].[Delivery] as D
using(
select d.*, i.IsValid
From [ErrorReportSvr].[ErrorReport].[dbo].[Delivery] d
inner join (Select* From [ErrorReportSvr].[ErrorReport].[dbo].[Issue] where IsValid = 1) i
on d.IssueID = i.ID
) as LinkD
on ( D.[ID] = LinkD.[ID])
WHEN NOT MATCHED BY TARGET
THEN INSERT(
[ID]
,[IssueID]
,[column2]
,[column3]
)
VALUES(
[ID]
,[IssueID]
,[column2]
,[column3]
);
GO

Update records SQL?

First when I started this project seemed very simple. Two tables, field tbl1_USERMASTERID in Table 1 should be update from field tbl2_USERMASTERID Table 2. After I looked deeply in Table 2, there is no unique ID that I can use as a key to join these two tables. Only way to match the records from Table 1 and Table 2 is based on FIRST_NAME, LAST_NAME AND DOB. So I have to find records in Table 1 where:
tbl1_FIRST_NAME equals tbl2_FIRST_NAME
AND
tbl1_LAST_NAME equals tbl2_LAST_NAME
AND
tbl1_DOB equals tbl2_DOB
and then update USERMASTERID field. I was afraid that this can cause some duplicates and some users will end up with USERMASTERID that does not belong to them. So if I find more than one record based on first,last name and dob those records would not be updated. I would like just to skip and leave them blank. That way I wouldn't populate invalid USERMASTERID. I'm not sure what is the best way to approach this problem, should I use SQL or ColdFusion (my server side language)? Also how to detect more than one matching record?
Here is what I have so far:
UPDATE Table1 AS tbl1
LEFT OUTER JOIN Table2 AS tbl2
ON tbl1.dob = tbl2.dob
AND tbl1.fname = tbl2.fname
AND tbl1.lname = tbl2.lname
SET tbl1.usermasterid = tbl2.usermasterid
WHERE LTRIM(RTRIM(tbl1.usermasterid)) = ''
Here is query where I tried to detect duplicates:
SELECT DISTINCT
tbl1.FName,
tbl1.LName,
tbl1.dob,
COUNT(*) AS count
FROM Table1 AS tbl1
LEFT OUTER JOIN Table2 AS tbl2
ON tbl1.dob = tbl2.dob
AND tbl1.FName = tbl2.first
AND tbl1.LName = tbl2.last
WHERE LTRIM(RTRIM(tbl1.usermasterid)) = ''
AND LTRIM(RTRIM(tbl1.first)) <> ''
AND LTRIM(RTRIM(tbl1.last)) <> ''
AND LTRIM(RTRIM(tbl1.dob)) <> ''
GROUP BY tbl1.FName,tbl1.LName,tbl1.dob
Some data after I tested query above:
First Last DOB Count
John Cook 2008-07-11 2
Kate Witt 2013-06-05 1
Deb Ruis 2016-01-22 1
Mike Bennet 2007-01-15 1
Kristy Cruz 1997-10-20 1
Colin Jones 2011-10-13 1
Kevin Smith 2010-02-24 1
Corey Bruce 2008-04-11 1
Shawn Maiers 2016-08-28 1
Alenn Fitchner 1998-05-17 1
If anyone have idea how I can prevent/skip updating duplicate records or how to improve this query please let me know. Thank you.
You could check for and avoid duplicate matches using with common_table_expression (Transact-SQL)
along with row_number()., like so:
with cte as (
select
t.fname
, t.lname
, t.dob
, t.usermasterid
, NewUserMasterId = t2.usermasterid
, rn = row_number() over (partition by t.fname, t.lname, t.dob order by t2.usermasterid)
from table1 as t
inner join table2 as t2 on t.dob = t2.dob
and t.fname = t2.fname
and t.lname = t2.lname
and ltrim(rtrim(t.usermasterid)) = ''
)
--/* confirm these are the rows you want updated
select *
from cte as t
where t.NewUserMasterId != ''
and not exists (
select 1
from cte as i
where t.dob = i.dob
and t.fname = i.fname
and t.lname = i.lname
and i.rn>1
);
--*/
/* update those where only 1 usermasterid matches this record
update t
set t.usermasterid = t.NewUserMasterId
from cte as t
where t.NewUserMasterId != ''
and not exists (
select 1
from cte as i
where t.dob = i.dob
and t.fname = i.fname
and t.lname = i.lname
and i.rn>1
);
--*/
I use the cte to extract out the sub query for readability. Per the documentation, a common table expression (cte):
Specifies a temporary named result set, known as a common table expression (CTE). This is derived from a simple query and defined within the execution scope of a single SELECT, INSERT, UPDATE, or DELETE statement.
Using row_number() to assign a number for each row, starting at 1 for each partition of t.fname, t.lname, t.dob. Having those numbered allows us to check for the existence of duplicates with the not exists() clause with ... and i.rn>1
You could use a CTE to filter out the duplicates from Table1 before joining:
; with CTE as (select *
, count(ID) over (partition by LastName, FirstName, DoB) as IDs
from Table1)
update a
set a.ID = b.ID
from Table2 a
left join CTE b
on a.FirstName = b.FirstName
and a.LastName = b.LastName
and a.Dob = b.Dob
and b.IDs = 1
This will work provided there are no exact duplicates (same demographics and same ID) in table 1. If there are exact duplicates, they will also be excluded from the join, but you can filter them out before the CTE to avoid this.
Please try below SQL:
UPDATE Table1 AS tbl1
INNER JOIN Table2 AS tbl2
ON tbl1.dob = tbl2.dob
AND tbl1.fname = tbl2.fname
AND tbl1.lname = tbl2.lname
LEFT JOIN Table2 AS tbl3
ON tbl3.dob = tbl2.dob
AND tbl3.fname = tbl2.fname
AND tbl3.lname = tbl2.lname
AND tbl3.usermasterid <> tbl2.usermasterid
SET tbl1.usermasterid = tbl2.usermasterid
WHERE LTRIM(RTRIM(tbl1.usermasterid)) = ''
AND tbl3.usermasterid is null

Joining on varchar(50) foreign key slows query

I have this query which is pretty long, but adding a where clause to it, or joining on a string makes it take an extra 2 seconds to run. I can't figure out why.
Here's the query in full:
ALTER PROCEDURE [dbo].[RespondersByPracticeID]
#practiceID int = null,
#activeOnly bit = 1
AS
BEGIN
SET NOCOUNT ON;
select
isnull(sum(isResponder),0) as [Responders]
,isnull(count(*) - sum(isResponder),0) as [NonResponders]
,isnull((select
count(p.patientID)
from patient p
inner join practice on practice.practiceid = p.practiceid
inner join [lookup] l on p.dosing = l.lookupid and l.lookupid = 'da_ncd'
where
p.practiceID = isnull(#practiceID, p.practiceID)
and p.active = case #activeOnly when 1 then 1 else p.active end
) - (isnull(sum(isResponder),0) + isnull(count(*) - sum(isResponder),0)),0)
as [Undetermined]
from (
select
v.patientID
,firstVisit.hbLevel as startHb
,maxHbVisit.hblevel as maxHb
, case when (maxHbVisit.hblevel - firstVisit.hbLevel >= 1) then 1 else 0 end as isResponder
,count(v.patientID) as patientCount
from patient p
inner join visit v on v.patientid = v.patientid
inner join practice on practice.practiceid = p.practiceid
inner join [lookup] l on p.dosing = l.lookupid and l.lookupid = 'da_ncd'
inner join (
SELECT
p.PatientID
,v.VisitID
,v.hblevel
,v.VisitDate
FROM Patient p
INNER JOIN Visit v ON p.PatientID = v.PatientID
WHERE
v.VisitDate = (
SELECT MIN(VisitDate)
FROM Visit
WHERE PatientId = p.PatientId
)
) firstVisit on firstVisit.patientID = v.patientID
inner join (
select
p.patientID
,max(v.hbLevel) as hblevel
from Patient p
INNER JOIN Visit v ON p.PatientID = v.PatientID
group by
p.patientID
) MaxHbVisit on maxHbVisit.patientid = v.patientId
where
p.practiceID = isnull(#practiceID, p.practiceID)
and p.active = case #activeOnly when 1 then 1 else p.active end
group by
v.patientID
,firstVisit.hbLevel
,maxHbVisit.hblevel
having
datediff(
d,
dateadd(
day
,-DatePart(
dw
,min(v.visitDate)
) + 1
,min(v.visitDate)
)
, max(v.visitDate)
) >= (7 * 8) -- Eight weeks.
) responders
END
The line that slows it down is:
inner join [lookup] l on p.dosing = l.lookupid and l.lookupid = 'da_ncd'
Also, moving it to the where clause has the same effect:
where p.dosing = 'da_ncd'
Otherwise, the query runs almost instantly. >.<
Ah, sorry I figured it out. Patient.Dosing was set as allow nulls. I guess that made it a different sort of index.
For the record, even though the question is answered.
Usually things like this happen because the execution plan is changed. Compare the plans in query analyzer.
Another gotcha is data types - if p.dosing and l.lookupid differ - nvarchar vs. varchar, for example, can have a huge impact.
Try creating an index on that table, being sure to properly include that VARCHAR field in the list of fields.

Resources