MERGE statement updates even if the data is not updated - sql-server

I have a merge statement in which I'd like to update my table rows in case any of the columns have a different value. But it seems like even though most of the rows in the source table have remained intact, the MERGE statement performs an UPDATE on at least counts what it does an UPDATE.
DECLARE #SummaryOfChanges TABLE(Change VARCHAR(50));
MERGE MyTarget AS TARGET
USING MySource AS SOURCE
ON (SOURCE.customeridHash = TARGET.Id)
WHEN MATCHED AND (TARGET.IsCompany <> SOURCE.company
OR TARGET.Gender <> SOURCE.gender
OR TARGET.BirthDate <> CONVERT(DATE, SOURCE.dateofbirth)
OR TARGET.ZipCode <> SOURCE.ZipCode
OR TARGET.City <> SOURCE.City
OR TARGET.WantsEmail <> (CASE WHEN SOURCE.noemail = 0 THEN 1 ELSE 0 END)
OR TARGET.WantsSMS <> (CASE WHEN SOURCE.nosms = 0 THEN 1 ELSE 0 END)
OR TARGET.WantsDM <> (CASE WHEN SOURCE.nodirectmarketing = 0 THEN 1 ELSE 0 END)
OR TARGET.WantsTM <> (CASE WHEN SOURCE.notelemarketing = 0 THEN 1 ELSE 0 END)
OR TARGET.HasEmail <> SOURCE.HasEmail
OR TARGET.HasMobilePhoneNumber <> SOURCE.HasMobilePhoneNumber
OR TARGET.HasPhoneNumber = SOURCE.HasPhoneNumber
OR TARGET.Created <> SOURCE.Created
OR TARGET.Updated <> SOURCE.changed)
THEN
UPDATE SET TARGET.IsCompany = SOURCE.company,
TARGET.Gender = SOURCE.gender,
TARGET.BirthDate = CONVERT(DATE, SOURCE.dateofbirth),
TARGET.ZipCode = SOURCE.ZipCode,
TARGET.City = SOURCE.City,
TARGET.WantsEmail = (CASE WHEN SOURCE.noemail = 0 THEN 1 ELSE 0 END),
TARGET.WantsSMS = (CASE WHEN SOURCE.nosms = 0 THEN 1 ELSE 0 END),
TARGET.WantsDM = (CASE WHEN SOURCE.nodirectmarketing = 0 THEN 1 ELSE 0 END),
TARGET.WantsTM = (CASE WHEN SOURCE.notelemarketing = 0 THEN 1 ELSE 0 END),
TARGET.HasEmail = SOURCE.HasEmail,
TARGET.HasMobilePhoneNumber = SOURCE.HasMobilePhoneNumber,
TARGET.HasPhoneNumber = SOURCE.HasPhoneNumber,
TARGET.Created = SOURCE.Created,
TARGET.Updated = SOURCE.changed
WHEN NOT MATCHED BY TARGET THEN
INSERT (
Id,
IsCompany,
Gender,
BirthDate,
ZipCode,
City,
WantsEmail,
WantsSMS,
WantsDM,
WantsTM,
HasEmail,
HasMobilePhoneNumber,
HasPhoneNumber,
Created,
Updated
)
VALUES (
SOURCE.customeridHash,
SOURCE.company,
SOURCE.gender,
CONVERT(DATE, SOURCE.dateofbirth),
SOURCE.ZipCode,
SOURCE.City,
(CASE WHEN SOURCE.noemail = 0 THEN 1 ELSE 0 END),
(CASE WHEN SOURCE.nosms = 0 THEN 1 ELSE 0 END),
(CASE WHEN SOURCE.nodirectmarketing = 0 THEN 1 ELSE 0 END),
(CASE WHEN SOURCE.notelemarketing = 0 THEN 1 ELSE 0 END),
SOURCE.HasEmail,
SOURCE.HasMobilePhoneNumber,
SOURCE.HasPhoneNumber,
SOURCE.Created,
SOURCE.changed
)
WHEN NOT MATCHED BY SOURCE THEN DELETE
OUTPUT $action INTO #SummaryOfChanges;
SELECT Change, COUNT(*) CountPerChange
FROM #SummaryOfChanges
GROUP BY Change;
I do some bookkeeping at the end of the update (the final SELECT) and it seems like almost all the rows that are not new, were updated. Is this a common behavior or is there really a value amongst my <> comparison for WHEN MATCHED AND... that is updated?
Update: As suggested by one of the comments, I wrote the following test to check whether my conditions trigger an update or not:
-- TEST MERGE
select count(*)
from MyTarget TARGET join MySource SOURCE on TARGET.Id=SOURCE.customeridHash
where TARGET.IsCompany <> SOURCE.company
OR TARGET.Gender <> SOURCE.gender
OR TARGET.BirthDate <> CONVERT(DATE, SOURCE.dateofbirth)
OR TARGET.ZipCode <> SOURCE.ZipCode
OR TARGET.City <> SOURCE.City
OR TARGET.WantsEmail <> (CASE WHEN SOURCE.noemail = 0 THEN 1 ELSE 0 END)
OR TARGET.WantsSMS <> (CASE WHEN SOURCE.nosms = 0 THEN 1 ELSE 0 END)
OR TARGET.WantsDM <> (CASE WHEN SOURCE.nodirectmarketing = 0 THEN 1 ELSE 0 END)
OR TARGET.WantsTM <> (CASE WHEN SOURCE.notelemarketing = 0 THEN 1 ELSE 0 END)
OR TARGET.HasEmail <> SOURCE.HasEmail
OR TARGET.HasMobilePhoneNumber <> SOURCE.HasMobilePhoneNumber
OR TARGET.HasPhoneNumber = SOURCE.HasPhoneNumber
OR TARGET.Created <> SOURCE.Created
OR TARGET.Updated <> SOURCE.changed;
I realised that this query returns the same number of updates. So it is somehow more about the condition that the MERGE statement. But I wonder how they trigger updates.

