Please explain what the following statements mean. It's an assignment of local variables but I do not understand what inserted or deleted means?
select #ID = ID from inserted
select #co_ID = co_ID from deleted
Thank you
INSERTED and DELETED are temporary, memory resident tables created by SQL Server for use (or misuse) within a DML trigger.
Inserts and updates copy new rows into INSERTED,
Deletes and updates copy old rows into DELETED.
It looks like this code is attempting to audit a change to a row of data - but will fail unless there is something else in the code path guaranteeing that only a single row will be updated.
These statements mean that you have written a trigger in SQL Server that is not safe. The trigger assumes that only one row has been updated. This is not safe because SQL Server calls triggers based on groups of rows.
If there is one row, then the parameters #ID and #co_ID are assigned values from that row. If there are multiple rows being updated, then values from arbitrary -- and perhaps different -- rows are assigned to the parameters.
Related
I'm new enough to Postgres, and I'm trying to figure out how to lock a specific row of a table.
As an example, I have a table of users:
Name: John, Money: 1
Name: Jack, Money: 2
In my backend, I want to select John and make sure that no other calls can update (or even select possibly) John's row until my transaction is complete.
I think I need an exclusive lock from what I've read up online? I can't seem to find a good example of locking just 1 row from a table online, any idea?
Edit - Should I be doing it at method level like #SqlUpdate (or some form of that - using org.skife.jdbi.v2) or in the query itself?
If you want to lock the table in a specific selected row you need to LOCK FIRST them use the FOR UPDATE / FOR SHARE statement.
For example, in your case if you need to lock the first row you do this:
BEGIN;
LOCK TABLE person IN ROW EXCLUSIVE MODE;
-- BLOCK 1
SELECT * FROM person WHERE name = 'John' and money = 1 FOR UPDATE;
-- BLOCK 2
UPDATE person set name = 'John 2' WHERE name = 'John' and money = 1;
END;
In the BLOCK1 before the SELECT statement you are doing nothing only telling the database "Hey, I will do something in this table, so when I do, lock this table in this mode". You can select / update / delete any row.
But in BLOCK2 when you use the FOR UPDATE you lock that row to other transactions to specific modes(read the doc for more details). Will be locked until that transaction ends.
If you need a example do a test and try to do another SELECT ... FOR UPDATE in BLOCK2 before end the first transaction. It will be waiting the first transaction to end and will select right after it.
Only an ACCESS EXCLUSIVE lock blocks a SELECT (without FOR
UPDATE/SHARE) statement.
I am using it in a function to control subsequences and it is great. Hope you enjoy.
As soon as you update (and not commit) the row, no other transaction will be able to update that row.
If you want to lock the row before doing the update (which seems useless), you can do so using select ... for update.
You can not prevent other sessions from reading that row, and frankly that doesn't make sense either.
Even if your transaction hasn't finished (=committed) other sessions will not see any intermediate (inconsistent) values - they will see the state of the database as it was before your transaction started. That's the whole point of having a relational database that supports transactions.
You can use
LOCK TABLE table IN ACCESS EXCLUSIVE MODE;
when you are ready to read from your table. "SELECT" and all other operations will be queued until the end of the transaction (commit changes or rollback).
Note that this will lock the entire table and referring to PostgreSQL there is no table level lock that can lock exclusively a specific row.
So you can use
FOR UPDATE
row level lock in all your SELECT that will update your row and this will prevent all those SELECT that will update a row from reading your row !
PostgreSQL Documentation :
FOR UPDATE causes the rows retrieved by the SELECT statement to be locked as though for update. This prevents them from being locked, modified or deleted by other transactions until the current transaction ends. That is, other transactions that attempt UPDATE, DELETE, SELECT FOR UPDATE, SELECT FOR NO KEY UPDATE, SELECT FOR SHARE or SELECT FOR KEY SHARE of these rows will be blocked until the current transaction ends; conversely, SELECT FOR UPDATE will wait for a concurrent transaction that has run any of those commands on the same row, and will then lock and return the updated row (or no row, if the row was deleted). Within a REPEATABLE READ or SERIALIZABLE transaction, however, an error will be thrown if a row to be locked has changed since the transaction started. For further discussion see Section 13.4.
The FOR UPDATE lock mode is also acquired by any DELETE on a row, and also by an UPDATE that modifies the values on certain columns. Currently, the set of columns considered for the UPDATE case are those that have a unique index on them that can be used in a foreign key (so partial indexes and expressional indexes are not considered), but this may change in the future.*
I'm using my own table, my table name is paid_properties and it has two columns user_id and counter.
As you want one transaction at a time, so you can use one of the following locks:
FOR UPDATE mode assumes a total change (or delete) of a row.
FOR NO KEY UPDATE mode assumes a change only to the fields that are not involved in unique indexes (in other words, this change does not affect foreign keys).
The UPDATE command itself selects the minimum appropriate locking mode; rows are usually locked in the FOR NO KEY UPDATE mode.
To test it run following query in one tab (I'm using pgadmin4):
BEGIN;
SELECT * FROM paid_properties WHERE user_id = 37 LIMIT 1 FOR NO KEY UPDATE;
SELECT pg_sleep(60);
UPDATE paid_properties set counter = 4 where user_id = 37;
-- ROLLBACK; -- If you want to discard the operations you did above
END;
And the following query in another tab:
UPDATE paid_properties set counter = counter + 90 where user_id = 37;
You'll see that you're the second query will not be executed until the first one finishes and you'll have an answer of 94 which is correct in my case.
For more information:
https://postgrespro.com/blog/pgsql/5968005
https://www.postgresql.org/docs/current/explicit-locking.html
Hope this is helpful
The existing design for this program is that all changes are written to a changelog table with a timestamp. In order to obtain the current state of an item's attribute we JOIN onto the changelog table and take the row having the most recent timestamp.
This is a messy way to keep track of current values, but we cannot readily change this changelog setup at this time.
I intend to slightly modify the behavior by adding an "IsMostRecent" bit to the changelog table. This would allow me to simply pull the row having that bit set, as opposed to the MAX() aggregation or recursive seek.
What strategy would you employ to make sure that bit is always appropriately set? Or is there some alternative you suggest which doesn't affect the current use of the logging table?
Currently I am considering a trigger approach, which turns the bit off all other rows, and then turns it on for the most recent row on an INSERT
I've done this before by having a "MostRecentRecorded" table which simply has the most recently inserted record (Id and entity ID) fired off a trigger.
Having an extra column for this isn't right - and can get you into problems with transactions and reading existing entries.
In the first version of this it was a simple case of
BEGIN TRANSACTION
INSERT INTO simlog (entityid, logmessage)
VALUES (11, 'test');
UPDATE simlogmostrecent
SET lastid = ##IDENTITY
WHERE simlogentityid = 11
COMMIT
Ensuring that the MostRecent table had an entry for each record in SimLog can be done in the query but ISTR we did it during the creation of the entity that the SimLog referred to (the above is my recollection of the first version - I don't have the code to hand).
However the simple version caused problems with multiple writers as could cause a deadlock or transaction failure; so it was moved into a trigger.
Edit: Started this answer before Richard Harrison answered, promise :)
I would suggest another table with the structure similar to below:
VersionID TableName UniqueVal LatestPrimaryKey
1 Orders 209 12548
2 Orders 210 12549
3 Orders 211 12605
4 Orders 212 10694
VersionID -- being the tables key
TableName -- just in case you want to roll out to multiple tables
UniqueVal -- is whatever groups multiple rows into a single item with history (eg Order Number or some other value)
LatestPrimaryKey -- is the identity key of the latest row you want to use.
Then you can simply JOIN to this table to return only the latest rows.
If you already have a trigger inserting rows into the changelog table this could be adapted:
INSERT INTO [MyChangelogTable]
(Primary, RowUpdateTime)
VALUES (#PrimaryKey, GETDATE())
-- Add onto it:
UPDATE [LatestRowTable]
SET [LatestPrimaryKey] = #PrimaryKey
WHERE [TableName] = 'Orders'
AND [UniqueVal] = #OrderNo
Alternatively it could be done as a merge to capture inserts as well.
One thing that comes to mind is to create a view to do all the messy MAX() queries, etc. behind the scenes. Then you should be able to query against the view. This way would not have to change your current setup, just move all the messiness to one place.
In the following statement, will f1 always get the value that f2 used to have? Or will f2 sometimes get updated first and f1 winds up with NULL? I am under the impression that the new values are not available within the statement, that f2 has the old value while processing the record, but I can't find an authoritative place that says this.
UPDATE x
SET
x.f1 = x.f2,
x.f2 = NULL
Conceptually the operation happens "all at once" so it will use the "before" values
Indeed
UPDATE x
SET
x.f1 = x.f2,
x.f2 = x.f1
would also work fine to swap the two column values.
f1 will always get f2's previous value before the UPDATE.
Technically speaking the record is deleted, and reinserted. So SQL will work out what the new record should be, then delete the current record, and insert the new record afterwards.
This article regarding SQL Triggers may help explain:
The deleted table stores copies of the affected rows during DELETE and UPDATE statements. During the execution of a DELETE or UPDATE statement, rows are deleted from the trigger table and transferred to the deleted table. The deleted table and the trigger table ordinarily have no rows in common.
The inserted table stores copies of the affected rows during INSERT
and UPDATE statements. During an insert or update transaction, new
rows are added to both the inserted table and the trigger table. The
rows in the inserted table are copies of the new rows in the trigger
table.
http://msdn.microsoft.com/en-us/library/ms191300.aspx
I need to write a trigger that only runs when an insertion that has a certain field with a specific value is inserted. There is no updating.
For example, if I insert an entry that has column X with the value "MAC ID", I want it to run a trigger, but ONLY if the value is "MAC ID".
I know how to test for this normally, but not sure how to test this in a trigger. So far I've found that I want to use "FOR INSERT", but beyond that I don't know how to implement it.
Any help is much appreciated! Thanks!
create trigger MaxIdInsert
on YourTable
after insert
as
if exists
(
select *
from inserted
where ColumnX = 'MAX ID'
)
begin
-- do what you want here if ColumnX has value of 'MAX ID'
end
go
There's no way to only fire a trigger on certain DML specifications (besides insert, update, and/or delete). Your best bet is to test out the dynamic inserted table, which contains records that are inserted into YourTable. In this, you can test for inserted records that have a ColumnX value of "MAX ID".
Edit: In case you were wondering, I know you specified a for trigger in your question. A for trigger is equivalent to an after trigger in SQL Server.
You need to be aware that triggers run once for a batch, not once per row. As such, you may be running in a circumstance in which some of the rows match your criteria, and others do not.
As such, you'd do best to write your trigger logic to select the matching rows from inserted. If there were no matching rows, the rest of your logic will be working with an empty set, but that's no real problem - SQL Server is plenty fast enough at doing nothing, when required.
E.g. if you're inserting these rows into an audit table, something like:
create trigger MacId
on T
for insert
as
insert into Audit(Col1,Col2,Col3)
select i.Col1,i.Col2,'inserted'
from inserted i
where i.Col4 = 'MAC ID'
I have a trigger that inserts a record into a diff table but I need to get that record that was inserted inside the trigger, how do I do it? There is no identity field, only account_nbr that is generated by a separate trigger on the insert table.
I don't know if there is sql statements to retrieve a row that was just inserted.
DB is Sql Server 2008.
The OUTPUT clause will give you back the records you have just inserted: http://msdn.microsoft.com/en-us/library/ms177564.aspx
If you mean the rows inserted before the trigger invoked, they are in the inserted pseudo-table.