Update Trigger Can't Reference Deleted Table - sql

I am trying to reference the deleted table as well as the inserted table when the AFTER UPDATE trigger gets fired. The following works when trying to call the inserted table.
CREATE TRIGGER FLXUpdateTrigger ON WORK_ORDER
AFTER UPDATE
FROM
(WORK_ORDER
LEFT JOIN PART ON WORK_ORDER.PART_ID = PART.ID
LEFT JOIN PART_SITE ON WORK_ORDER.PART_ID = PART_SITE.PART_ID
JOIN Inserted AS i ON i.rowid = WORK_ORDER.rowid)
But then when I try and do the same exact thing for the delete table like so.
FROM (WORK_ORDER
LEFT JOIN PART ON WORK_ORDER.PART_ID = PART.ID
LEFT JOIN PART_SITE ON WORK_ORDER.PART_ID = PART_SITE.PART_ID
JOIN deleted AS d ON d.rowid = WORK_ORDER.rowid)
It just displays the new data that was inserted for both tables. Am I joining these tables incorrectly? I am unable to just call SELECT * FROM DELETED because I do not need all of the columns from that tuple.

You defined your trigger as "AFTER UPDATE", which means by the time your trigger has processed the DELETED rows are no longer in WORK_ORDER at all!
Well, some might be, if a row is counted as UPDATED then it's in both the DELETED and INSERTED table with the same ID, so you might get some, but you won't get all.
To solve this, reference the data exclusively through DELETED because all fields are there, or, if you need new values, you could do a UNION ALL split by in DELETED and NOT IN INSERTED vs. in DELETED and IN INSERTED.
And BTW, you can select specific fields from DELETED, you don't need to select the whole row.

Related

Request optimisation

I have two tables, on one there are all the races that the buses do
dbo.Courses_Bus
|ID|ID_Bus|ID_Line|DateHour_Start_Course|DateHour_End_Course|
On the other all payments made in these buses
dbo.Payments
|ID|ID_Bus|DateHour_Payment|
The goal is to add the notion of a Line in the payment table to get something like this
dbo.Payments
|ID|ID_Bus|DateHour_Payment|Line|
So I tried to do this :
/** I first added a Line column to the dbo.Payments table**/
UPDATE
Table_A
SET
Table_A.Line = Table_B.ID_Line
FROM
[dbo].[Payments] AS Table_A
INNER JOIN [dbo].[Courses_Bus] AS Table_B
ON Table_A.ID_Bus = Table_B.ID_Bus
AND Table_A.DateHour_Payment BETWEEN Table_B.DateHour_Start_Course AND Table_B.DateHour_End_Course
And this
UPDATE
Table_A
SET
Table_A.Line = Table_B.ID_Line
FROM
[dbo].[Payments] AS Table_A
INNER JOIN (
SELECT
P.*,
CP.ID_Line AS ID_Line
FROM
[dbo].[Payments] AS P
INNER JOIN [dbo].[Courses_Bus] CP ON CP.ID_Bus = P.ID_Bus
AND CP.DateHour_Start_Course <= P.Date
AND CP.DateHour_End_Course >= P.Date
) AS Table_B ON Table_A.ID_Bus = Table_B.ID_Bus
The main problem, apart from the fact that these requests do not seem to work properly, is that each table has several million lines that are increasing every day, and because of the datehour filter (mandatory since a single bus can be on several lines everyday) SSMS must compare each row of the second table to all rows of the other table.
So it takes an infinite amount of time, which will increase every day.
How can I make it work and optimise it ?
Assuming that this is the logic you want:
UPDATE p
SET p.Line = cb.ID_Line
FROM [dbo].[Payments] p JOIN
[dbo].[Courses_Bus] cb
ON p.ID_Bus = cb.ID_Bus AND
p.DateHour_Payment BETWEEN cb.DateHour_Start_Course AND cb.DateHour_End_Course;
To optimize this query, then you want an index on Courses_Bus(ID_Bus, DateHour_Start_Course, DateHour_End_Course).
There might be slightly more efficient ways to optimize the query, but your question doesn't have enough information -- is there always exactly one match, for instance?
Another big issue is that updating all the rows is quite expensive. You might find that it is better to do this in loops, one chunk at a time:
UPDATE TOP (10000) p
SET p.Line = cb.ID_Line
FROM [dbo].[Payments] p JOIN
[dbo].[Courses_Bus] cb
ON p.ID_Bus = cb.ID_Bus AND
p.DateHour_Payment BETWEEN cb.DateHour_Start_Course AND cb.DateHour_End_Course
WHERE p.Line IS NULL;
Once again, though, this structure depends on all the initial values being NULL and an exact match for all rows.
Thank you Gordon for your answer.
I have investigated and came with this query :
MERGE [dbo].[Payments] AS p
USING [dbo].[Courses_Bus] AS cb
ON p.ID_Bus= cb.ID_Bus AND
p.DateHour_Payment>= cb.DateHour_Start_Course AND
p.DateHour_Payment<= cb.DateHour_End_Course
WHEN MATCHED THEN
UPDATE SET p.Line = cb.ID_Ligne;
As it seems to be the most suitable in an MS-SQL environment.
It also came with the error :
The MERGE statement attempted to UPDATE or DELETE the same row more than once. This happens when a target row matches more than one source row. A MERGE statement cannot UPDATE/DELETE the same row of the target table multiple times. Refine the ON clause to ensure a target row matches at most one source row, or use the GROUP BY clause to group the source rows.
I understood this to mean that it finds several lines with identical
[p.ID_Bus= cb.ID_Bus AND
p.DateHour_Payment >= cb.DateHour_Start_Course AND
p.DateHour_Payment <= cb.DateHour_End_Course]
Yes, this is a possible case, however the ID is different each time.
For example, if two blue cards are beeped at the same time, or if there is a loss of network and the equipment has been updated, thus putting the beeps at the same time. These are different lines that must be treated separately, and you can obtain for example:
|ID|ID_Bus|DateHour_Payments|Line|
----------------------------------
|56|204|2021-01-01 10:00:00|15|
----------------------------------
|82|204|2021-01-01 10:00:00|15|
How can I improve this query so that it takes into account different payment IDs?
I can't figure out how to do this with the help I find online. Maybe this method is not the right one in this context.