I think I found my own mistake, in the conditions I write:
OR TARGET.HasPhoneNumber = SOURCE.HasPhoneNumber
Which almost always renders true!

Related

T-SQL multiple WHERE clause if the string is empty

I have three fields I want to filter my query by. I want to query even if 1 or 2 of the fields are empty (passing an empty string). I can figure out 1 field, but once I add the other two, I do not get any results. Curly brackets are my form fields that get passed to the query.
IF '{Envelope Size}' <> ''
SELECT Tools.ToolNo,
Tools.[Name]
FROM Tools
LEFT JOIN
(
SELECT AdditionalInfo.OwnerID,
AdditionalInfo.UserDefined3,
AdditionalInfo.UserDefined4,
AdditionalInfo.UserDefined5,
AdditionalInfo.UserDefined10,
AdditionalInfo.UserDefined24
FROM AdditionalInfo
WHERE AdditionalInfo.ModuleID = 35
) AS EnvStyle ON Tools.ToolID = EnvStyle.OwnerID
WHERE EnvStyle.UserDefined24 LIKE '{Envelope Size}';
ELSE
SELECT Tools.ToolNo,
Tools.[Name]
FROM Tools
LEFT JOIN
(
SELECT AdditionalInfo.OwnerID,
AdditionalInfo.UserDefined3,
AdditionalInfo.UserDefined4,
AdditionalInfo.UserDefined5,
AdditionalInfo.UserDefined12
FROM AdditionalInfo
WHERE AdditionalInfo.ModuleID = 35
) AS EnvStyle ON Tools.ToolID = EnvStyle.OwnerID
WHERE EnvStyle.UserDefined3 <> 'Stationery'
AND EnvStyle.UserDefined4 = (CASE WHEN '{Env. Height}' = '' THEN 'NULL' ELSE '{Env. Height}' END)
AND EnvStyle.UserDefined5 = (CASE WHEN '{Env. Width}' = '' THEN 'NULL' ELSE '{Env. Width}' END)
AND EnvStyle.UserDefined12 = (CASE WHEN '{Flap Size}' = '' THEN 'NULL' ELSE '{Flap Size}' END);
I'm not exactly clear on what your data structure is and the curly braces are throwing me off a bit, but maybe something like this might work for you. You could change your filter conditions to use an OR with a CASE to force a match in those cases when the values are blank. Again, I'm not sure of your structure and the below is just following your example, but hopefully this might get you in the right direction.
IF '{Envelope Size}' <> ''
SELECT Tools.ToolNo,
Tools.[Name]
FROM Tools
LEFT JOIN
(
SELECT AdditionalInfo.OwnerID,
AdditionalInfo.UserDefined3,
AdditionalInfo.UserDefined4,
AdditionalInfo.UserDefined5,
AdditionalInfo.UserDefined10,
AdditionalInfo.UserDefined24
FROM AdditionalInfo
WHERE AdditionalInfo.ModuleID = 35
) AS EnvStyle ON Tools.ToolID = EnvStyle.OwnerID
WHERE EnvStyle.UserDefined24 LIKE '{Envelope Size}';
ELSE
SELECT Tools.ToolNo,
Tools.[Name]
FROM Tools
LEFT JOIN
(
SELECT AdditionalInfo.OwnerID,
AdditionalInfo.UserDefined3,
AdditionalInfo.UserDefined4,
AdditionalInfo.UserDefined5,
AdditionalInfo.UserDefined12
FROM AdditionalInfo
WHERE AdditionalInfo.ModuleID = 35
) AS EnvStyle ON Tools.ToolID = EnvStyle.OwnerID
WHERE EnvStyle.UserDefined3 <> 'Stationery'
AND (EnvStyle.UserDefined4 = {Env.Height}
OR 1 = CASE WHEN {Env.Height} = '' THEN 1 ELSE 0 END)
AND (EnvStyle.UserDefined5 = {Env.Width}
OR 1 = CASE WHEN {Env.Width} = '' THEN 1 ELSE 0 END)
AND (EnvStyle.UserDefined12 = {Flap Size}
OR 1 = CASE WHEN {Flap Size} = '' THEN 1 ELSE 0 END);

