This trigger is supposed to update the free rooms available for a hospital.When a patient leaves the trigger adds one more free room.But it just doesn't work and I really cannot understand why.I use these tables for this trigger:
create table Incidents
(primary key(incident_code),
foreign key(patient_code) references Patients(patient_code)on delete cascade on update cascade,
foreign key(section_code) references Sections(section_code)on delete cascade on update cascade,
foreign key(doctor_code) references Doctors(doctor_code)on delete cascade on update cascade,
incident_code int unsigned,
patient_code int unsigned,
section_code int unsigned,
doctor_code int unsigned,
import_day date not null,
export_day date,
incident_value real not null
check(datediff(export_day,import_day)>-1));
(primary key(section_code),
section_code int unsigned,
section_name varchar(30) not null,
total_rooms int not null,
free_rooms int not null
check(total_rooms>=free_rooms));
this is the triggers code:
delimiter #
create trigger FreeRooms1
after update on incidents
for each row
begin
if (old.export_day=null and new.export_day != null)
then
update sections
set free_rooms = free_rooms + 1
where sections.section_code = incidents.section_code;
end if;
end;
#
And this is how I try to activate it:
update incidents
set export_day = '2013/12/24'
where incident_code = 2;
select section_code,section_name,free_rooms
from sections;
I tried many variations for the trigger but any change I make doesn't make work.Any help?
Sorry if my question seems bad I am fairly new to mySQL and I can't find an answer anywhere.
I think you should use:
delimiter #
create trigger FreeRooms1
after update on incidents
for each row
begin
if (old.export_day IS NULL and new.export_day IS NOT NULL)
then
update sections
set free_rooms = free_rooms + 1
where sections.section_code = NEW.section_code;
end if;
end;
#
and reference the new row.
Related
I try to use View to create a constraint on a Mysql Table.
I have one table with cinemas sessions and one other for tickets, the goal is we can't sell tickets when the session is full.
The session has a number of places, for example, 300 places. When one ticket is sold, the number of places decreases. The rest is 299. (this is what I do with a view)
I will check before the data is written if the amount of places is enough.
The condition is place_left >= number (of tickets wanted).
For example, the rest of places is 5 and a user wants 6 places, the request is rejected because the condition is not true.
The condition is place_left >= number (of tickets wanted).
Actually I use two views to do that (not tested, just created), I think I can do this in one shot but I failed.
I have seen this possibility here:
https://dev.mysql.com/doc/refman/8.0/en/view-check-option.html
I first tried something like that:
CREATE VIEW view_place_left AS
SELECT sessions.id,
(SELECT places FROM rooms WHERE id=sessions.room_id)-(SELECT SUM(number) AS count FROM tickets WHERE session_id=sessions.id)
AS place_left FROM sessions WHERE place_left>=0
WITH CHECK OPTION;
, but the part WHERE place_left>=0 is not working.
So I do the job in two steps
The first view:
CREATE VIEW view_place_left
AS SELECT sessions.id,
(SELECT places FROM rooms WHERE id=sessions.room_id)-(SELECT SUM(number) AS count FROM tickets WHERE session_id=sessions.id)
AS place_left FROM sessions;
And the second view:
CREATE VIEW check_view_place_left AS
SELECT id, place_left FROM view_place_left WHERE place_left>=0
WITH CHECK OPTION;
That seems ok, but I think it's possible to do the job in a single view, someone can help?
I work with MySql 8.0
First Add :
create sessions table :
CREATE TABLE IF NOT EXISTS sessions
(
id VARCHAR(36) NOT NULL PRIMARY KEY DEFAULT (UUID()),
movie_id VARCHAR(36),
room_id VARCHAR(36),
date DATETIME,
FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
Create tickets table :
CREATE TABLE IF NOT EXISTS tickets
(
id VARCHAR(36) NOT NULL PRIMARY KEY DEFAULT (UUID()),
session_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
price_id VARCHAR(36) NOT NULL,
number INT NOT NULL,
date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
paid INT,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (price_id) REFERENCES prices(id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
SOLUTION
So i found my solution.
To Check and validate the place left, i just need a trigger before insert.
Here is my code :
\! echo "\033[33m*** Create trigger on 'tickets' ***\033[m";
DELIMITER $$
CREATE TRIGGER check_places BEFORE INSERT ON tickets
FOR EACH ROW
BEGIN
IF PlaceLeft(NEW.session_id, NEW.number) != 1 THEN SIGNAL sqlstate '45000' set message_text = "No enough places";
END IF;
END$$
DELIMITER ;
The function PlaceLeft
DELIMITER $$
CREATE FUNCTION PlaceLeft(session_id VARCHAR(36), number INT)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE places INT;
SELECT place_left INTO places FROM view_place_left;
IF places>=number THEN RETURN 1;
ELSE RETURN 0;
END IF;
END$$
Maybe that can help another lost soul :)
I have multiple tables in this database; two of which are involved with this trigger
create table shipment_item(
shipmentID int not null,
shipmentItemID int not null,
purchaseID int not null,
insuredValue decimal(5,2) not null,
constraint shipment_ItemPK primary key(shipmentID, shipmentItemID),
constraint shipmentFK foreign key(shipmentID)
references shipment(shipmentID)
on delete cascade,
constraint purchaseFK foreign key(purchaseID)
references purchase(purchaseID)
);
create table purchase(
purchaseID int not null auto_increment,
storeID int not null,
purchaseDate date not null,
description char(30) not null,
category char(30) not null,
price decimal(5,2) not null,
constraint purchasePK primary key(purchaseID),
constraint storeFK foreign key(storeID)
references store(storeID)
);
I'm trying to implement a trigger in my MySQL database. That trigger looks like this
DELIMITER //
CREATE TRIGGER checkInsuranceTrigger
BEFORE INSERT ON shipment_item
FOR EACH ROW BEGIN
IF(shipment_item.insuredValue <= purchase.price) THEN
SET NEW.insuredValue = purchase.price;
END IF;
END
//
DELIMITER ;
When I implement this trigger and then try to insert data into the shipment_item table I get the following error
Error Code 1109: Unknown Table 'shipment_item' in field list
Reference the column in the row being inserted with the NEW keyword, like you did on the SET statement.
To reference values from rows in other tables, you need a SQL statement, in your case, looks like you want a SELECT.
For example (following the outline of the logic in your trigger), something like this:
BEGIN
-- local variable
DECLARE ln_purchase_price DECIMAL(5,2);
-- populate local variable (this is just an example of one way to do this)
SELECT p.price
INTO ln_purchase_price
FROM purchase p
WHERE p.purchaseID = NEW.purchaseID
LIMIT 1;
-- compare value from row to local variable
IF (NEW.insuredValue <= ln_purchase_price) THEN
SET NEW.insuredValue = ln_purchase_price;
END IF;
May I suggest verifying that the table really exists in the same database as the trigger itself?
For example I have a table which is used for logging. So very old data is useless and there are no reasons to leave it in the table. I want create a trigger which will delete old rows if number of existing rows more than 10 for example. What I already have:
CREATE TABLE log (
logId INT UNSIGNED NOT NULL AUTO_INCREMENT,
firstLogin DATETIME NOT NULL,
lastLogin DATETIME NOT NULL,
fingerprint VARCHAR(64) CHARACTER SET BINARY,
ip VARCHAR(24) NOT NULL,
accountId INT UNSIGNED NOT NULL,
FOREIGN KEY (accountId)
REFERENCES accounts (accountId)
ON UPDATE CASCADE ON DELETE CASCADE,
PRIMARY KEY (logId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DELIMITER |
CREATE TRIGGER logbeforeinsert BEFORE INSERT ON log
FOR EACH ROW
BEGIN
SET #rowcount = (SELECT COUNT(*) FROM log WHERE accountId = NEW.accountId);
IF #rowcount > 9 THEN
DELETE FROM log WHERE accountId = NEW.accountId LIMIT 1;
END IF;
END;
|
DELIMITER ;
But with this trigger inserting stopped at all after number of rows had reached 10.
Your trigger tries to write to the same table (DELETE is write access), that it is inserting into - this is not supported.
As you have a BEFORE INSERT trigger, failure of the trigger means failure of the INSERT.
You need either to trigger the delete from an operation, that does not write to log, or rethink your model.
I've got this mysql table:
CREATE TABLE IF NOT EXISTS `activities` (
`id` INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT,
`name` CHAR(255) NOT NULL,
`status` ENUM('open','progress','close'),
`date_begin` DATE,
`date_finish` DATE,
`progress` TINYINT,
`reliance` INTEGER,
`parent` INTEGER,
FOREIGN KEY(owner) REFERENCES users(id) ON UPDATE CASCADE ON DELETE SET NULL,
FOREIGN KEY(reliance) REFERENCES activities(id) ON UPDATE CASCADE ON DELETE SET NULL,
FOREIGN KEY(parent) REFERENCES activities(id) ON UPDATE CASCADE ON DELETE SET NULL
)ENGINE = INNODB;
My problem is when i want to update the date_begin of one activity. Infact i would like to update the date of begin of all activities that are reliant or child of the updated activity.
Can i force mysql to create a recursive trigger?
Something like this should work, at least if you're on a recent version of MySQL:
delimiter |
CREATE TRIGGER activitiesUpdateReliantAndChildren BEFORE UPDATE ON 'activities'
FOR EACH ROW
BEGIN
update 'activities'
set date_begin = NEW.date_begin
where reliance = NEW.id;
update 'activities'
set date_begin = NEW.date_begin
where parent = NEW.id;
END;
|
delimiter ;
Although the trigger may work...it is not a good practice to create recursive triggers! and if u thing you must... then think not only twice but 100times before applying! They can result in more harm than good sometimes.
I imagine that I have designed my database badly, but I'm currently stumped by the fact that I need to use dynamic sql in a trigger and that's making mysql unhappy.
The context is that I have created a membership database with several dozen tables, the main one of which is the 'member' table with a unique primary key 'id'. There are a number of other tables which have foreign keys referring to the member.id field.
Because the data has been gathered over many years and with little dupe-control, there is another field in the 'member' table called 'superseded_by', which contains the id of the member who supersedes this one. By default, superseded_by is set to be the member_id. Any one whose superseded_by <> id is deemed to be a dupe.
Now the tricky part... when we identify a dupe, we want to set the superseded_by field to point to the new primary member and update all the tables with foreign keys pointing to the now redundant member id. I have tried to do this using an after update trigger... and then I've tried to be clever by querying the foreign keys from the information_schema and using dynamic sql to update them.
This clearly doesn't work (Error Code: 1336 Dynamic SQL is not allowed in stored function or trigger).
I'm assuming there is a better way to design the schema / handle dupes which I haven't thought of.
Help please...
CODE SNIPPET:
-- ---
-- Table 'member'
-- ---
DROP TABLE IF EXISTS member;
CREATE TABLE member (
id INTEGER AUTO_INCREMENT,
superseded_by INTEGER DEFAULT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
date_of_birth DATE DEFAULT NULL,
gender ENUM('M', 'F') DEFAULT NULL,
mailing_address_id INTEGER DEFAULT NULL,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (mailing_address_id) REFERENCES mailing_address (id),
FOREIGN KEY (superseded_by) REFERENCES member (id)
);
DELIMITER $$
CREATE TRIGGER set_superseded_by_on_insert BEFORE INSERT ON member FOR EACH ROW
BEGIN
SET NEW.superseded_by = NEW.id;
END$$
-- Trigger to update other tables (volunteers, donations, presenters, etc.) when member's superseded_by record is updated
-- Assumes the new superseding person exists (they should also not be superseded by anyone themselves)
CREATE TRIGGER adjust_foreign_member_keys_on_superseded_by_update AFTER UPDATE ON member FOR EACH ROW
BEGIN
DECLARE db, tbl, col VARCHAR(64);
DECLARE fk_update_statement VARCHAR(200);
DECLARE no_more_rows BOOLEAN;
DECLARE fks CURSOR FOR SELECT kcu.TABLE_SCHEMA, kcu.TABLE_NAME, kcu.COLUMN_NAME
FROM information_schema.TABLE_CONSTRAINTS tc
JOIN information_schema.KEY_COLUMN_USAGE kcu ON
tc.table_schema = kcu.table_schema AND tc.constraint_name = kcu.constraint_name
WHERE tc.constraint_type='FOREIGN KEY' AND
kcu.REFERENCED_TABLE_NAME = 'member' AND
kcu.REFERENCED_COLUMN_NAME = 'id';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_rows = TRUE;
IF NEW.superseded_by <> OLD.superseded_by THEN
OPEN fks;
SET no_more_rows = FALSE;
update_loop: LOOP
FETCH fks INTO db, tbl, col;
IF no_more_rows THEN
LEAVE update_loop;
END IF;
SET #fk_update_statement = CONCAT("UPDATE ", db, ".", tbl, " SET ", col, " = NEW.superseded_by WHERE ", col, " = NEW.id;");
PREPARE stmt FROM #fk_update_statement;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE fks;
END IF;
END$$
DELIMITER ;
Why are you trying to maintain duplicates in your main table? Seems like you'd be better off with a member table and a member_history table to track previous changes. You could do it by having a table that stored the field changed, date changed and the old and new values. Or you could just store the previous snapshot of the member table before updating it. For instance:
INSERT INTO member_history SELECT NULL, * FROM member WHERE id = ?
UPDATE member SET [...] WHERE id = ?
The schema for member_history would be nearly identical except that you would store member.id as member_id and have a separate primary key for each history entry. (Note: I'm glossing over the syntax a little, the NULL, * part might not work in which case you may need to explicitly name all the fields. Haven't taken the time to check it).
CREATE TABLE member (
id INTEGER AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
date_of_birth DATE DEFAULT NULL,
gender ENUM('M', 'F') DEFAULT NULL,
mailing_address_id INTEGER DEFAULT NULL,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (mailing_address_id) REFERENCES mailing_address (id),
);
CREATE TABLE member_history (
id INTEGER AUTO_INCREMENT,
member_id INTEGER NOT NULL,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
date_of_birth DATE DEFAULT NULL,
gender ENUM('M', 'F') DEFAULT NULL,
mailing_address_id INTEGER DEFAULT NULL,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (member_id) REFERENCES member (id),
);
Notice that I removed the superseded_by field in the member table and the foreign key to mailing_address in the member_history table. You shouldn't need the superseded_by any more and keeping the foreign key in the member_history table isn't really necessary unless you're worried about dangling references in your history.
Ok, just a couple of thoughts on this:
superseded_by is referencing id on the same table and is in general equal to the latter - not in those cases where you were able to identify a dupe, though, in which case it would point to another already existing member's id.
Given that we can safely assume that no superseded_by field will ever hurt the foreign key constraint.
I further assume that id and superseded_by fields of dupes that have not been identified yet are equal.
So, if all of the above is true, you may bend the foreign key of the other related tables to reference superseded_by instead of id. This way you could cascade the changes made to the dupe down to the other tables and still have the exact same constraint as before.
What you think? Am I missing something?
Please note that this is an option only if you are using InnoDB rather than MyISAM.
Regards,
aefxx
Trigger and stored function in mysql have limitations that we can not use dynamic sql in both of these. I hope this helps.