I've created a Database for a DVD store which contains a Film table to hold info about films + copies available, and a sale table. I've created sequences for the Film and Sale table, to auto-increment the Primary keys FILM_ID and SALE_TRANSACTION_REF respectively, using the following code.
CREATE SEQUENCE Film_Seq
MINVALUE 1
START WITH 1
INCREMENT BY 1
NOCACHE;
CREATE OR REPLACE TRIGGER New_Film
BEFORE INSERT ON FILM
FOR EACH ROW
BEGIN
SELECT Film_Seq.NEXTVAL
INTO :new.FILM_ID
FROM dual;
END;
/
This seems to work handy enough, but when I try to use a trigger to decrement the COPIES_AVAILABLE attribute of the film in Film after a sale with the following code, I get an error:
CREATE OR REPLACE TRIGGER After_Sale
AFTER INSERT ON SALE
DECLARE
ID NUMBER(38) := Sale_Seq.CURRVAL;
BEGIN
UPDATE FILM
SET COPIES_AVAILABLE = COPIES_AVAILABLE - 1
FROM FILM, SALE
WHERE FILM.FILM_ID = SALE.FILM_ID
AND (SALE.SALE_TRANSACTION_REF = ID);
END;
/
I actually get two errors, the first one relates to the declare statement, which is
4/2 PL/SQL: SQL Statement ignored
And the second one has to do with the UPDATE statement and it is
6/2 PL/SQL: ORA-00933: SQL command not properly ended
I've wrecked my brains trying to construct this statement differently, and have searched the documentation but can't seem to find the answer, and help would be greatly appreciated.
Also, looking over my question it may not be obvious but I have already created a sale_seq.
Sorry, it may not be evident what I am trying to achieve here. Whenever a Film is sold and added to the sale table, I want to automatically decrement the Copie_Available for the corresponding film in the film table, so if I left out the AND AND (SALE.SALE_TRANSACTION_REF = Sale_Seq.CURRVAL) clause the trigger will complie grand, but will decrement all copies available for all films in the film table, which is incorrect.
try it like this:
CREATE OR REPLACE TRIGGER After_Sale
AFTER INSERT ON SALE
FOR EACH ROW
BEGIN
UPDATE FILM
SET COPIES_AVAILABLE = COPIES_AVAILABLE - 1
WHERE FILM.FILM_ID = :NEW.FILM_ID
END;
Why save COPIES_AVAILABLE to your database. Why wouldn't you detract for instance [all copies in stock] - [all sold copies] if you want to know available copies.
Related
I am working on a Postgres update transaction.
Let's say I have two tables: events and ticket_books with event booking types. The ticket_books table has a foreign key pointing to the events.
I need to update an event stored in the database, including booking type records from the ticket_books table.
To deal with cascading update and delete, I decided to build a transaction, in a "pseudo-code" it looks like:
BEGIN;
DELETE FROM
ticket_books
WHERE
event_id = ${req.params.id} AND
id NOT IN (${bookingIds})
FOR booking IN json_to_recordset('${JSON.stringify(book)}') as book(id int, title varchar(200), price int, ...) LOOP
IF bookind.id THEN
UPDATE
ticket_books
SET
title = booking.title, price = booking.price
WHERE
event_id = ${req.params.id};
ELSE
INSERT INTO
ticket_books (title, price, qty_available, qty_per_sale)
VALUES
(booking.title, booking.price, booking.qty_available, booking.qty_per_sale)
RETURNING
id
END IF;
END LOOP;
UPDATE
event
SET
...
WHERE
id = ...
RETURNING
id;
COMMIT;
I currently get the error: syntax error at or near "json_to_recordset". I never used json_to_recordset or friends before, just saw from the document that from 9.3 and later those are available. Unsure how to get Postgres to understand what I need, though.
I am embedding a JSON array so the final line looks like:
FOR booking IN json_to_record('[{"id":13,"description":"Three day access to the festival","title":"Three Day General Admission","price":260,"qty_available":5000,"qty_per_sale":10},{"id":14,"description":"Single day access to the festival","title":"Single Day General Admission","price":"90.90","qty_available":2000,"qty_per_sale":2},{"title":"Free Admission","price":"0.00","qty_available":0,"qty_per_sale":0}]')
I believe that my JSON array is valid. Apparently, this is not how I should be passing it to the Postgres. What should I be doing instead? My goal is to iterate over the array entries. If there is an integer value for booking.id, I want to update the record, else insert a new one.
You need a query, and a standalone function call usually does not count as a query:
FOR booking IN select * from json_to_recordset(...
Also, you can't use BEGIN to start a transaction in plpgsql. It is only used to start a block. If you are using a procedure rather than a function, then you can COMMIT but then a new transaction starts immediately with no BEGIN token being used.
You are also missing a semicolon between the DELETE and the FOR, but from the error message that seems to be missing from only your post, and not from your actual code.
I'm in trouble with the implementation of a trigger.
Assuming that I have two types:
CREATE TYPE customer_t AS OBJECT(
code INTEGER,
name VARCHAR(20),
surname VARCHAR(20),
age INTEGER);
and the type
CREATE TYPE ticket_t AS OBJECT (
price INTEGER,
cust REF customer_t
)
And then I have the associate tables:
CREATE TABLE customers OF TYPE customer_t
CREATE TABLE tickets OF TYPE ticket_t
I have to do an exercise so I have to create a trigger for ensure that a customer won't buy more than 10 tickets but, if I use command like "select count(*)" I get an error because I can't access to mutating table.
Please can anyone help me with this trigger?
EDIT:
I populated the tables as follows:
INSERT INTO custs (code, name, surname, age) values (123, 'Paolo', 'Past', 32);
and repeating the following operation ten times:
INSERT INTO tickets (price, cust) values
(4, (SELECT * FROM (SELECT REF(T) FROM custs T WHERE name = 'Paolo' AND surname = 'Past') WHERE rownum < 2))
The trigger implemented is:
create or replace
trigger check_num_ticket after insert on tickets
for each row
declare
num_ticket number;
begin
SELECT count(*) INTO num_ticket FROM tickets WHERE :new.cust = cust;
if (num_ticket >= 10) then
raise_application_error('-20099', 'no ticket available');
end if;
end;
And I get this error:
A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
You are getting the mutating table error, because you are inserting in the same table where you want to get the row count for. Imagine your insert statement inserts two rows. There is no rule which row to insert first and which last, but your trigger fires on one inserted row and wants to know how many rows are already in the table. The DBMS tells you this is undefined, as the table is currently mutating.
You need an after statement trigger instead of a before row trigger. So when the insert statement's inserts are done, you look at the table to see whether there are suddenly customers with too many rows in it.
(A great alternative is a compound trigger. It combines row and statement triggers. So in the after row section you'd remember the customers in some array/collection and in the after statement section you'd look up the table for only the remembered customers.)
I'm a new SQL developer. After recommendations I have altered my trigger (for this task I need to use a trigger so can't avoid it), but I have re-altered my trigger. I want it to prevent a duplication in the Rentals table of the BikeID foreign key contained within it.
This is my code at the moment:
CREATE TRIGGER BikeNotAvailable
ON dbo.SA_Rental
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM SA_Rental
INNER JOIN inserted i ON i.BikeID = dbo.SA_Rental.BikeID)
BEGIN
ROLLBACK
RAISERROR ('This bike is already being hired', 16, 1);
END
go
But when I enter the BikeID in the Rentals table, even though the BikeID is not present inside a row yet, it still raises the error - why? (I have also tested this on an empty table and it still raises the error)
Just some context on my data, the BikeID is a primary key from the 'Bike' table that is shared as a foreign key to the Rentals table, not sure if this has anything to do with the error.
Can someone please help me fix this trigger so it works.
Thanks.
Well, as it's an AFTER trigger, the trigger is run after the new record is added to the table (at least visible for your trigger).
Supposing that your table has an automatically generated ID column, you should exclude the inserted row from your check like this:
CREATE TRIGGER BikeNotAvailable ON dbo.SA_Rental
AFTER INSERT
AS
if exists ( select * from SA_Rental
inner join inserted i on i.BikeID=dbo.SA_Rental.BikeID
where SA_Rental.RentalID <> i.RentalID)
begin
rollback
RAISERROR ('This bike is already being hired', 16, 1);
end
go
A far simpler way to achieve what you are after is to create a unique index:
CREATE UNIQUE INDEX BikeRented ON SA_Rental (BikeID);
This, of course, assumes that you delete the row from your table when the bike is no longer rented (as this is the implied logic in your post). If this is not the case, then we need more detail; what specifies on your table that the rental has completed?
If we assume you have a return date, and the return date is NULL when the bike is yet to be returned, then you would use a filtered index like so:
CREATE UNIQUE INDEX BikeRented ON SA_Rental (BikeID)
WHERE ReturnedDate IS NULL;
I have 2 tables (1) product_warehouse and (2) Return_Vendor Invoice.
i have to update the quantity in product_warehouse by trigger according to the value of Return_Vendor Invoice Table.
where Item_code is unique key in both tables.
For example if the product_warehouse contain 3 quantities , and the shopkeeper returns 1 quantity to vendor then it should be 2 in the Product_warehouse. update query will also acceptable.
create or replace trigger tiuda_return_vendor after insert or update or delete
on return_vendor is
begin
update product_wherehouse
set
quantity = quantity - (:new.quantity - nvl(:old.quantity, 0)
where
item_code = nvl(:new.item_code, :old.item_code);
end;
This trigger works in most cases. You can insert update or delete the return line.
Only thing is: when updating, you cannot update the item_code itself, because the update statement doesn't take that into account. You could easily solve that, but I don't know if it's in your requirements. I usually don't change values like that, but rather remove the item and add a new line for a different item.
Updating the quantity works fine. If you update the quantity, the difference between old and new is calculated and that difference is used to modify the stock quantity in the wherehouse.
create or replace
TRIGGER "WR_RETURN_INVOICE_UPDATE_TRG"
AFTER UPDATE ON RETURN_INVOICE
FOR EACH ROW
BEGIN
UPDATE PRODUCT_WAREHOUSE
SET QUANTITY=QUANTITY-:OLD.QUANTITY
WHERE ITEM_CODE=:OLD.ITEM_CODE;
END WR_RETURN_INVOICE_UPDATE_TRG;
Try this one it will works.
I have two tables
moduleprogress which contains fields:
studentid
modulecode
moduleyear
modules which contains fields:
modulecode
credits
I need a trigger to run when the user is attempting to insert or update data in the moduleprogress table.
The trigger needs to:
look at the studentid that the user has input and look at all modules that they have taken in moduleyear "1".
take the modulecode the user input and look at the modules table and find the sum of the credits field for all these modules (each module is worth 10 or 20 credits).
if the value is above 120 (yearly credit limit) then it needs to error; if not, input is ok.
Does this make sense? Is this possible?
#a_horse_with_no_name
This looks like it will work but I will only be using the database to input data manually so it needs to error on input. I'm trying to get a trigger similar to this to solve the problem(trigger doesn't work) and forget that "UOS_" is before everything. Just helps me with my database and other functions.
CREATE OR REPLACE TRIGGER "UOS_TESTINGS"
BEFORE UPDATE OR INSERT ON UOS_MODULE_PROGRESS
REFERENCING NEW AS NEW OLD AS OLD
DECLARE
MODULECREDITS INTEGER;
BEGIN
SELECT
m.UOS_CREDITS,
mp.UOS_MODULE_YEAR,
SUM(m.UOS_CREDITS)
INTO MODULECREDITS
FROM UOS_MODULE_PROGRESS mp JOIN UOS_MODULES m
ON m.UOS_MODULE_CODE = mp.UOS_MODULE_CODE
WHERE mp.UOS_MODULE_YEAR = 1;
IF MODULECREDITS >= 120 THEN
RAISE_APPLICATION_ERROR(-20000, 'Students are only allowed to take upto 120 credits per year');
END IF;
END;
I get the error message :
8 23 PL/SQL: ORA-00947: not enough values
4 1 PL/SQL: SQL Statement ignored
I'm not sure I understand your description, but the way I understand it, this can be solved using a materialized view, which might give better transactional behaviour than the trigger:
CREATE MATERIALIZED VIEW LOG
ON moduleprogress WITH ROWID (modulecode, studentid, moduleyear)
INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW LOG
ON modules with rowid (modulecode, credits)
INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW mv_module_credits
REFRESH FAST ON COMMIT WITH ROWID
AS
SELECT pr.studentid,
SUM(m.credits) AS total_credits
FROM moduleprogress pr
JOIN modules m ON pr.modulecode = m.modulecode
WHERE pr.moduleyear = 1
GROUP BY pr.studentid;
ALTER TABLE mv_module_credits
ADD CONSTRAINT check_total_credits CHECK (total_credits <= 120)
But: depending on the size of the table this might however be slower than a pure trigger based solution.
The only drawback of this solution is, that the error will be thrown at commit time, not when the insert happens (because the MV is only refreshed on commit, and the check constraint is evaluated then)