TSQL turning these multiple CASE queries into one query

I have the following CASE queries that return 1 if they find anything, and 0 otherwise. I would like to turn these into a single query that returns 1 if any of them are true, or 0 otherwise. How can I acomplish this?
SELECT CASE WHEN count(PLACE.CODE) > 0 THEN 1 ELSE 0 END
FROM PLACE
WHERE STYLE = 'RED'
AND RULES = 'NO'
SELECT CASE WHEN count(GARDEN.AREA) > 0 THEN 1 ELSE 0 END
FROM GARDEN
WHERE PLACE = 'GROUND'
AND MAZE = '1'
SELECT CASE WHEN count(place_area.AVAILABLE_AREA) > 0 THEN 1 ELSE 0 END
FROM PLACE_AREA as place_area
INNER JOIN USED_PLACE as used_place
ON used_place.COLOR = 'RED'
AND used_place.MAKE = 'INDUSTRY'
WHERE place_area.CODE = 'FLOOR'
AND place_area.DANCE = '0'
If I understand you correctly, you can union all the result and calculate the sum, if sum > 0 then there was at least one 1
Select Case When Sum(x.col) > 0 THEN 1 ELSE 0 END from
(
SELECT CASE WHEN count(PLACE.CODE) > 0 THEN 1 ELSE 0 END as col
FROM PLACE
WHERE STYLE = 'RED'
AND RULES = 'NO'
Union All
SELECT CASE WHEN count(GARDEN.AREA) > 0 THEN 1 ELSE 0 END as col
FROM GARDEN
WHERE PLACE = 'GROUND'
AND MAZE = '1'
Union All
SELECT CASE WHEN count(place_area.AVAILABLE_AREA) > 0 THEN 1 ELSE 0 END as col
FROM PLACE_AREA as place_area
INNER JOIN USED_PLACE as used_place
ON used_place.COLOR = 'RED'
AND used_place.MAKE = 'INDUSTRY'
WHERE place_area.CODE = 'FLOOR'
AND place_area.DANCE = '0'
) x
SELECT
CASE
WHEN
EXISTS(
SELECT TOP 1 1
FROM PLACE
WHERE STYLE = 'RED'
AND RULES = 'NO'
AND count(PLACE.CODE) > 0
)
OR EXISTS (
SELECT TOP 1 1
FROM GARDEN
WHERE PLACE = 'GROUND'
AND MAZE = '1'
AND count(GARDEN.AREA) > 0
)
OR EXISTS (
SELECT TOP 1 1
FROM PLACE_AREA as place_area
INNER JOIN USED_PLACE as used_place
ON used_place.COLOR = 'RED'
AND used_place.MAKE = 'INDUSTRY'
WHERE place_area.CODE = 'FLOOR'
AND place_area.DANCE = '0'
AND count(place_area.AVAILABLE_AREA) > 0
)
THEN 1
ELSE 0
END

