I have a trigger on a table which gets triggered on any new insert...Here is the code for the trigger. This trigger has to calculate duration in seconds and insert the record in the table.
CREATE TRIGGER duration_trigger
BEFORE INSERT on StudentData
BEGIN
UPDATE StudentData set duration = (select cast( (julianday(enddatetime) - julianday(startdatetime) ) *24 * 60 *60 as integer) from StudentData);
END
In StudentsData table Duration column is defined as INTEGER,
StartDatetime and EndDatetime are defined as TEXT
Here comes my issue.
Trigger gets triggered, but the value in Duration column is always 7
When I execute the same select query that is in the the trigger in a SQL tool, it gives me correct duration in seconds. Trigger on the database is not producing the same result...what could be the issue?
I am also attaching screenshots of the trigger data in the table and select query results from same table.
Table results after trigger.
Select Query results
Basically you are updating all rows as you are not specifying a WHERE clause for the update. So the very last successful update will apply the value to all rows, hence why they are all 7.
Furthermore before you have inserted a row what is there to update? I don't think this can be done an analogy would be; Before you build the wall paint the wall.
Now you could UPDATE after the insert, but care needs to be taken when using UPDATE i.e. if you want to update anything other than all rows then you need to restrict the update to the required rows. A WHERE clause can do this.
As such if you were to ensure that an inserted column were set to an invalid value (as far as your view of the data e.g. a duration of -1 would only suit Dr. Who (apologies to any other Time Travellers)).
Null could also be used.
However, I prefer using a value that is specifically set. Assuming that the row is inserted with duration being given a value of -1 (e.g. duartion INTEGER DEFAULT -1) Then :-
CREATE TRIGGER duration_trigger001
AFTER INSERT on StudentData
BEGIN
UPDATE StudentData SET duration = ((julianday(enddatetime) - julianday(startdatetime)) * 24 * 60 * 60) WHERE duration = -1;
END;
Would work e.g. :-
Notes
The first two rows were added before the trigger was created.
Row 10 was deleted because I used . instead of : as a separator it did nothing.
I didn't cast to INT for simplicity/laziness.
Related
I have a table, which should hold rows for OrderGroups. Basically, when a client creates an Order, his Order should be put inside a group based on his client id, until an administrator can verify the order. The OrderGroups tables structure is the following:
OrderGroupId | IsClosed | clientId
-------------------------------------------------
INT PRIMARY (AutoIncrement) | BOOLEAN | INT
My code should work in the following way: when a client creates a new order, we should check if he already has a NOT cloesd order group. If he has, we should attach that order group to his order. If he has none, we should create a new order group for him, and attach his order to the newly created group.
In the past, no locking was used when fetching/creating the order group, which resulted in naturally, that some clients, when inserting multiple orders concurently, ended up with multiple open order groups. I've modified my order group fetching query, to the following:
BEGIN TRANSACTION;
SELECT * FROM OrderGroups WHERE clientId = {id} AND IsClosed = 0 FOR UPDATE;
// if no order groups are returned, insert a new group and use that one
// if an order group is returned, use the returned order group
END TRANSACTION;
This prevents the appeareance of multiple OrderGroups, but it sometimes results in a deadlock. I presume that the reason for this, is how MariaDB is locking the rows, when they are not present. Basically, if a result would be returned by the query in question, all subsequent calls requesting the same row, should wait, until the transaction that was first requesting it for update, commits or rolls back. But this is not the case, if a non-existent row gets locked this way. The insersions are still prevented (that is why I am getting the deadlock), but the select queries are processed.
Basically, this is what happens:
C1 -> BEGIN TRANSACTION;
C1 -> SELECT OrderGroups WHERE clientId = 1 AND IsClosed = 0 FOR UPDATE; // returns no rows
C2 -> BEGIN TRANSACTION;
C2 -> SELECT OrderGroups WHERE clientId = 1 AND IsClosed = 0 FOR UPDATE; // returns no rows, instead of waiting for C1 to commit or rollback the transaction
C1 -> INSERT INTO OrderGroups SET clientId = 1, IsClosed = 0; // holds, because C2 has a for update lock on the row? being inserted
C2 -> INSERT INTO OrderGroups SET clientId = 1, IsClosed = 0; // holds, because C1 has a for update lock on the row
MARIADB -> randomly kills C1 or C2 because of the deadlock, while the other may finish
How could I avoid this deadlock situation, and still maintain the single open group policy for the OrderGroups table?
For the moment, I managed to resolve this in a kind of hackish way, by inserting a new, unique column inside the table.
I've inserted the UniqId (VARCHAR(20)) column as a unique in the database. When I am trying to fetch or create a new row in the table, instead of selecting and then inserting if nothing is found, I just simply make an INSERT IGNORE INTO ... using c{clientId} for the UniqId column. Whenever the order group is closed, I update the UniqId column to the primary key of the table.
This way, when performing the INSERT IGNORE, if the row already exists, it gets locked, and I will be able to select it inside the next SELECT statement, and if the row does not exist yet, it gets inserted and also gets locked, and I am able to select it in the next select statement.
This is a rather hacky way to solve my exact situation, but I am still open to suggestions for the possibility, to somehow get an exclusive lock for a row in the table, that does not exist yet.
I have two tables TRIP and DRIVER. When a new set of values in inserted into TRIP (to indicate a new trip being made), the values in the column TOTALTRIPMADE (which is currently empty) in the table DRIVER will increase by one. The trigger should recognise which row to update with the select statement I've made.
This is the trigger I've made:
CREATE OR REPLACE TRIGGER updatetotaltripmade
AFTER INSERT ON trip
FOR EACH ROW
ENABLE
BEGIN
UPDATE DRIVER
SET TOTALTRIPMADE := OLD.TOTALTRIPMADE+1
WHERE (SELECT L#
FROM TRIP
INNER JOIN DRIVER
ON TRIP.L# = DRIVER.L#;)
END;
/
However I get this error:
ORA-04098: trigger 'CSCI235.UPDATETOTALTRIPMADE' is invalid and failed re-validation
What should I edit in my code so that my trigger works? Thanks!
One error you made is in trying to reference OLD.TOTALTRIPMADE in your SET clause since no alias OLD exists, and unless the table TRIP contains a TOTALTRIPMADE column then the :OLD record won't contain a TOTALTRIPMADE column either (note that since this is an insert trigger the :OLD record either won't exist or won't contain any meaningful data anyway). Another error is in your WHERE clause where you are selecting L# from TRIP joined to DRIVER, but you aren't linking it back to the DRIVER table that you are attempting to update. Instead just update DRIVER where L# is equal the :NEW value of L# from the trip table. The final error I noticed is your use of , the := assignment operator which is for PLSQL code, however you are using it within SQL so just use = without the colon:
CREATE OR REPLACE TRIGGER updatetotaltripmade
AFTER INSERT ON trip
FOR EACH ROW
ENABLE
BEGIN
UPDATE DRIVER
SET TOTALTRIPMADE = nvl(TOTALTRIPMADE,0)+1
WHERE L# = :NEW.L#;
END;
/
Your code has syntax error due to which the trigger is not compiling,I have modified the trigger and it should get compiled successfully with desired results.Please check and feedback.
Please find below the script to create table and compile the trigger,
drop table trip;
create table trip (trip_id number(10),L# varchar2(10));
drop table driver;
create table driver(driver_id number(10),TOTALTRIPMADE number(10),L# varchar2(10));
drop trigger updatetotaltripmade;
CREATE OR REPLACE TRIGGER updatetotaltripmade
AFTER INSERT ON trip
FOR EACH ROW
ENABLE
DECLARE
BEGIN
UPDATE DRIVER
SET TOTALTRIPMADE = nvl(TOTALTRIPMADE,0) + 1
WHERE DRIVER.L# = :new.L#;
END;
/
select * from ALL_OBJECTS where object_type ='TRIGGER';
Output is below from the tests i did on https://livesql.oracle.com/apex/
There are no issues in the code.The trigger is compiled successfully and is valid.
In my database, i have a trigger which insert the change log entries when a row in Table tblA is updated.
Now, in my code i have to update it through a plain Sql query like
int count = DBContext.ExecuteStoreCommand("<sql query to update records>");
This count variable contains the number of rows affected(no of rows updated + no of rows inserted) due to query.
So my question is, How do i can get only the number of updated rows?
Currently i'm using Entity framework 4. I have looked for solution through connected or disconnected model but couldn't help myself.
int count = DBContext.ExecuteStoreCommand("");
I think you hv to change this to return Select result set
then do this,
<sql query to update>
Select ##RowCount rowcountAffected
Or
suppose your update is
update table1 set col1='foo' where id=2
select count(*) rowcountAffected from table1 where id=2
The most efficient way to return row affected can be
i) Assuming you only update (don't refresh any record after that)
Put Set Nocount ON
Declare #Output parameter inside proc
I'm trying to add an auto-calculated field in SQL Server 2012 Express, that stores the % of project completion, by calculating the date difference by using:
ALTER TABLE dbo.projects
ADD PercentageCompleted AS (select COUNT(*) FROM projects WHERE project_finish > project_start) * 100 / COUNT(*)
But I am getting this error:
Msg 1046, Level 15, State 1, Line 2
Subqueries are not allowed in this context. Only scalar expressions are allowed.
What am I doing wrong?
Even if it would be possible (it isn't), it is anyway not something you would want to have as a caculated column:
it will be the same value in each row
the entire table would need to be updated after every insert/update
You should consider doing this in a stored procedure or a user defined function instead.Or even better in the business logic of your application,
I don't think you can do that. You could write a trigger to figure it out or do it as part of an update statement.
Are you storing "percentageCompleted" as a duplicated column value in the same table as your project data?
If this is the case, I would not recommend this, because it would duplicate the data.
If you don't care about duplicate data, try something separating the steps out like this:
ALTER TABLE dbo.projects
ADD PercentageCompleted decimal(2,2) --You could also store it as a varchar or char
declare #percentageVariable decimal(2,2)
select #percentageVariable = (select count(*) from projects where Project_finish > project_start) / (select count(*) from projects) -- need to get ratio by completed/total
update projects
set PercentageCompleted = #percentageVariable
this will give you a decimal value in that table, then you can format it on select if you desire to % + PercentageCompleted * 100
I am developing an Auction site in asp.net3.5 and sql server 2008R2, My Database has an Auction Table that has a calculated column "AuctionStatus" -
(case when [EndDateTime] < getdate() then '0' else '1' end)
that gives auction status Active or inactive based on End Date.
Now I want to call a stored procedure that sends email notifications to buyers and sellers as soon as AuctionStatus becomes '0'. For that i tried to create a after update trigger that could call the email notification sp, but i am not able to do so.
I am getting the following error message :-
Msg 2114, Level 16, State 1, Procedure trgAuctionEmailNotification,
Line 6 Column 'AuctionStatus' cannot be used in an IF UPDATE clause
because it is a computed column.
The trigger is:
CREATE TRIGGER trgAuctionEmailNotification ON SE_Auctions
AFTER UPDATE
AS
BEGIN
IF (UPDATE (AuctionStatus))
BEGIN
IF EXISTS (SELECT * FROM inserted WHERE currentbidderid > 0
AND AuctionStatus='0' )
BEGIN
DECLARE #ID int
SELECT #ID = AuctionID from inserted
EXEC spSelectSE_AuctionsByAuctionID #ID
END
END
END
You could just replace AuctionStatus with the corresponding expression :
IF EXISTS (SELECT * FROM inserted WHERE currentbidderid > 0 AND [EndDateTime] < getdate() )
But, the point is I don't see how your trigger will be "triggered" as [AuctionStatus] is never "updated". Its Value is just calculated whenever you need it.
You could go for a sql job that runs every x minutes and send a notification for each auction which ended during the last x minutes.
You need to add a real column containing a flag to indicate whether the notifications have been sent, and then implement a polling technique to scan the table for rows where the status is inactive and notifications haven't been sent.
The computed column doesn't really transition from one state to another, so it's not like an UPDATE has occurred. Even if SQL Server did implement this, it would be hideously expensive, since it would have to query the entire table for transitioning rows every 3ms. (or even more frequently if you're using datetime2 with a higher precision)
Whereas you can pick a suitable polling interval yourself. This could be an SQL agent job, or in some service code somewhere, whatever best fits the rest of your architecture.