I'm using MySQL. I have a record on A table with a soft delete column: active with a value of 0.
This row is linked to 11 tables. All have the same active column.
I need to be sure that the record on A is deleted only if all the references across the 11 tables have active = 0 also.
I know I can write a view with these queries to get if I can "deleted" or not. But this is one example and IMO not very practical solution. Cascade update won't work either because I can't delete the parent row if any of the child is still active.
Thanks!
This should work if you create the active_view as you said you could. Just add the active flags of all the related tables into the foreign_active column, and you should be good to go.
CREATE TRIGGER before_update_student
BEFORE UPDATE ON student FOR EACH ROW
BEGIN
IF NEW.active = 0 AND (SELECT foreign_active FROM active_view
WHERE id = NEW.id) > 0
THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Cannot delete student when active roles exist.';
END IF;
END;
Related
I have table "Months" with 12 rows(obviously) and I want to add a trigger that prevent adding new row if ID number is over 12
CREATE TRIGGER "no_more_months" BEFORE INSERT ON "months"
FOR EACH ROW BEGIN
IF NEW.ID_Month>12 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'check constraint on Months failed';
END IF;
END;
But somehow it doesn't work and still adds new rows
As said in the comment section, just a constraint should do the job.
ALTER TABLE [dbo].[Months]
ADD CONSTRAINT CK_Months_ID_Months CHECK ([ID_Months] <= 12 AND [ID_Months] > 0);
If you absolutely need a custom error message look at this article : MySql Signals
I currently have two separate tables with a third to be added which store recipes, items and custom_meal (the one to be created).
I am looking to create a Meal Planner where the user may add either a recipe, item or custom item as their breakfast/lunch/dinner for a specific day.
I have an idea for my new tables below although going down this path I would have no way of ensuring that the meal_planner.id can only be either a recipe, item or custom.
With well written queries I realise that this should never be allowed to be an issue, although as someone with a great deal still to learn about databases I would much prefer a solution that ensures data may never be entered when it shouldn't be.
The current columns of interest on tables I'm already using are:
recipe.id
item.id
user.id
My current thoughts on the "Meal Planner" tables would be:
Table: meal_planner
id - primary key
user_id - foreign key user.id
mdate - the day which the meal is planned for
mtime - whether the meal is breakfast, lunch or dinner.
mcomment (can be null)
Table: meal_is_recipe
meal_id - foreign key meal_planner.id
recipe_id - foreign key recipe.id
Table: meal_is_custom
meal_id - foreign key meal_planner.id
custom_id - foreign key custom_meal.id
Table: meal_is_item
meal_id - foreign key meal_planner.id
item_id - foreign key item.id
Table: custom_meal
id - primary key
user_id - foreign key user.id
name - varchar to hold the name of the meal
This should allow me to use joins to grab all the required data for display although as mentioned it bugs me that their is no constraint stopping a meal.id being used in recipe, item and/or custom.
Ok so since posting this I have discovered "triggers".
Triggers are similar to a stored procedure although in a triggers case they will fire automatically when the set condition is met, for example before completing an insert, update or delete on a table.
In my case I have created six triggers, two for each meal_is_x table to check if the meal_id already exists in one of the other tables and return an error instead of the insert if it does.
I didn't have any luck through the phpmyadmin query using examples so I created my triggers in phpmyadmin by selecting the database, clicking on the Triggers tab, clicking add new trigger and then entering the following (modified slightly for each different table and whether it was for insert or update).
Trigger name: meal_is_recipe_BeforeInsert
Table: meal_is_recipe
Time: BEFORE
Event: INSERT
Definition:
BEGIN
DECLARE custom_match INT;
DECLARE item_match INT;
SELECT COUNT(1) INTO custom_match FROM meal_is_custom
WHERE meal_id = NEW.meal_id;
IF custom_match > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'A Custom record with that meal id already exists';
ELSE
SELECT COUNT(1) INTO item_match FROM meal_is_item
WHERE meal_id = NEW.meal_id;
IF item_match > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'An Item record with that meal id already exists';
END IF;
END IF;
END
Definer: I left this blank as the user that made the query if fine to run the trigger.
I believe I should also have been able to get the same result with the MySQL query but as I mention this did not work straight away for me in phpmyadmin and rather than look into it further the triggers tab was simple enough:
DELIMITER //
CREATE TRIGGER meal_is_recipe_BeforeInsert BEFORE INSERT ON meal_is_recipe
BEGIN
DECLARE custom_match INT;
DECLARE item_match INT;
SELECT COUNT(1) INTO custom_match FROM meal_is_custom
WHERE meal_id = NEW.meal_id;
IF custom_match > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'A Custom record with that meal id already exists';
ELSE
SELECT COUNT(1) INTO item_match FROM meal_is_item
WHERE meal_id = NEW.meal_id;
IF item_match > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'An Item record with that meal id already exists';
END IF;
END IF;
END//
DELIMITER ;
As I mentioned in the question my knowledge working with databases still has a long way to go so whilst I have tested this with my initial question schema and it works, I cannot guarantee this is the most efficient way of achieving the outcome.
Is there a way to check if the rows already exist and is active?
Let say the rows look like that:
ID: 123456
Active : 1
I know I could do something like that:
SELECT * FROM TableName WHERE id=123456 AND Active=1;
But that's not what I want.
And if I try to insert a query into the table again with the same information it should not duplicate.
But the constraint should work if it's not active (0).
I don't want to do the verification code wise. I would like the database server to do the verification.
Thanks!
You could use a trigger before insert to block the insert if and only if there is already an active row.
create trigger tr_bi_TableName
before insert on TableName
for each row
begin
if((select count(*) from TableName where id = new.id and active = 1) > 0) then
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'There is already an active row for this id.';
end if
end
SQLState 45000 means unhandled user-defined exception.
See documentaton for more info on signal.
Hello, every one :)!
I'll try and keep this as simple as possible, basically, I have one table that references itself via a parent_id column. Each row in the table can have a parent and can keep count of how many children it has via the count column. So essentially what I'm trying to do is have the triggers update each parent row's count column when necessary
The problem is that the update trigger gets called when the update operation in the insert trigger gets called. Then I get:
"General error: 1442 Can't update table 'term_taxonomies' in stored function/trigger because it is already used by statement which invoked this stored function/trigger".
Any ideas?
Actual code:
TRIGGER `dbname`.`ai_term_taxonomies`
AFTER INSERT ON `dbname`.`term_taxonomies`
FOR EACH ROW
BEGIN
IF NEW.parent_id NOT 0 THEN
UPDATE term_taxonomies as termTax SET assocItemCount = (assocItemCount + 1)
WHERE termTax.term_taxonomy_id = NEW.parent_id;
END IF;
END$$
CREATE
TRIGGER `dbname`.`au_term_taxonomies`
AFTER UPDATE ON `dbname`.`term_taxonomies`
FOR EACH ROW
BEGIN
IF NEW.parent_id NOT OLD.parent_id THEN
IF NEW.parent_id NOT 0 THEN
UPDATE term_taxonomies as termTax SET assocItemCount = (assocItemCount + 1)
WHERE termTax.term_taxonomy_id = NEW.parent_id;
END IF;
IF OLD.parent_id NOT 0 THEN
UPDATE term_taxonomies as termTax SET assocItemCount = (assocItemCount - 1)
WHERE termTax.term_taxonomy_id = OLD.parent_id;
END IF;
END IF;
END$$
All mysql triggers execute in the same transaction as the triggering statement.
You want to update using the SET NEW.assocItemCount syntax as opposed to performing an UPDATE statement on the underlying table.
Edit: However, in your case this is not possible because you are updating a different row in the same table, the hardest thing to do in a mysql trigger. Sorry.
You will have to change your schema. Take assocItemCount out of your table, create a new table holding just term_taxonomy_id and assocItemCount, and update that using an UPDATE statement from your query. It is also possible to use a view joining these two tables to hide this detail if a query needs to use your original schema.
Alternatively, if you did not have assocItemCount in your database at all, you would still be able to compute it in any queries, and your database would be better normalized than it is now.
I have a row in a table that I do not want to be changed (ever).
Is it possible to set a MySQL row to READ-ONLY so that it cannot be updated in any way? If so, how?
If not, is it possible to set a permanent value in one of the columns of that row so that it cannot be changed? If so, how?
Thanks.
This is likely to be business logic, which probably doesn't belong in your data storage layer. However, it can nonetheless be accomplished using triggers.
You can create a BEFORE UPDATE trigger that raises an error if a "locked" record is about to be updated; since an error occurs before the operation is undertaken, MySQL ceases to proceed with it. If you also want to prevent the record from being deleted, you'd need to create a similar trigger BEFORE DELETE.
To determine whether a record is "locked", you could create a boolean locked column:
ALTER TABLE my_table ADD COLUMN locked BOOLEAN NOT NULL DEFAULT FALSE;
DELIMITER ;;
CREATE TRIGGER foo_upd BEFORE UPDATE ON my_table FOR EACH ROW
IF OLD.locked THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot update locked record';
END IF;;
CREATE TRIGGER foo_del BEFORE DELETE ON my_table FOR EACH ROW
IF OLD.locked THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete locked record';
END IF;;
DELIMITER ;
UPDATE my_table SET locked = TRUE WHERE ...;
Note that SIGNAL was introduced in MySQL 5.5. In earlier versions, you must perform some erroneous action that causes MySQL to raise an error: I often call an non-existent procedure, e.g. with CALL raise_error;
I cannot create an additional column on this table, but the row has a unique id in one of the columns, so how would I do this for that scenario?
Again, if you absolutely must place this logic in the storage layer—and cannot identify the locked records through any means other than the PK—you could hard-code the test into your trigger; for example, to "lock" the record with id_column = 1234:
DELIMITER ;;
CREATE TRIGGER foo_upd BEFORE UPDATE ON my_table FOR EACH ROW
IF OLD.id_column <=> 1234 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot update locked record';
END IF;;
CREATE TRIGGER foo_del BEFORE DELETE ON my_table FOR EACH ROW
IF OLD.id_column <=> 1234 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Cannot delete locked record';
END IF;;
DELIMITER ;
But this is absolutely horrible and I would do almost anything to avoid it whenever possible.