SQL: IS NULL fails when trying to update values - sql

I am currently trying to update some table values and I am stuck with a particular instance.
The situation is that I have a main table DBO.MAIN_INTERACTIONS that I join with an external table. Based on the below WHERE values I would then like to update the main database values. KCMACustomer.DBO.DATA_EXT_GREEN_ENR is a table that is linked to one particular product and my idea was to select and update the table rows of MAIN that do not have a connection with this GREEN_ENR table. That's why I put the EXT.NUMBER_OF_ACCOUNTS IS NULL.
So the rows I am trying to retrieve will have no match on this JOIN condition 'MAIN.GENID = EXT.GENID'. Is this what makes my update query fail? and would there be a better way to get the rows that have no connection with KCMACustomer.DBO.DATA_EXT_GREEN_ENR (the main table values are all the same, so I can't differentiate there)
Extra info: the second query does work, probably because there is a succesful 'MAIN.GENID = EXT.GENID' join
UPDATE
MAIN
SET
STATE_CODE='S8',
STATE_NAME='Reminder 1',
OLD_STATE='S4',
MODIFIED_DT = #NOW
OUTPUT INSERTED.ID, 0, 'AUTO-STATE','business','Doc.expected -> Reminder 1',INSERTED.CAMPAIGNID, #NOW,'S4','S8' INTO KCMACustomer.DBO.DATA_EBW_FFC_LOG_STATES
FROM KCMACUSTOMER.DBO.MAIN_INTERACTIONS AS MAIN
JOIN KCMACustomer.DBO.DATA_EXT_GREEN_ENR AS EXT
ON MAIN.GENID = EXT.GENID
WHERE
MAIN.STATE_CODE='S4'
AND
MAIN.TYPE_DEMAND='S4'
AND
EXT.NUMBER_OF_ACCOUNTS IS NULL
AND
DATEDIFF(hh, MAIN.MODIFIED_DT, #NOW)>=168
AND
MAIN.PRODUCT IN (#HELLO4YOU, #COMFORT_PACK, #PREMIUM_PACK)
In this case the 'MAIN.GENID = EXT.GENID' will have a match and it does update the records I want
UPDATE
MAIN
SET
STATE_CODE='S8',
STATE_NAME='Reminder 1 eID',
OLD_STATE='S4',
MODIFIED_DT = #NOW
OUTPUT INSERTED.ID, 0, 'AUTO-STATE','business','Doc expected -> Reminder 1 eID',INSERTED.CAMPAIGNID, #NOW,'S4','S8' INTO KCMACustomer.DBO.DATA_EBW_FFC_LOG_STATES
FROM KCMACUSTOMER.DBO.MAIN_INTERACTIONS AS MAIN
JOIN KCMACustomer.DBO.DATA_EXT_GREEN_ENR AS EXT
ON MAIN.GENID = EXT.GENID
WHERE
MAIN.STATE_CODE = 'S4'
AND
DATEDIFF(hh, MAIN.MODIFIED_DT, #NOW)>=120
AND
MAIN.PRODUCT IN (#HELLO4YOU, #COMFORT_PACK, #PREMIUM_PACK)
AND
EXT.NUMBER_OF_ACCOUNTS IS NOT NULL
AND
MAIN.DEMAND_DT > '2020-05-27 00:00:00'

JOIN implies an INNER JOIN
The use case you described requires a LEFT JOIN.
You will need to change your query so that it uses
LEFT JOIN KCMACustomer.DBO.DATA_EXT_GREEN_ENR AS EXT
ON MAIN.GENID = EXT.GENID
I also suggest to test the NULL condition of the same column you use in the join condition. So instead of doing
EXT.NUMBER_OF_ACCOUNTS IS NULL
check if
EXT.GENID IS NULL
You are more familiar with your data so it might not have an impact on your query. But the record from MAIN could be linked to a record in EXT, but that record could have NUMBER_OF_ACCOUNTS NULL.
However, checking on the GENID of the EXT table would ensure that a link was not found when trying to find a matching record in main.

Related

Combine multiple updates with conditions, better merge?

A follow up question to SQL Server Merge: update only changed data, tracking changes?
we have been struggling to get an effective merge statement working, and are now thinking about only using updates, we have a very simple problem: Update Target from Source where values are different and record the changes, both tables are the same layout.
So, the two questions we have are: is it possible to combine this very simple update into a single statement?
UPDATE tbladsgroups
SET tbladsgroups.Description = s.Description,
tbladsgroups.action='Updated'
FROM tbladsgroups t
INNER JOIN tbladsgroups_staging s
ON t.SID = s.SID
Where s.Description <> t.Description
UPDATE tbladsgroups
SET tbladsgroups.DisplayName = s.DisplayName,
tbladsgroups.action='Updated'
FROM tbladsgroups t
INNER JOIN tbladsgroups_staging s
ON t.SID = s.SID
Where s.DisplayName <> t.DisplayName
....for each column.
Second question.
Can we record into a separate table/variable which record has been updated?
Merge would be perfect, however we cannot see which record is updated as the data returned from OUTPUT shows all rows, as the target is always updated.
edit complete merge:
M
ERGE tblADSGroups AS TARGET
USING tblADSGroups_STAGING AS SOURCE
ON (TARGET.[SID] = SOURCE.[SID])
WHEN MATCHED
THEN UPDATE SET
TARGET.[Description]=CASE
WHEN source.[Description] != target.[Description] THEN(source.[Description]
)
ELSE target.[Description] END,
TARGET.[displayname] = CASE
WHEN source.[displayname] != target.[displayname] THEN source.[displayname]
ELSE target.[displayname] END
...other columns cut for brevity
WHEN NOT MATCHED BY TARGET
THEN
INSERT (
[SID],[SamAccountName],[DisplayName],[Description],[DistinguishedName],[GroupCategory],[GroupScope],[Created],[Members],[MemberOf],[SYNCtimestamp],[Action]
)
VALUES (
source.[SID],[SamAccountName],[DisplayName],[Description],[DistinguishedName],[GroupCategory],[GroupScope],[Created],[Members],[MemberOf],[SYNCtimestamp],[Action]
)
WHEN NOT MATCHED BY SOURCE
THEN
UPDATE SET ACTION='Deleted'
You can use a single UPDATE with an OUTPUT clause, and use an INTERSECT or EXCEPT subquery in the join clause to check whether any columns have changed.
For example
UPDATE t
SET Description = s.Description,
DisplayName = s.DisplayName,
action = 'Updated'
OUTPUT inserted.ID, inserted.Description, inserted.DisplayName
INTO #tbl (ID, Description, DisplayName)
FROM tbladsgroups t
INNER JOIN tbladsgroups_staging s
ON t.SID = s.SID
AND NOT EXISTS (
SELECT s.Description, s.DisplayName
INTERSECT
SELECT t.Description, t.DisplayName
);
You can do a similar thing with MERGE, if you also want to INSERT
MERGE tbladsgroups t
USING tbladsgroups_staging s
ON t.SID = s.SID
WHEN MATCHED AND NOT EXISTS ( -- do NOT place this condition in the ON
SELECT s.Description, s.DisplayName
INTERSECT
SELECT t.Description, t.DisplayName
)
THEN UPDATE SET
Description = s.Description,
DisplayName = s.DisplayName,
action = 'Updated'
WHEN NOT MATCHED
THEN INSERT (ID, Description, DisplayName)
VALUES (s.ID, s.Description, s.DisplayName)
OUTPUT inserted.ID, inserted.Description, inserted.DisplayName
INTO #tbl (ID, Description, DisplayName)
;
We have similar needs when dealing with values in our Data Warehouse dimensions. Merge works fine, but can be inefficient for large tables. Your method would work, but also seems fairly inefficient in that you would have individual updates for every column. One way to shorten things would be to compare multiple columns in one statement (which obviously makes things more complex). You also do not seem to take NULL values into consideration.
What we ended up using is essentially the technique described on this page: https://sqlsunday.com/2016/07/14/comparing-nullable-columns/
Using INTERSECT allows you to easily (and quickly) compare differences between our staging and our dimension table, without having to explicitly write a comparison for each individual column.
To answer your second question, the technique above would not enable you to catch which column changed. However, you can compare the old row vs the new row (we "close" the earlier version of the row by setting a "ValidTo" date, and then add the new row with a "ValidFrom" date equal to today's date.
Our code ends up looking like the following:
INSERT all rows from the stage table that do not have a matching key value in the new table (new rows)
Compare stage vs dimension using the INTERSECT and store all matches in a table variable
Using the table variable, "close" all matching rows in the Dimension
Using the table variable, INSERT the new rows
If there's a full load taking place, we can also check for Keys that only exist in the dimension but not in the stage table. This would indicate those rows were deleted in the source system, and we mark them as "IsDeleted" in the dimension.
I think you may be overthinking the complexity, but yes. Your underlying update is a compare between the ads group and staging tables based on the matching ID in each query. Since you are already checking the join on ID and comparing for different description OR display name, just update both fields. Why?
groups description groups display staging description staging display
SomeValue Show Me SOME other Value Show Me
Try This Attempt Try This Working on it
Both Diff Changes Both Are Diff Change Me
So the ultimate value you want is to pull both description and display FROM the staging back to the ads groups table.
In the above sample, I have three samples which if based on matching ID present entries that would need to be changed. If the value is the same in one column, but not the other and you update both columns, the net effect is the one bad column that get updated. The first would ultimately remain the same. If both are different, both get updated anyhow.
UPDATE tbladsgroups
SET tbladsgroups.Description = s.Description,
tbladsgroups.DisplayName = s.DisplayName,
tbladsgroups.action='Updated'
FROM tbladsgroups t
INNER JOIN tbladsgroups_staging s
ON t.SID = s.SID
Where s.Description <> t.Description
OR s.DisplayName <> t.DisplayName
Now, all this resolution being said, you have redundant data and that is the whole point of a lookup table. The staging appears to always have the correct display name and description. Your tblAdsGroups should probably remove those two columns and always get them from staging to begin with... Something like..
select
t.*,
s.Description,
s.DisplayName
from
tblAdsGroups t
JOIN tblAdsGroups_Staging s
on t.sid = s.sid
Then you always have the correct description and display name and dont have to keep synching updates between them.

Oracle, update column in table 1, when related row does not exist in table 2

I have seen many answers that will update a table 1 when rows exist in table 2, but not one that works using a LEFT JOIN in selecting the rows, (for better performance). I have a solution to the update, but it will perform badly as it uses NOT IN.
So this SQL will update the tables as required, but looks to be very costly when run against large tables making it difficult to use.
update header
set status='Z'
where status='A'
and header.id not in (
select headerid
from detail
where detail.id between 0 and 9999999
);
Now I have a well performing query using a LEFT JOIN which returns the correct ids, but I have not been able to insert it into an update statement to give the same results.
The select statement is
select header.id
from header
left join detail on detail.headerid = header.id
where detail.headerid is null
and header.status='A'
So if I use this in the update statement as in:
update header
set status = 'Z'
where header.id = (
select header.id
from header
left join detail on detail.headerid = header.id
where detail.headerid is null and header.status='A'
)
Then I fail with:
ORA-01427: single-row subquery returns more than one row
I am expecting multiple header.id to be returned and want to update all these rows.
So I am still searching for a solution which will update the returned rows, using a well performing SQL select to return rows in table header, that do not have related rows in the detail table.
Any help would be appreciated, otherwise I will be left with the badly performing update.
Since you are expecting multiple header ID & the sub query returns multiple ID as you expected you should use IN
Try this
Update
header
Set status = 'Z'
Where
header.id IN (select
header.id
From
header
Left join
detail
On
detail.headerid = header.id
Where
detail.headerid is null
And
header.status='A')
I wouldn't put the condition on the outer table in the subquery. I am more comfortable writing this logic as:
update header h
set status = 'Z'
where not exists (select 1
from detail d
where d.headerid = h.id
) and
h.status = 'A';
If performance is an issue, indexes on detail(headerid) and header(status, id) and help.
typical, the next place I looked, I found an answer...
update header set status='Z' where not exists (select detail.headerid from detail where detail.headerid = header.id) and status = 'A'
Oh well, at least its here if anyone else wants to find it.
As the error states your subquery is returning more than one rows and you are using a = sign in your update query. = sign is not allowed if your query returns more than one records use either IN, NOT IN , EXISTS, NOT EXISTS as per your requirement

SQL - Execute two update queries at one time

I am trying to allocate a value from one table into another table using an update script.
This works fine for one record with a matching possibility
UPDATE Template_Survey
SET K2ref = tbAsset.AssetNo
FROM Template_Structure_Location INNER JOIN
Template_Survey ON
Template_Structure_Location.TEMPLATE_STRUCTURE_LOCATION_REF = Template_Survey.TEMPLATE_STRUCTURE_LOCATION_REF INNER JOIN
tbAsset ON Template_Structure_Location.RoomID = tbAsset.LocationID AND Template_Structure_Location.RoomID = tbAsset.LocationID
WHERE K2transferNote ='Reallocate - same no of spare items' and (tbAsset.AssetName = 'Archive or re-use') and (TEMPLATE_SURVEY_REF = 4369 or TEMPLATE_SURVEY_REF = 4405)
and (Template_Survey.K2ref = 0 OR
Template_Survey.K2ref IS NULL)
but if there are two records in Template_Survey table and in the tbAsset table that match the criteria, the first record found in the tbAsset table is used as the value against both records in the Template_Survey table.
When there is just one possible record I run a second query to update the tbAsset.AssetName value so that that record can no longer be found by the previous query eg
UPDATE tbAsset
SET tbAsset.AssetName = 'Reallocated to item in same location-needs update'
WHERE (AssetNo IN
(SELECT K2Ref
FROM Template_Survey)) and (tbAsset.AssetName = 'Archive or re-use')
How do I write something that will do the first query for one record and then the second query for the corresponding record before repeating the process so that a distinct record is found each time?
Thanks in advance. Sorry if it doesn't make much sense, it's really hard to explain.

SQL Update using value from join table

I tried using this sql to update a new column in a table from a value in table that already exists.
update "PROMOTION" t1
set "OFFER_CHAIN_ID" = poc."OFFER_CHAIN_ID"
from "PROMOTION_OFFER_CHAIN" poc
inner join "PROMOTION" on "PROMOTION"."ID" = poc."PROMOTION_ID"
What happened is that the first value of the join got replicated in all the subsequent entries. about a both tables. The original table has unique values all the values in the updated column are the same.
Eventually I used this SQL instead.
update "PROMOTION" t1
set "OFFER_CHAIN_ID" = poc."OFFER_CHAIN_ID"
from "PROMOTION_OFFER_CHAIN" poc
where
t1."ID" = poc."PROMOTION_ID"
This update works and duplicates all the data, 1000 unique elements in the original table, 1000 unique elements in the updated table.
Is this a bug, or is this the expected result?
SQL is behaving correctly. Your original query is:
update "PROMOTION" t1
--------^
set "OFFER_CHAIN_ID" = poc."OFFER_CHAIN_ID"
from "PROMOTION_OFFER_CHAIN" poc inner join
"PROMOTION"
-----------^
on "PROMOTION"."ID" = poc."PROMOTION_ID"
Note that the table PROMOTION is mentioned twice. Not good. So, the join takes place, producing lots of rows. Then there is no correlation to the t1 version of the table.
You don't mention the database you are using. In SQL Server, you would just do:
update p
set "OFFER_CHAIN_ID" = poc."OFFER_CHAIN_ID"
from "PROMOTION_OFFER_CHAIN" poc inner join
"PROMOTION" p
on p."ID" = poc."PROMOTION_ID";
Note the alias is used after the update (or table name with if there is no alias). Now the table is mentioned only once, so the update should behave as desired.

Compare Temp table and Main table to find matches

I have two tables #mtss table:
#mtss
( [MM],[YYYY],[month_Start],[month_Finish],[ProjectID],[ProjectedBillable],[ProjectedPayable],[ActualBilled],[ActualPaid],[Total_To_Bill],[Total_To_Pay])
tbl_Snapshot ([MM],[YYYY],[month_Start],[month_Finish],[ProjectID],[ProjectedBillable],[ProjectedPayable],[ActualBilled],[ActualPaid],[Total_To_Bill],[Total_To_Pay]
)
I need to compare two tables and find matches
if tbl_snapshot [MM] and [ProjectId] matches then delete record in tbl_snapshot and insert record from #mtts.
Thanks in advance.
Since you're on 2008, you can use MERGE to perform the as a single logical operation:
;merge into tbl_Snapshot s
using #mtss m on s.MM = m.MM and s.ProjectId = m.ProjectId
when matched then update
set
YYYY = m.YYYY,
month_Start = m.month_Start
/* Other columns as well, not going to type them all out */
;
This would also easily extend to other cases you may have to deal with, if the data only exists in one table and not the other, with addition match clauses.
Of course, thinking further, in this case a simple UPDATE would work also. A DELETE followed by an INSERT (where the deleted and inserted rows are related by a key) is the equivalent of an UPDATE.
Somethig like:
DELETE tbl_snapshot
FROM tbl_snapshot ss
INNER JOIN #mtss m ON m.MM = ss.MM AND m.ProjectId = ss.ProjectId
(I wrote the code directly to this editor so it might have errors, but it should give you an idea how to continue)