AFTER UPDATE trigger using an aggregate from the updated table - sql

I'm having trouble with an update trigger. I want the trigger to set Quarterbacks.Yards equal to the sum of Wide receiving yards if they're on the same team.
create trigger T_Receiving_Passing
on NFL.Widereceivers
after update
as
begin
declare
#receivingyards int,
select #receivingyards = sum (Widereceivers.Yards) from NFL.Widereceivers
update NFL.Quarterbacks
set Quarterbacks.Yards = #receivingyards
where Quarterbacks.Team = Widereceivers.Team
end
At the end of the statement, Widereceivers.Team is underlined in red, and it is causing errors. I get this same error whenever I try to reference a column in another table without naming the table in a from clause. How can I fix this problem?

Ok, you should be able to do this without the SELECT statement or its variable and instead use a more complex UPDATE statement joining on the special inserted table which holds the new values from the update.
CREATE TRIGGER T_Receiving_Passing
ON NFL.Widereceivers
AFTER UPDATE
AS
UPDATE NFL.Quarterbacks
-- Get the SUM() in a subselect
SET Quarterbacks.Yards = (SELECT SUM(Yards) FROM Widereceivers WHERE Team = inserted.Team)
FROM
NFL.Quarterbacks
-- Join against the special inserted table
JOIN inserted ON Quarterbacks.Team = inserted.Team
GO
Here is a proof of concept
In your original attempt, you hoped to use a SELECT query first to populate a scalar variable. In an UPDATE statement however, you can use a subselect that returns exactly one column of exactly one row inside the SET clause to retrieve a new value.
Since your requirement was to use an aggregate SUM() it isn't as straightforward as assigning a value directly from the inserted like SET Yards = inserted.Yards. Instead, the subselect produces the aggregate sum limited to just the Team used in the inserted row.
As far as the inserted/deleted tables go, review the official documentation. I have not worked with SQL Server regularly for a few years but if I recall correctly, the inserted table must occur in the FROM clause which implies it will usually need to be JOINed in. In your UPDATE statement, inserted is needed in both the subselect and the outer query, so it was joined in the outer one.

Related

How to write a SQL update statement that will fail if it updates more than one row?

A common mistake when writing update statements is to forget the where clause, or to write it incorrectly, so that more rows than expected get updated. Is there a way to specify in the update statement itself that it should only update one row (and to fail if it would update more)?
Correcting an error in the number of rows updated requires thinking ahead - using a transaction, formatting it as a select first to check the number of rows - and then actually catching the error. It would be useful to be able to write in one place the expectation for the number of rows.
Combining a few facts, I found a working solution for Postgres.
A select will fail when comparing using = to a subquery that returns more than one row. (where x = (select ...))
Values can be returned from an update statement, using the returning clause. An update cannot be used as a subquery, but it can be used as a CTE, which can be used in a subquery.
Example:
create table foo (id int not null primary key, x int not null);
insert into foo (id, x) values (1,5), (2,5);
with updated as (update foo set x = 4 where x = 5 returning id)
select id from foo where id = (select id from updated);
The query containing the update fails with ERROR: more than one row returned by a subquery used as an expression, and the updates are not applied. If the update's where clause is adjusted to only match one row, the update succeeds.

Update multiple rows in the target table from one row in the source table using a natural key

