SQL MERGE statement with conditional MATCH clause - sql

As you can see in the code below the MERGE statement is exactly the same except based on the IF statement I'm changing the column that will be merged into the target table. Is there a better way of writing this? As the number of #Event's grow I don't want to be copying and pasting more and more of these as its not maintainable.
IF #Event = 1
BEGIN
MERGE INTO SomeTable
USING (
-- Some Query
) AS A ON SomeTable.Id = A.Id
WHEN MATCHED THEN
UPDATE SET SomeTable.Column1 = SomeTable.Column1 + 1;
END
ELSE IF #Event = 2
BEGIN
MERGE INTO SomeTable
USING (
-- Some Query
) AS A ON SomeTable.Id = A.Id
WHEN MATCHED THEN
UPDATE SET SomeTable.Column2 = SomeTable.Column2 + 1;
END

It might be a matter of debate whether one statement is better than two. But, you can combine these:
MERGE INTO SomeTable
USING (
-- Some Query
) AS A ON SomeTable.Id = A.Id
WHEN MATCHED THEN
UPDATE SET SomeTable.Column1 = (CASE WHEN #Event = 1
THEN SomeTable.Column1 + 1
ELSE SomeTable.Column1
END),
SomeTable.Column2 = (CASE WHEN #Event = 2
THEN SomeTable.Column2 + 1
ELSE SomeTable.Column2
END);

You dont really need a Merge statement for this and yes you can write your UPDATE statement a bit more dynamically. something like this.....
UPDATE ST
SET ST.Column1 = CASE WHEN #Event = 1
THEN ST.Column1 + 1
ELSE ST.Column1 END
,ST.Column2 = CASE WHEN #Event = 2
THEN ST.Column2 + 1
ELSE ST.Column2 END
FROM SomeTable ST
INNER JOIN
(
-- Some Query
) AS A
ON ST.Id = A.Id
Also you might want to read Use Caution with SQL Server's MERGE Statement as well though.

Related

How can I improve this conditional UPDATE query?