COUNT(DISTINCT()) Return false value

I try to count total employee in my subquery table. Suppose the count result will return 0, but it keeps returning 1.
If I try to return only employee_id and month together, I didn't get any return value for may which is correct, but each time I try to count(distinct), I will get 1 as my return value. This is my sql
SELECT
count (distinct(CASE WHEN (x.month =5 and x.employee_id <> 0) THEN
x.employee_id
ELSE 0 END)) as test_may
FROM(
(
SELECT
h.month,
h.employee_id,
eb.employee_no,
ee.company_code,
h.amount,
h.year,
h.trx_type,
h.trx_code,
v.trx_desc,
h.frequency,
h.run_sequence
FROM
v_employee h,
v_trans v,
employee_emp ee,
employee eb
WHERE
( h.year = 2014 ) AND
( h.employee_id = ee.employee_id ) AND
( ee.employee_id = eb.employee_id ) AND
( h.employee_no = eb.employee_no ) AND
( h.trx_code = v.trx_code ) AND
( h.trx_type = v.trx_type ) AND
( v.company_code = ee.company_code OR v.company_code is NULL) AND
( h.trx_type IN ('S','B','N','O','A','D','L') )
)
)x,
employee_emp ee,
employee eb
WHERE
( x.employee_id = ee.employee_id ) AND
( ee.employee_id = eb.employee_id ) AND
( x.employee_no = eb.employee_no ) AND
( x.year = 2014 )
The count as you have it now will also count the 0 that is in the ELSE clause of the CASE expression. Even with DISTINCT still one instance of that 0 will be counted.
Remove the ELSE 0 so that you have NULL -- which is not counted:
count (distinct(CASE WHEN x.month =5 and x.employee_id <> 0
THEN x.employee_id
END)) as test_may
Note that with NULLIF you can shorten this expression to:
count (distinct(CASE x.month WHEN 5 THEN NULLIF(x.employee_id, 0) END)) as test_may
Your count will return the same count even in both cases. Because you are giving the value for Count function in both the cases.
Change from
count (distinct(CASE WHEN (x.month =5 and x.employee_id <> 0) THEN
x.employee_id
ELSE 0 END))
To
count (distinct(CASE WHEN (x.month =5 and x.employee_id <> 0) THEN
x.employee_id
ELSE NULL END))
Count will just count the values whether it is 0 or 100 as 1 & skip null values while counting. So in the Else condition NULL will give you correct output.

SQL Server CASE - WHEN - ELSE

I have 2 tables like this:
Table CT: Number, CtID, Date, CTIE
Table VT: Number, VtID, Quantities
And my code:
SELECT
MAX(CT.Date), MAX(CT.CtID), VT.VtID,
SUM(VT.Quantities) AS SumVT,
CASE
WHEN CT.CTIE = 0 THEN SUM(VT.Quantities)
ELSE 0
END AS IMPORT,
CASE
WHEN CT.CTIE = 1 THEN SUM(VT.Quantities)
ELSE 0
END AS EXPORT
FROM
CT
INNER JOIN
VT ON CT.Number = VT.Number
GROUP BY
VT.VtID, CT.CTIE
ORDER BY
VT.VtID
This code works fine but the result is not what I want. With some VtID that have both CTIE = 1 and CTIE = 1, SQL now returns 2 separate rows with same VtID, one for CTIE = 0 and one for CTIE = 1. But I need it to display only 1 row for each VtID instead of 2.
Remove CT.CTIE in the group by and place your case statements inside the aggregate function
SElECT
MAX(CT.Date), MAX(CT.CtID), VT.VtID, SUM(VT.Quantities) AS SumVT,
SUM(CASE WHEN CT.CTIE = 0 THEN VT.Quantities ELSE 0 END) AS IMPORT,
SUM(CASE WHEN CT.CTIE = 1 THEN VT.Quantities ELSE 0 END) AS EXPORT
FROM CT INNER JOIN VT ON CT.Number=VT.Number
GROUP BY VT.VtID
ORDER by VT.VtID

How to use conditional columns values in the same select statement?

