update trigger updating all rows in target, not just updated? - sql

I have a very suspicious feeling that this update trigger is updating ALL rows on the target, not just those that satisfy the "update(shape)" test. Performance was fine 'till I added the second operation. A single spatial join occurs much faster, and this is a not a spatial index issue, as well, there are only a few records in this dataset.
ALTER TRIGGER [dbo].[GRSM_WETLANDS_Point_GIS_tbl_locations_update]
ON [dbo].[GRSM_WETLANDS_POINT]
after update
AS
BEGIN
SET NOCOUNT ON;
if UPDATE (shape)
update GRSM_WETLANDS_Point
set X_Coord =CASE WHEN u.shape.STDimension() = 2 THEN u.shape.STCentroid().STX ELSE u.shape.STEnvelope().STCentroid().STX END,
Y_Coord =CASE WHEN u.shape.STDimension() = 2 THEN u.shape.STCentroid().STY ELSE u.shape.STEnvelope().STCentroid().STY END
from inserted i
inner join GRSM_WETLANDS_POint u on i.GIS_Location_ID = u.GIS_Location_ID;
--second spatial operation
update GRSM_WETLANDS_Point
set QuadName = grsm.dbo.USGS_24K_TOPOMAP_BOUNDARIES.name
FROM GRSM_WETLANDS_POint i
inner join grsm.dbo.USGS_24K_TOPOMAP_BOUNDARIES
on i.GIS_Location_ID = i.GIS_Location_ID
WHERE (USGS_24K_TOPOMAP_BOUNDARIES.Shape.STContains(i.SHAPE) = 1) ;
end
Is my suspicion right?
Upated: Based on suggestion from Aaron...solves the fire on all rows issue.
update GRSM_WETLANDS_Point
set QuadName = grsm.dbo.USGS_24K_TOPOMAP_BOUNDARIES.name
FROM inserted i inner join GRSM_WETLANDS_POint u on i.GIS_Location_ID = u.GIS_Location_ID
left outer join grsm.dbo.USGS_24K_TOPOMAP_BOUNDARIES
on i.GIS_Location_ID = i.GIS_Location_ID
WHERE (USGS_24K_TOPOMAP_BOUNDARIES.Shape.STContains(i.SHAPE) = 1);