I need to update two columns in a FACT table using data from a dimension table. the challenge is that I don't have a primary key that match both tables, so I have to use a natural key, two columns to create a unique value. besides the source have a single record and the target has multiples records. if I do a merge I get
ora-30926 unable to get a stable set of rows
and if I do an update I get another error. please I need help.
I try this update statement:
UPDATE dw.target_table obc
SET
( obc.sail_key,
obc.durations ) = (
SELECT
sd.sail_key,
sd.durations
FROM
dw.source_table sd
WHERE
obc.code_1 = sd.code_2
AND obc.date_1 = sd.date_2
)
WHERE
obc.item NOT IN (
30,
40
)
AND obc.sail_key = 0
and OBC.load_date between to_date('01-12-2018','DD-MM-YYYY')
AND to_date ('31-12-2018','DD-MM-YYYY');
and I try this merge statement:
MERGE INTO dw.target_table obc
USING ( SELECT distinct
code_2,date_2,durations,sail_key
FROM dw.source_table
) tb_dim
ON ( obc.code_1 = tb_dim.code_2
AND obc.date_1 = tb_dim.date_2 )
WHEN MATCHED THEN UPDATE SET obc.durations = tb_dim.durations,
obc.sail_key = tb_dim.sail_key
WHERE
obc.sail_key = 0
AND obc. NOT IN (
30,
40
)
AND obc.loaddate BETWEEN TO_DATE('01-01-2012','DD-MM-YYYY')
AND TO_DATE ('31-01-2012','DD-MM-YYYY');
ora-30926 unable to get a stable set of rows
This means (code_2,date_2) is not a unique key of tb_dim. Consequently your USING subquery does not produce a set which matches just one row to any row in obc. Consequently the MERGE fails, because Oracle cannot determine which row from the USING subquery should be applied to the target. The DISTINCT does not help because it is applied to the whole projection and it seems you have multiple different values of durations,sail_key for each permutation of code_2,date_2.
You don't say which error you get when you run your UPDATE but presumably it's ORA-01779 or ORA-01427. Something indicating the subquery isn't returning a set of joining keys.
So how do you fix the situation? We cannot give you the correct solution because this is a failure of your data model or your specification. Solving requires an understanding of your business that we do not have. But generally you need to find an extra rule which reduces the USING subquery to a set. That is:
add a third key column which allows the ON clause to map one row in tb_dim to one row in obc; or
use row_number() analytic function in the subquery to fake such a column, preferably ordering by a meaningful column such as date; or
add a criterion WHERE clause of the subquery to remove duplicate values of code_2,date_2.
Alternatively, if you don't care which particular values of durations,sail_key get applied you can use an aggregate:
USING (SELECT code_2
,date_2
,max(durations) as durations
,max(sail_key) as sail_key
FROM dw.source_table
group by code_2,date_2 ) tb_dim
Use whatever function makes sense to you.

Change column value after INSERT if the value fits criteria?

I have never really worked with Triggers before in MSSQL but I think it'll be what I need for this task.
The structure of the table is as such:
ID|****|****|****|****|****|****|****|TOUROPERATOR
The Tour Operator Code is the code that tells us what company owned the flight we carried out for them. Two of those codes (there are 24 in total) are outdated. Our users requested that those two be changed but the tour operator code is pulled from a database we don't control. The FlightData table however, we do control. So I was thinking a trigger could change the tour operator code if it was one of the two outdated ones, to the correct ones instead respectively when they were inserted.
So I went into good ol' SQL Management Studio and asked to make a trigger. It gave me some sample code and here is my Pseudo Code below:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER ChangeProvider
ON FlightData
AFTER INSERT
AS
BEGIN
IF(TheInsertedValue == Criteria)
UPDATE FlightData
SET TheInsertedValue = NewValue
ENDIF
END
GO
I am not that good with this type of Database Programming so excuse my mistakes.
How would I go about doing this?
You could add a computed column to your table instead of adding a trigger.
Then the new column could just use a case statement to either show
the original TourOperator column value or the new value you wanted.
You'd add a new column to your table like this
TourOperatorCorrect = CASE WHEN TourOperator = 'Whatever value' THEN 'ChangedValue'
--I just want to use what I have already in the TourOperator column
ELSE TourOperator
END AS VARCHAR(50)
Basics of computed columns are here - https://msdn.microsoft.com/en-ie/library/ms188300.aspx
Your misconception here is that the trigger runs once per inserted value - it is in fact run once per insert statement, so you can and will find more than one row inserted at once.
You'll find that your inserted values are in the pseudo table inserted, which has the same structure as your FlightData table in this case. You write a select statement against that, specifying any criteria you wish.
However, it's not immediately clear what your logic is - does the FlightData table you are updating in your trigger only have one row? Do you update every row in the table with the newest inserted value? It is hard to understand what you are trying to now, and what the purpose of the table and this trigger are - let alone what you would want to do if you inserted more than one row at once.
When inserted table contains mutiple rows,your code will fail,so change code to work with inserted table as whole
UPDATE F
SET f.TheInsertedValue = i.value
from inserted i
join
Flighttable F
on f.matchingcolumn=i.matchingcolumn
and i.somevalue='criteria'

