I am wondering if there is a way to restrict MySQL INSERTs on a given condition, for example:
We have a table with projects and another table with employees. The maximum allowed people in a project is restricted to 5. How can we do this in MySQL?
Thanks in advance,
Marley
You can't enforce this constraint via the schema without breaking the relation (i.e. having 5 employees columns). MySQL does not allow CHECK constraints.
You can SELECT the number of employees separately before doing the query, or you can use INSERT ... SELECT with the restriction.
INSERT INTO Employees
(employee, column, names)
SELECT
?, ?, ?
FROM
Projects
NATURAL JOIN Employees
WHERE
projID = ?
GROUP BY
projID
HAVING
COUNT(empID) < 5
You have to read about constraint, check (or value based) type.
CREATE TABLE tbl (
...
numPeople INT CHECK (numPeople <= 5)
...
)
Yeah, sorry, check constraint doesn't work in MySQL
Possibly use a trigger to check the number of users prior to insert:-
DELIMITER $$
CREATE TRIGGER project_before_insert_allow_only_five_employees
BEFORE INSERT ON project FOR EACH ROW
BEGIN
IF (SELECT COUNT(employee_id) FROM project WHERE project_id=NEW.project_id) > 4
THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Cannot add or update row: only 5 employees on a project';
END IF;
END;
Not tested, and assuming that your project table has a row per employee (easy enough to modify if the project id is instead stored on the employees table for those associated with it).
Related
I'm attempting to create a trigger that increases the value of a column INCOME in the Salary database by 500 each time the value of WorkYear in the Employee table is increased by one year. For example, if the workYear is 4 and the salary is 1000, the salary should be 1500 if the workYear is increased by one year, 2000 if the workYear is increased by two years, and so on.
I tried to create such trigger and here is my code :
DELIMITER $$
create trigger increment AFTER UPDATE on employee
for each row
BEGIN
IF OLD.workYear <> new.workYear THEN
update salary
set income = (income + (new.workYear-old.workYear)*500);
END IF;
END$$
The idea behind this code is that after we update the workYear, the trigger should increase the salary by the difference of years * 500, (new.workYear-old.workYear)*500, but it increases all the rows by the same number, (5500 if we add one year, 27500 if we add two years, etc.) which not what we are looking for .
I am new to MySQL and would appreciate it if someone could assist me with this.
Thanks in advance
FaissalHamdi
In MySQL an AFTER trigger can affect the entire table, so you must declare the update scope in the form of criteria or a join.
Create Trigger in MySQL
To distinguish between the value of the columns BEFORE and AFTER the DML has fired, you use the NEW and OLD modifiers.
The concept is similar but each RDBMS has a slightly different syntax for this, be careful to search for help specifically on your RDBMS.
In the original query these special table references were used to evaluate the change condition however the scope of the update was not defined.
Assuming that there is a primary key field called Id on this salary table.
Also note that if you can, the query should be expressed in the form of a set-based operation, instead of static procedural script, this will be more conformant to other database engines.
So lets try this:
DELIMITER $$
create trigger increment AFTER UPDATE on employee
for each row
BEGIN
UPDATE salary s
SET income = (income + (new.workYear-old.workYear)*500)
WHERE s.Id = OLD.Id
END$$
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.
I have table called company. Structure as follows:
company
-----------------------------
company_id integer
company_name varchar
fk_company_type varchar
created_date date
fk_company_type is foreign key with the following values:
HQ
SITE
CUSTOMER
SUPPLIER
My issue I only want one record in company table to be HQ (HeadQuarters). Therefore I need a trigger that will count how many HQ in the company table. If it the count returns 1 and the new record being inserted has value of fk_company_type = HQ then the insert is aborted.
Any help on the best way to do this will be much appreciated. Also I already have a trigger which generates a UUID for the company_id and date. Hopefully this does not effect what I am trying to achieve.
phpadmin trigger (time: BEFORE, event: INSERT)
BEGIN
SET NEW.company_id=UUID_SHORT();
SET NEW.created_date=current_timestamp();
END
I've tried the basics layout and got as far as this but it produces a syntax error, here is how far I got.
BEGIN
IF fk_company_type = "HQ" THEN
DECLARE valid_number int;
SELECT COUNT(*) into valid_number FROM company WHERE fk_company_type = "HQ";
IF valid_number > 0 THEN
-- some error message
END IF;
END IF;
SET NEW.company_id=UUID_SHORT();
SET NEW.created_date=current_timestamp();
END
A simple way to abort an insert is to set a value to an illegal one.
for example if the column is set to NOT NULL then set the value to NULL and the insert will fail.
The question/issue of how you tell people it has failed depends on your application.
I have the following three (InnoDB-) tables in a MySQL database:
Entity (DLID)
Category (CatID, CatName)
hasCategory (DLID, CatID)
Now, upon insertion into the table hasCategory I would like to make sure, that each Entity is associated with at least one Category. Thus, I wrote the following trigger:
delimiter |
create trigger Max before insert on hasCategory for each row begin
if (exists (select distinct DLID from Entity where not exists (select distinct new.DLID from new))) then
signal sqlstate '45000'
set message_text = 'Min of 1 category per entity required';
end if;
end|
delimiter ;
Now, when I execute the following query: insert into hasCategory values (1, 1); I get the error error code 1146: table mydb.new does not exist. I have created some other triggers similar to this one, also referring to the new-table, where it worked perfectly well. Yet, I don't get it, what causes the error in this particular trigger.
Is it possible that the select statement causes some trouble? I've read that only select into statements are valid in procedures, but I don't know if this has got anything to do with this.
Thanks for your help!
select distinct new.DLID from new
there is no table new in your DB just as the error states. You can use NEW for the record that will be inserted in your trigger but you cannot use it as a table name and select from it.
try
if (select 1 from Entity where DLID = new.DLID) = 1 then
I have an auction application with these two tables (this is highly simplified, obviously):
create table auctions (
auction_id int,
end_time datetime
);
create table bids (
bid_id int,
auction_id int,
user_id int,
amount numeric,
bid_time timestamp,
constraint foreign key (auction_id) references auctions (auction_id)
);
I don't want bids on an auction after that auction has ended. In other words, rows in the bids table should be allowed only when the bid_time is earlier than the end_time for that auction. What's the simplest way to do this in MySQL?
Ufortunately MySQL does not have a CHECK constraint feature. But You should be able to enforce this using a trigger. However, MySQL trigger support isn't as advanced or well optimized as it is in other RDBMS-es, and you will suffer a considerable performance hit if you do it this way. So if this is a real-time trading system with massive amounts of concurrent users, you should look for another solution.
CREATE TRIGGER bir_bids
BEFORE INSERT ON bids
FOR EACH ROW
BEGIN
DECLARE v_end_time datetime;
-- declare a custom error condition.
-- SQLSTATE '45000' is reserved for that.
DECLARE ERR_AUCTION_ALREADY_CLOSED CONDITION FOR SQLSTATE '45000';
SELECT end_time INTO v_end_time
FROM auctions
WHERE auction_id = NEW.auction_id;
-- the condition is written like this so that a signal is raised
-- only in case the bid time is greater than the auction end time.
-- if either bid time or auction end time are NULL, no signal will be raised.
-- You should avoid complex NULL handling here if bid time or auction end time
-- must not be NULLable - simply define a NOT NULL column constraint for those cases.
IF NEW.bid_time > v_end_time THEN
SIGNAL ERR_AUCTION_ALREADY_CLOSED;
END IF;
END:
Note that the SIGNAL syntax is available only since MySQL 5.5 (currently GA). Triggers are available since MySQL 5.0. So if you need to implement this in a MySQL version prior to 5.5, you need to hack your way around not being able to raise a signal. You can do that by causing some change in the data that will guarantee the INSERT to fail. For instance you could write:
IF NEW.bid_time > v_end_time THEN
SET NEW.auction_id = NULL;
END IF;
Since acution_id is declared NOT NULL in the table, the state of the data will be such that it cannot be inserted. The drawback is that you will get a NOT NULL constraint violation, and the application will have to guess whether this is due to this trigger firing or due to a "real" NOT NULL constraint violation.
For more information, see: http://rpbouman.blogspot.nl/2009/12/validating-mysql-data-entry-with_15.html and http://rpbouman.blogspot.nl/2006/02/dont-you-need-proper-error-handling.html
Insert into bids (auction_id, user_id, amount, bid_time)
Select auction_id, [USER_ID], [AMOUNT], [TIMESTAMP]
From auctions
WHERE UNIX_TIMESTAMP() <= UNIX_TIMESTAMP(end_time)
Of course you have to replace the '[]' values
instead do a simple thing create a column name status. set its type to enum. when you want to block it update its value to 0. default should be 1 means open. 0 means closed