If Shape can't be NULL, a better way to see if it has changed is to check if the values in inserted and deleted are different. For example:
IF EXISTS
(
SELECT 1 FROM inserted AS i
INNER JOIN deleted AS d
ON i.GIS_Location_ID = d.GIS_Location_ID
WHERE i.Shape.STEquals(d.Shape) = 0
)
BEGIN
...
END
If Shape is nullable then you just have to add more conditions there to check, e.g.
WHERE
(
(i.Shape IS NULL AND d.Shape IS NOT NULL
OR (i.Shape IS NOT NULL AND d.Shape IS NULL)
OR (i.Shape.STEquals(d.Shape) = 0)
)
(You might not care if Shape has been updated to NULL, I'm just illustrating how to test for that case.)
Since the operation can occur on multiple rows, and this condition will only identify that at least one such update has occurred (but not that ALL rows meet the condition), it may be better to have your operations include similar criteria in the WHERE clause. In fact I think you can perform both updates in a single operation, e.g.
ALTER TRIGGER [dbo].[GRSM_WETLANDS_Point_GIS_tbl_locations_update]
ON [dbo].[GRSM_WETLANDS_POINT]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE p SET
X_Coord = CASE WHEN i.shape.STDimension() = 2
THEN i.shape.STCentroid().STX
ELSE i.shape.STEnvelope().STCentroid().STX
END,
Y_Coord = CASE WHEN i.shape.STDimension() = 2
THEN i.shape.STCentroid().STY
ELSE i.shape.STEnvelope().STCentroid().STY
END,
QuadName = COALESCE(b.name, p.QuadName)
FROM
dbo.GRSM_WETLANDS_Point AS p
INNER JOIN
inserted AS i
ON i.GIS_Location_ID = p.GIS_Location_ID
LEFT OUTER JOIN grsm.dbo.USGS_24K_TOPOMAP_BOUNDARIES AS b
ON b.Shape.STContains(i.Shape) = 1
WHERE EXISTS
(
SELECT 1 FROM inserted AS i2
INNER JOIN deleted AS d
ON i2.GIS_Location_ID = d.GIS_Location_ID
WHERE i2.GIS_Location_ID = i.GIS_Location_ID
AND i2.Shape.STEquals(d.Shape) = 0
-- ...and NULL handling if necessary
);
END
GO
In general, you seem to be having a lot of troubles implementing triggers, and make a lot of guesses about how the syntax should work. Have you considered forcing data updates to occur via stored procedures, where you can control all of this business logic but eliminate the complexity that the inserted and deleted pseudo tables add?

if UPDATE (shape) will fire even if the values don't change, if the column is present in the update statement it will fire
And you are not joining with INSERTED in your second update

Related

SQL Server trigger showing "asynchronous" behavior

I'm currently using this trigger in SQL server. Out of 16,000 orders, this is working 99.9% of the time. I've noticed that 0.1% of the time I get orders where the trigger does not run correctly and the UPDATE to the stock isn't made. I think what's happening is that the operation is getting locked while trying to find all the tables in the INNER JOIN and moves on to the second UPDATE operation while it's waiting for the first one to finish. It updates the stock_adjusted to 1 then comes back to the first UPDATE but now that stock_adjusted is 1 it no longer qualifies for the WHERE clause and doesn't update the stock. The delay was added to try and address the "async" issue.
I've read that SQL server is synchronous so I'm wondering if such a thing is even possible?
ALTER TRIGGER [dbo].[adjustStock]
ON [dbo].[payment]
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for trigger here
-- run query to adjust stock on product
UPDATE product WITH(ROWLOCK)
SET stock = stock - item.qty
FROM product
INNER JOIN item ON product.id = item.product
INNER JOIN cart WITH(NOLOCK) ON item.cart = cart.id
INNER JOIN inserted i ON cart.id = i.cart
WHERE item.stock_adjusted = 0 AND
product.open_box = 1;
WAITFOR DELAY '00:00:01';
-- mark line item as stock adjusted
UPDATE item
SET item.stock_adjusted = 1
FROM item
INNER JOIN cart WITH(NOLOCK) ON item.cart = cart.id
INNER JOIN product ON item.product = product.id
INNER JOIN inserted i ON cart.id = i.cart
WHERE item.stock_adjusted = 0 AND
product.open_box = 1;
END

Update a column after select where does not exists

How can i update a column after i have selected records which do not exist from another table.
Need to update Status column to 0 when there are certain records which do not exist.
SELECT *
FROM [BankingServicesReconciliations].[dbo].[Test1] p1
WHERE p1.[Age] is not null and
Not EXISTS (SELECT * FROM [BankingServicesReconciliations].[dbo].[Test2] p2 WHERE ( p1.[Surname] =p2.[Surname]) )
Your code basically translates to an update:
UPDATE [BankingServicesReconciliations].[dbo].Test1
SET status = 0
WHERE Age is not null and
NOT EXISTS (SELECT 1
FROM [BankingServicesReconciliations].[dbo].[Test2] p2
WHERE p2.[Surname] = Test1.[Surname]
);
The only subtlety is that SQL Server doesn't allow you to declare an alias in the UPDATE clause. You could use a FROM clause but that hardly seems necessary in this case.
I prefer LEFT JOIN:
UPDATE p1
SET STATUS = 0
FROM [BankingServicesReconciliations].[dbo].[Test1] p1 LEFT JOIN
[BankingServicesReconciliations].[dbo].[Test2] p2 ON p1.[Surname] = p2.[Surname] and p2.[Age] IS NOT NULL
WHERE p1.[Age] IS NOT NULL
AND p2.[Surname] IS NULL
This one is way much cleaner and understandable than EXISTS approach and, you better believe me, is WAY MUCH FASTER!

Updating a column that is within a view

I have this simple view set in place which joins 3 different tables and displays data within my program. I noticed however, that I can update columns from the table PRODCODE like so:
update PRODCODE_VW set MAXGH = 5.00 where ORIGREC = 114406 --RETURNS 1 UPDATED
And it will update 1 record. However, when I do an update on ExpectedLevels, it will update 0 rows. I'm assuming this is because it is left joined. Is there a way to get around this by only altering the way the view is set up and not the update statement?
update PRODCODE_VW set EMIN = 5.00 where ORIGREC = 114406 --RETURNS 0 UPDATED
This is the view I have set in place:
IF EXISTS (SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_NAME = N'PRODCODE_vw')
DROP VIEW PRODCODE_vw
GO
CREATE VIEW dbo.PRODCODE_vw
AS
SELECT PRODCODE.STUFF,PRODCODE.ORIGREC,PRODCODE.PRODCODE,PRODCODE.PRODNAM,
C1JMASTER.C1JTYPE, EXPECTEDLEVELS.EMIN, EXPECTEDLEVELS.EMAX
FROM PRODCODE (NOLOCK)
LEFT JOIN C1JMASTER (NOLOCK)
ON PRODCODE.c1jcode = C1JMASTER.c1jcode
LEFT JOIN EXPECTEDLEVELS
ON PRODCODE.PRODCODE = EXPECTEDLEVELS.PRODCODE
GO
When you select ORIGREC = 114406 from the view, is there any value for EMIN, or it is null? Most likely, the ORIGREC 114406 does not have a matching record in the left joined table. Your update statement effectively filters on WHERE ORIGREC = 114406 AND PRODCODE.PRODCODE = EXPECTEDLEVELS.PRODCODE
You can target the left joined tables as long as your affected columns are all sourced from the same table in the view. However, if the left join would not return a EXPECTEDLEVELS row for the ORIGREC, your rows updated will be zero, because there is no matching row in the target table.

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

DELETE statement issues within a trigger definition

I have created an insert/update trigger that is designed to update information in a different table based on the data being inserted. The last thing the trigger does (or is supposed to do) is remove all data from a target table with conditions that may have changed during the insert portions of the trigger.
Everything appears to be working with the trigger except the final DELETE statement. It is executing the DELETE statement, but not following any of the conditions in the where clause. It simply deletes everything in the table.
I have even tried changing the NOT IN in the where clause to IN, and it still does the same. I have isolated the DELETE statement and tested outside the trigger and it works fine (using the same variables and subqueries).
Am I missing something with the behavior of a trigger?
Here comes the code:
ALTER TRIGGER [dbo].[cust_trgr_profile_attribute]
ON [dbo].[port_module_instance_setting]
AFTER INSERT, UPDATE
AS
DECLARE #ModuleId INT=449,
#MatchGroupModSetting VARCHAR(50) = 'AttributeGroup',
#FilterGroupModSetting VARCHAR(50) = 'FilterAttributeGroup',
#MatchAttributes TABLE (attribute_id INT),
#FilterAttributes TABLE (attribute_id INT)
INSERT INTO #MatchAttributes
SELECT DISTINCT camatch.attribute_id
FROM inserted I
JOIN core_attribute camatch ON I.value = CONVERT(VARCHAR(10), camatch.attribute_group_id)
JOIN port_module_instance pmi ON I.module_instance_id = pmi.module_instance_id
AND pmi.module_id=#ModuleId
WHERE I.name like #MatchGroupModSetting+'_'
INSERT INTO #FilterAttributes
SELECT DISTINCT cafilter.attribute_id
FROM inserted I
JOIN core_attribute cafilter ON I.value = CONVERT(VARCHAR(10), cafilter.attribute_group_id)
JOIN port_module_instance pmi ON I.module_instance_id = pmi.module_instance_id
AND pmi.module_id=#ModuleId
WHERE I.name=#FilterGroupModSetting
IF ((SELECT COUNT(*) FROM #MatchAttributes) > 0 OR (SELECT COUNT(*) FROM #FilterAttributes) > 0)
BEGIN
IF (SELECT COUNT(*) FROM #MatchAttributes) > 0
BEGIN
UPDATE cpa
SET cpa.[required]=0
FROM cust_profile_attribute cpa
JOIN #MatchAttributes ma ON cpa.attribute_id = ma.attribute_id
END
IF (SELECT COUNT(*) FROM #FilterAttributes) > 0
BEGIN
UPDATE cpa
SET cpa.[required]=0
FROM cust_profile_attribute cpa
JOIN #FilterAttributes fa ON cpa.attribute_id=fa.attribute_id
END
DELETE FROM cust_profile_attribute
WHERE attribute_id NOT IN (SELECT distinct ca.attribute_id
FROM core_attribute ca
JOIN port_module_instance_setting inst ON CONVERT(VARCHAR(10),ca.attribute_group_id) = inst.value
JOIN port_module_instance modinst ON inst.module_instance_id = modinst.module_instance_id
AND modinst.module_id = #ModuleId
WHERE inst.name like #MatchGroupModSetting + '_'
OR inst.name like #FilterGroupModSetting)
END
I have found a flaw in my fundamental logic of how the trigger works. I have now refined my understanding of what is going on and able to articulate more information for others to help. Rather than try to completely transform the original post, I thought it better to post the new info here.
The basic idea is that the port_module_instance_setting table stores a string in the that represents a setting (in this specific case, the conditions are set so that the value field will always be a number). What I am trying to accomplish is that when the value field is updated in one of these specific "settings", everything in the cust_profile_attribute table that is referenced by the old value gets deleted. In this context, the value field of port_module_instance_setting is a numeric varchar value that directly references the attribute_group_id of core_attribute. Please don't comment on best practices regarding referencing tables using different data types, as I have no control over the actual table structure :)
Everything in the trigger functions properly, except the DELETE statement at the end. It isn't doing anything. Any ideas on why it isn't deleting the attributes I want it to?
I have changed the DELETE statement as follows to reference the pre-updated valuefield.
DELETE FROM cust_profile_attribute
WHERE attribute_id IN(SELECT ISNULL(attribute_id,-1) FROM deleted d
JOIN core_attribute ca ON ca.attribute_group_id= CONVERT(INT,d.value))