SQL: I need to take two fields I get as a result of a SELECT COUNT statement and populate a temp table with them

So I have a table which has a bunch of information and a bunch of records. But there will be one field in particular I care about, in this case #BegAttField# where only a subset of records have it populated. Many of them have the same value as one another as well.
What I need to do is get a count (minus 1) of all duplicates, then populate the first record in the bunch with that count value in a new field. I have another field I call BegProd that will match #BegAttField# for each "first" record.
I'm just stuck as to how to make this happen. I may have been on the right path, but who knows. The SELECT statement gets me two fields and as many records as their are unique #BegAttField#'s. But once I have them, I haven't been able to work with them.
Here's my whole set of code, trying to use a temporary table and SELECT INTO to try and populate it. (Note: the fields with # around the names are variables for this 3rd party app)
CREATE TABLE #temp (AttCount int, BegProd varchar(255))
SELECT COUNT(d.[#BegAttField#])-1 AS AttCount, d.[#BegAttField#] AS BegProd
INTO [#temp] FROM [Document] d
WHERE d.[#BegAttField#] IS NOT NULL GROUP BY [#BegAttField#]
UPDATE [Document] d SET d.[#NumAttach#] =
SELECT t.[AttCount] FROM [#temp] t INNER JOIN [Document] d1
WHERE t.[BegProd] = d1.[#BegAttField#]
DROP TABLE #temp
Unfortunately I'm running this script through a 3rd party database application that uses SQL as its back-end. So the errors I get are simply: "There is already an object named '#temp' in the database. Incorrect syntax near the keyword 'WHERE'. "
Comment out the CREATE TABLE statement. The SELECT INTO creates that #temp table.

Update trigger with GROUP BY

I'm using insert-/update triggers to update a second table's column Price.
The insert trigger seems to work perfectly, but when I try to change a single record in SSMS, I get an error:
The row value(s) updated or deleted either do not make the row
unique or they alter multiple rows(2 rows).
This is my update-trigger:
CREATE TRIGGER [dbo].[trgUpdateMasterData] ON [dbo].[tabSparePartMasterData_Temp]
AFTER UPDATE
AS
UPDATE tabSparePart
SET Price = MD.Price
FROM tabSparePart INNER JOIN
(
SELECT inserted.[Material Number (SAP)] AS MaterialNumber, inserted.Price
FROM inserted
GROUP BY [Material Number (SAP)], inserted.Price
) MD
ON tabSparePart.SparePartName = MD.MaterialNumber
I need to group by Material-Number because there are redundant rows inserted into table tabSparePartMasterData_Temp which i'm only using to update the Sparepart-Price in tabSparePart. But i assumed that the group by would sort out the duplicates(Price is same for any duplicate).
It's possible that the inserted/updated records' MaterialNumber is not available in tabSparepart. In this case this record should be "skipped". Does the INNER JOIN takes that into account?
Try adding SET NOCOUNT ON to the trigger
This error doesn't look like a SQL error and I'm guessing the client code is getting confused by the 2nd update in the trigger.
Edit: this error can be caused by the data grid view in SSMS being silly.
This isn't a SQL message as I thought: it is an SSMS being stupid message
See these which all says "learn to write SQL"
http://blogs.lessthandot.com/index.php/DataMgmt/DataDesign/dealing-with-the-row-value-s-updated-or (Less than dot blog)
Trigger that modifies multiple rows on diffrent table then it was invoked on in SQL Server 2005
SSMS permits duplicate records in a table, but not subsequent updates
Saying that, there is a KB article about a bug in SQL Server 2005...