I have something like
(COMPLEX_EXPRESSION_N stands for a long subquery)
select
ID_Operation,
FirstCheck = CASE WHEN (COMPLEX_EXPRESSION_1)= 0 then 0 else 1 end,
SecondCheck = CASE WHEN (COMPLEX_EXPRESSION_2)= 0 then 0 else 1 end,
ThirdCheck = CASE WHEN (COMPLEX_EXPRESSION_3)= 0 then 0 else 1 end,
AllChecksOk = Case WHEN
(FirstCheck + SecondCheck + Third CHeck = 3)
Then 'OK' Else 'No' End
from
AllOperationsTable
Is it possible to use FirstCheck, SecondCheck, ThirdCheck as I did in the AllChecksOk line?
I am not concerned about performance, this is something that is manually run once a day on a very small number of records, I just want to avoid to create views, tables or temporary tables and keep all in a single select statement.
As an altenrative I can do this, but it makes the query less readable (as I need to write twice every complex expression):
select
ID_Operation,
FirstCheck = CASE WHEN (COMPLEX_EXPRESSION_1)= 0 then 0 else 1 end,
SecondCheck = CASE WHEN (COMPLEX_EXPRESSION_2)= 0 then 0 else 1 end,
ThirdCheck = CASE WHEN (COMPLEX_EXPRESSION_3)= 0 then 0 else 1 end,
AllChecksOk = Case WHEN
(COMPLEX_EXPRESSION_1+ COMPLEX_EXPRESSION_2+
COMPLEX_EXPRESSION_3CHeck = 3) Then 'OK' Else 'No' End
from
AllOperationsTable
You can't reference a column alias in the select but you can use a CTE as below.
;WITH CTE AS
(
select
ID_Operation,
FirstCheck = CASE WHEN (COMPLEX_EXPRESSION_1)= 0 then 0 else 1 end,
SecondCheck = CASE WHEN (COMPLEX_EXPRESSION_2)= 0 then 0 else 1 end,
ThirdCheck = CASE WHEN (COMPLEX_EXPRESSION_3)= 0 then 0 else 1 end
from
AllOperationsTable
)
SELECT *,
AllChecksOk = Case WHEN
(COMPLEX_EXPRESSION_1+ COMPLEX_EXPRESSION_2+
COMPLEX_EXPRESSION_3CHeck = 3) Then 'OK' Else 'No' End
FROM CTE
You can also use CROSS APPLY to define the 3 column aliases then reference them in the main SELECT list as in this example.
Below is a derived table solution
SELECT
T.ID_Operation,
FirstCheck = CASE WHEN T.Expr1 = 0 THEN 0 ELSE 1 END,
SecondCheck = CASE WHEN T.Expr2 = 0 THEN 0 ELSE 1 END,
ThirdCheck = CASE WHEN T.Expr3 = 0 THEN 0 ELSE 1 END,
AllChecksOk = CASE WHEN T.Expr1 + T.Expr2 + T.Expr3 = 3 THEN 'OK' ELSE 'No' END
FROM
(
SELECT
ID_Operation,
Expr1 = (COMPLEX_EXPRESSION_1),
Expr2 = (COMPLEX_EXPRESSION_2),
Expr3 = (COMPLEX_EXPRESSION_3)
FROM
AllOperationsTable
) T
Personally, I find using CTE or derived tables a bit confusing for this purpose, as you have to nest things one level and think about the nesting impliciations. A much simpler approach (at least in my opinion) is to use APPLY (or standard SQL LATERAL in other RDBMS) to generate column expression aliases:
SELECT
ID_Operation,
FirstCheck,
SecondCheck,
ThirdCheck,
AllChecksOk = CASE
WHEN FirstCheck + SecondCheck + ThirdCheck = 3 THEN 'OK' ELSE 'NO'
END
FROM
AllOperationsTable
CROSS APPLY (
SELECT
FirstCheck = CASE WHEN COMPLEX_EXPRESSION_1 = 0 THEN 0 ELSE 1 END,
SecondCheck = CASE WHEN COMPLEX_EXPRESSION_1 = 0 THEN 0 ELSE 1 END,
ThirdCheck = CASE WHEN COMPLEX_EXPRESSION_1 = 0 THEN 0 ELSE 1 END
) t

Resources