I have a table t with several columns, let's name them a, b and c. I also have a state column which indicates the current state. There is also an id column.
I want to write the following query: update column a always, but b and c only if the application state is still equal to the database state. Here, the state column is used for optimistic locking.
I wrote this query as following:
UPDATE t
SET a = $a$,
b = (CASE WHEN state = $state$ THEN $b$ ELSE b END),
c = (CASE WHEN state = $state$ THEN $c$ ELSE c END)
WHERE id = $id$ AND
(
a != $a$ OR
b != (CASE WHEN state = $state$ THEN $b$ ELSE b END) OR
c != (CASE WHEN state = $state$ THEN $c$ ELSE c END)
)
Here, $id$, $a$, ... are input variables from the application. The second part of the WHERE clause is to avoid updates which do not effectively update anything.
This query works as expected, but is very clumsy. I am repeating the same condition several times. I am looking for a way to rewrite this query in a more elegant fashion. If this was a simple SELECT query, I could do something with a LATERAL JOIN, but I cannot see how to apply this here.
How can I improve this query?
Split the query in two:
UPDATE t
SET a = $a$
WHERE id = $id$
UPDATE t
SET b = $b$,
c = $c$
WHERE id = $id$ AND
state = $state$
If you need atomicity, wrap in a transaction.
This seems a bit cleaner(untested):
WITH src AS (
SELECT $a$ AS a
, (CASE WHEN state = $state$ THEN $b$ ELSE b END) AS b
, (CASE WHEN state = $state$ THEN $c$ ELSE c END) AS c
FROM t
WHERE id = $id$
)
UPDATE t dst
SET a=src.a, b=src.b, c=src.c
FROM src
WHERE dst.id = src.id
AND (src.a, src.b, src.c) IS DISTINCT FROM (dst.a, dst.b, dst.c)
;
EDIT: It Took me a while to realize my fault here: The question obviously targets at a single update, while my answer tried to update many rows. However, if you need to execute this Update for a set of rows you could:
Insert the needed parameters in a temporary table
Join that table within the "t2" subquery
Select it's columns (e.g. tempTable.b As tempB)
Replace the Parameters (e.g. $b$ -> t2.tempB)
.
UPDATE t
SET a=source.a,
b=source.b,
c=source.c
FROM
(
SELECT
id,
a,
(CASE WHEN UpdateCondition THEN $b$ ELSE b END) AS b,
(CASE WHEN UpdateCondition THEN $c$ ELSE c END) AS c
FROM
(
SELECT state = $state$ As UpdateCondition, * FROM t
) As t2
WHERE
id = $id$ AND
(
a != $a$ OR
b != (CASE WHEN UpdateCondition THEN $b$ ELSE b END) OR
c != (CASE WHEN UpdateCondition THEN $c$ ELSE c END)
) AS source
WHERE t.id=source.id;
The Sub query for t2 gives you your state Condition and executes the calculation for it only once per row.
The subquery for "source" gives you the mapped values and filters those without changes.
The only filter you need is on ID = $id
The case statement says don't change it in the update if the state doesn't match, so you don't need to filter it.
EDIT
where Id = $id and a !=$a
Or (state = $state and (b !=b or c!= $c))
If you do any more than that then"always update a" will not necessary be true.
3rd attempt checks for the possibility of a remaining the same, but b or c updating.

Part of the update is not working in Procedure

I have written two update statement in my procedure. For some strange reason first update statement only update some of the record (basically I update more than 100,000 rows). So second update statement works fine all the time. I brainstormed but procedure completes successfully but I am not getting what is the issue. Is there any way I can perform a validation check like how many got updated and and how many not?
1st update statement(Which update only some records sometimes)
UPDATE /*+PARALLEL(A,10,2)*/ VV_ACT_CALL_DET_DIS_EXTRACT A SET PRIORITY =
( SELECT P.PRIORITY
FROM VV_ACT_CALL_DET_DIS P
WHERE P.CALL_ID = A.CALL_ID
AND P.PRODUCT_ID = A.PRODUCT_ID
AND P.IS_DELETED = A.IS_DELETED
AND ROWVAL = 1 )
WHERE EXTRACT_STATUS = 'PENDING'
AND EXISTS
( SELECT B.PRIORITY
FROM VV_ACT_CALL_DET_DIS B
WHERE B.CALL_ID=A.CALL_ID
AND B.PRODUCT_ID = A.PRODUCT_ID
AND B.IS_DELETED = A.IS_DELETED );
2nd Update statement which updates record successfully all the time though condition is same as above
UPDATE /*+PARALLEL(A,10,2)*/ VV_ACT_CALL_DET_DIS_EXTRACT A SET TYPE =
( SELECT P.TYPE_VAL
FROM VV_ACT_CALL_DET_DIS P
WHERE P.CALL_ID = A.CALL_ID
AND P.PRODUCT_ID = A.PRODUCT_ID
AND P.IS_DELETED = A.IS_DELETED
AND ROWVAL = 1 )
WHERE EXTRACT_STATUS = 'PENDING'
AND EXISTS
( SELECT B.TYPE_VAL
FROM VV_ACT_CALL_DET_DIS B
WHERE B.CALL_ID = A.CALL_ID
AND B.PRODUCT_ID = A.PRODUCT_ID
AND B.IS_DELETED = A.IS_DELETED );
You can query the updated row count using SQL%rowcount right after the update statement (before the commit), e.g.:
update ....
if sql%rowcount <> nnn then --or = 0 or ...
raise_application_error(-20001, 'invalid number of rows updated: ' || sql%rowcount);
end if;
Regarding the 'why it is not updating issue': without seeing that actual data it is hard to tell. Can you give us an example data set? Is it possible, that the first statement updates the same number of rows, but data does not change (priority remains the same)?

Table type of variable returning blank cells after update sql 2008

I have a select query returning two results and what I want is to save them in a table type of variable. This is how I am doing it:
declare #CompletedTotalValues table (CMedian int, CPerc int);
update #CompletedTotalValues set CMedian = t.CMed, CPercentile = t.CPerc
from(
Select CMed = dbo.median(case when cr.Priority = 1 then cr.Days else null end),
CPerc = dbo.Percentile90(case when cr.Priority = 1 then cr.Days else null end)
from A a inner join B b on b.Id = a.Id
where b.StatusId = 3
) t;
Here, when I run the subquery, I see CMed is 25 and CPerc is 43. However, when I execute Select * from #CompletedTotalValues, it is returning both column blank (no value it shows). Where is my mistake? Any advice would be appreciated
Why not just insert the data in the first place?
DECLARE #CompletedTotalValues TABLE (CMedian INT, CPerc INT);
INSERT #CompletedTotalValues (CMedian, CPerc)
SELECT CMed = dbo.median(CASE WHEN cr.Priority = 1 THEN cr.Days END),
CPerc = dbo.Percentile90(CASE WHENn cr.Priority = 1 THEN cr.Days END)
FROM A
INNER JOIN B ON b.Id = a.Id
WHERE b.StatusId = 3;
If I go by the query you have shared, you are trying to update without even inserting any data in the table variable at first place. Is it?

How to update more than 1000 records in SQL Server by LOOP?

I need to update more than 1000 records. The values are in another table.
I tried with this query:
Update tblEnrollment
SET TrOOPCurrentYrBalanceAmt = tr.TrOOPCurrentYrBalanceAmt
from tblTrOOPbalance tr
join tblMember mem on tr.MedicareNumber = mem.MANumber
join tblEnrollment enr on mem.MemberID = enr.MemberID
where enr.EnrollmentID IN ('16823', '16828')
but I get an error :
Msg 512, Level 16, State 1, Procedure tblEnrollment_UpdateTrigger, Line 9
Subquery returned more than 1 value.
This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I guess I can't update more than 1 with the query. How can I update multiple records?
Thanks,
Because the table tblenrollment has a trigger that prevents updating multiple records, I have to use LOOP to update a record one by one.
Could anyone help how to write LOOP query please?
Of cource, you can update multiple records!!!
It seems that you have other code in your procedure tblEnrollment_UpdateTrigger and there is a problem with subquery. Please check if you do not have similar cases where sabquery will be return more than 1 record:
where column = (select column2 from table)
update tab set column = (select column2 from table)
select #variable = (select column2 from table)
About UPDATE statement, I think will be better use something like that:
update enr
set enr.TrOOPCurrentYrBalanceAmt = tr.TrOOPCurrentYrBalanceAmt
from tblEnrollment enr
join tblMember mem on enr.MemberID = mem.MemberID
join tblTrOOPbalance tr on mem.MANumber = tr.MedicareNumber
where enr.EnrollmentID in ('16823','16828')
I believe this update is better. Does tblEnrollment have a primary key or unique column?
update tblEnrollment
set TrOOPCurrentYrBalanceAmt = (
select tr.TrOOPCurrentYrBalanceAmt
from tblTrOOPbalance tr
where tr.MemberID = tblEnrollment.MemberID
)
where EnrollmentID in ('16823', '16828')
Here's a hack that might be useful for a one-time fix:
declare #done bit = 0;
while #done = 0 begin
update top(1) tblEnrollment
set TrOOPCurrentYrBalanceAmt = (
select tr.TrOOPCurrentYrBalanceAmt
from tblTrOOPbalance tr
where tr.MemberID = tblEnrollment.MemberID
)
where EnrollmentID in ('16823', '16828')
and coalesce(TrOOPCurrentYrBalanceAmt, -999999) <>
coalesce((
select tr.TrOOPCurrentYrBalanceAmt
from tblTrOOPbalance tr
where tr.MemberID = tblEnrollment.MemberID
), -999999);
set #done = case when ##rowcount = 0 then 1 else 0 end
end
You can inspect and debug with this:
select
TrOOPCurrentYrBalanceAmt as TrOOPCurrentYrBalanceAmt1,
(
select tr.TrOOPCurrentYrBalanceAmt
from tblTrOOPbalance tr
where tr.MemberID = tblEnrollment.MemberID
) as TrOOPCurrentYrBalanceAmt2
from tblEnrollment
where EnrollmentID in ('16823', '16828')
and coalesce(TrOOPCurrentYrBalanceAmt, -999999) <>
coalesce((
select tr.TrOOPCurrentYrBalanceAmt
from tblTrOOPbalance tr
where tr.MemberID = tblEnrollment.MemberID
), -999999);

Update statement across multiple fields

I have a table structure (particularly not fond of) where I need to update multiple colums in one row.
Source Table
ID TotalIntake TotalOutput Day
1 1000 500 0
1 1500 1000 1
2 100 200 0
Destination Table (should look like this after update)
ID TotalIntake_0 TotalIntake_1 TotalOutput_0 TotalOutput_1
1 1000 1500 500 1000
2 100 NULL 200 NULL
I tried to use Case when statement, but for some reason it only updates one of each columns and not all the columns across
UPDATE e
Set e.TotalIntake_0 = Case WHEN i.ProcedureDay = 0 Then i.TotalIntake End
,e.TotalIntake_1 = Case WHEN i.ProcedureDay = 1 Then i.TotalIntake End
,e.TotalOutput_0 = Case WHEN i.ProcedureDay = 0 Then i.TotalOutput End
,e.TotalOutput_1 = Case WHEN i.ProcedureDay = 1 Then i.TotalOutput End
FROM DestinationTable e LEFT JOIN SourceTable i ON e.id = i.id
Any ideas greatly appreciated!
Thanks!
Updates on a row are not cumulative. So, only one matching row is used for the update, and you don't know which one. You can do what you want but you need to summarize the rows first and then do the update:
UPDATE e
Set TotalIntake_0 = i.TotalIntake_0,
TotalIntake_1 = TotalIntake_1,
TotalOutput_0 = TotalOutput_0,
TotalOutput_1 = TotalOutput_1
FROM DestinationTable e LEFT JOIN
(select i.id,
TotalIntake_0 = max(Case WHEN i.ProcedureDay = 0 Then i.TotalIntake End),
TotalIntake_1 = max(Case WHEN i.ProcedureDay = 1 Then i.TotalIntake End),
TotalOutput_0 = max(Case WHEN i.ProcedureDay = 0 Then i.TotalOutput End),
TotalOutput_1 = max(Case WHEN i.ProcedureDay = 1 Then i.TotalOutput End)
from SourceTable i
group by i.id
) i
ON e.id = i.id ;
The documentation that describes this is a bit hard to parse:
Use caution when specifying the FROM clause to provide the criteria
for the update operation. 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. For example, in the UPDATE statement in the
following script, both rows in Table1 meet the qualifications of the
FROM clause in the UPDATE statement; but it is undefined which row
from Table1 is used to update the row in Table2.
I've highlighted the relevant part.
Wouldn't it be simpler to do this in multiple updates?
UPDATE e
SET e.TotalIntake_0 = i.TotalIntake
FROM DestinationTable e
LEFT JOIN SourceTable i ON e.id = i.id
WHERE i.Day = 0
UPDATE e
SET e.TotalIntake_1 = i.TotalIntake
FROM DestinationTable e
LEFT JOIN SourceTable i ON e.id = i.id
WHERE i.Day = 1
Use transactions if necessary.