How do I specify the table from which a record should be deleted in Access using JOIN?

I have a table and a query, qryAktuelleSchülerbildercontains some of the records in tblSchülerbilder. My goal is to delete all records from tblSchülerbilder which do not exist in qryAktuelleSchülerbilder.
I can get those records with
SELECT tblSchülerbilder.*, qryAktuelleSchülerbilder.ID
FROM tblSchülerbilder
LEFT JOIN qryAktuelleSchülerbilder
ON tblSchülerbilder.ID = qryAktuelleSchülerbilder.ID
WHERE qryAktuelleSchülerbilder.ID Is Null;
When I now change over to a delete-query, the design-view in Access looks fine (I don't really know, what it does to the SQL-view at this point), it shows tblSchülerbilder.* with Delete: From and ID of qryAktuelleSchülerbilder with Delete: Where and Criteria: Is Null. But when I execute the query, it gives me Specify the table containing the records you want to delete. (Error 3128).
I don't know where to put tblSchülerbilder again, since it is already set as delete from. The online article is of no help, since I have already specified tblSchülerbilder.* in the select statement.
You can do it with NOT IN:
DELETE FROM tblSchülerbilder
WHERE ID NOT IN (SELECT ID FROM qryAktuelleSchülerbilder)
or with NOT EXISTS:
DELETE FROM tblSchülerbilder AS t
WHERE NOT EXISTS (
SELECT 1 FROM qryAktuelleSchülerbilder AS q
WHERE q.ID = s.ID
)

Access Update on Query Table

I'm currently in the process of converting an access database to SQL. While going through it, I found this.
Update EmployeeCustomerOrderDetail set Valid = -1;
This is strange because EmployeeCustomerOrderDetail is a query, defined below.
Select *
From Employee
inner join Order on Employee.EmployeeID = Order.EmployeeID
inner join Customer on Order.CustomerID = Customer.CustomerID
inner join OrderDetail on Order.OrderID = OrderDetail.OrderID
I thought that this achieved nothing since an Update on a Select wouldn't work, but I tested it with
Update (Select * From Employees) as Emp set Emp.WorkPhone as 'Random Value'
And it worked.
My question then, is how is this processed. Does the update traverse backwards through all query tables until it finds a dataTable? Will it change the value of a reference column, or the actual data in the associated table?
This only works with updateable recordsets. It's the same as just changing a value directly in the Access query.
In an updateable recordsets, each field in the dataset (query) is directly bound to a field in a table. This means updates directly affect the underlying tables of a query. You can review this answer for rules about which recordsets are and aren't updateable.
Another really contra-intuitive thing about this is that recordsets with outer joins can be contain updateable unmatched records. In this case, a record is created in the table that doesn't contain a matched record, then a new one gets created.

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.

SQL server 2008 trigger not working correct with multiple inserts

I've got the following trigger;
CREATE TRIGGER trFLightAndDestination
ON checkin_flight
AFTER INSERT,UPDATE
AS
BEGIN
IF NOT EXISTS
(
SELECT 1
FROM Flight v
INNER JOIN Inserted AS i ON i.flightnumber = v.flightnumber
INNER JOIN checkin_destination AS ib ON ib.airport = v.airport
INNER JOIN checkin_company AS im ON im.company = v.company
WHERE i.desk = ib.desk AND i.desk = im.desk
)
BEGIN
RAISERROR('This combination of of flight and check-in desk is not possible',16,1)
ROLLBACK TRAN
END
END
What i want the trigger to do is to check the tables Flight, checkin_destination and checkin_company when a new record for checkin_flight is added. Every record of checkin_flight contains a flightnumber and desknumber where passengers need to check in for this destination.
The tables checkin_destination and checkin_company contain information about companies and destinations restricted to certain checkin desks. When adding a record to checkin_flight i need information from the flight table to get the destination and flightcompany with the inserted flightnumber. This information needs to be checked against the available checkin combinations for flights, destinations and companies.
I'm using the trigger as stated above, but when i try to insert a wrong combination the trigger allows it. What am i missing here?
EDIT 1:
I'm using the following multiple insert statement
INSERT INTO checkin_flight VALUES (5315,3),(5316,3),(5316,2)
//5315 is the flightnumber, 3 is the desknumber to checkin for that flight
EDIT 2:
Tested a single row insert which isn't possible, then the error is being thrown correct. So it's the multiple insert which seems to give the problem.
The problem is that your logic is allowing any insert that includes at least one valid set of values through. It will only fail if all of the inserted records are invalid, instead of if any of the inserted records are invalid.
Change your "IF NOT EXISTS(...)" to a statement "IF EXISTS(...)" and change your SELECT statement to return invalid flights.
eg:
IF EXISTS
(
SELECT 1
FROM Flight v
INNER JOIN Inserted AS i ON i.flightnumber = v.flightnumber
LEFT JOIN checkin_destination AS ib ON ib.airport = v.airport
AND i.desk = ib.desk
LEFT JOIN checkin_company AS im ON im.company = v.company
AND i.desk = im.desk
WHERE (im.desk IS NULL OR ib.desk IS NULL)
)
BEGIN
RAISERROR('This combination of of flight and check-in desk is not possible',16,1)
ROLLBACK TRAN
END
I'm not sure of your business logic, but you need to check that the query does the proper thing.
Your problem is the IF NOT EXISTS, if the condition is true for 1 of the 3 rows in INSERTED it does not exist. You need to convert it to find a problems row and use IF EXISTS then error out.
However, when in a trigger the best way to error out is:
RAISERROR()
ROLLBACK TRANSACTION
RETURN
I kind of doubt that the lack of a RETURN is your problem, but it is always best to include the three Rs when erroring out in a trigger.
The problem is that the condition will be true if only one of the inserted records are correct. You have to check that all records are correct, e.g.:
if (
(
select count(*) from inserted
) = (
select count(*) from flight v
inner join inserted i ...
)
) ...
The inserted table can contain multiple rows and therefore all logic within a trigger MUST be able to apply to all rows. The idea triggers must fire once per row effect is a common misunderstanding WRT triggers. SQL Server will tend to coalesce calls to a trigger to increase performance when they occur within the same transaction.
To fix you might start with a COUNT() of inserted and compare that with a COUNT() of the matching conditions and raise an error if there is a mismatch.