MYSQL Trigger based on query - mysql

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.

Related

How to add a integrity check on mySQL Workbench

I'm new to mySQL and I'd like to add a integrity check (or constraint? Sorry I'm italian) in my database.
Let me explain: I have two tables
Workshop (location, numPlaces, numOperations)
Operation (idoperation, workLocation, ...)
numPlaces represents the maximum number of operations the workshop can afford. I created a trigger that everytime I insert a new record in Operation, the numOperations of Workshop referred to that specific location is increased by 1.
Now what I'd like to do is: when numOperations = numPlaces, if I try to insert a new record to Operation, the system must tell me that I can't. Basically, it can't be possible that numOperations > numPlaces
Is there a way to achieve that? Sorry if I can't provide codes, but I literally have no idea where should I go to create these types of CHECKS. Hope you can help me!
For this to work, you must have set the workshop with the correct number of places.
And you should have a routine, that diminishes the number of operations so that you can enter new operation in one workshop
CREATE TABLE Workshop
(location Text, numPlaces int , numOperations int
)
INSERT INTO Workshop VALUES ('A',9,8)
CREATE TABLE Operation (idoperation int, workLocation Text)
CREATE TRIGGER before_Operation_insert
BEFORE INSERT
ON Operation FOR EACH ROW
BEGIN
DECLARE Placescount_ INT;
DECLARE Operationscount_ INT;
SELECT numPlaces, numOperations
INTO Placescount_,Operationscount_
FROM Workshop WHERE location = NEW.workLocation;
IF Placescount_ < Operationscount_ THEN
UPDATE Workshop
SET numOperations = numOperations + 1 WHERE location=new.workLocation ;
ELSE
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Maximum Number of Operarations reached in location ';
END IF;
END
INSERT INTO Operation VALUES (1,'A')
Maximum Number of Operarations reached in location
db<>fiddle here

How can I ensure multiple table columns are unique (or restructure my tables) in MySQL using Innodb?

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.

MySql : Select statement inside Check statement

I have to create a table as following
Borrower(customerNo,LoanNo)
customers can take loans if they havent take more than 3 loans.
I created table as follows
create table borrower(
customerno int(5),
LoanNo int(5),
primary key(customerno,loanno),
check( customerno not in
(select customerno from borrower group by customerno having count(loanno)>=4))
);
But it givs me an error saying
[root#localhost:3306] ERROR 1146: Table 'test.borrower' doesn't exist
Can someone tell me how to fix this error??
The reason it's giving the error is because the CHECK constraint refers to the table being created, but it doesn't exist at the time that the statement is parsed.
But I have some bad news for you... mysql ignores CHECK constraints. It is allowed as syntax only for compatibility with create statements from other databases.
See the mysql documentation for CREATE TABLE:
The CHECK clause is parsed but ignored by all storage engines.
You'll have to use a trigger, but note that you can't throw an exception from one. The best you can hope for is when you detect a problem, do something like execute SELECT * FROM TOO_MANY_LOANS and hope that the caller figures out what the error "No such table TOO_MANY_LOANS" really means.
As this belongs to the business rules and not to data structure you should use a Stored Procedure like this
DELIMITER ;;
CREATE PROCEDURE `AddCustomerLoan`(IN Acustomerno int(5), IN ALoanNo int(5), OUT ResultCode INT)
BEGIN
SELECT COUNT(*)
INTO #LoanCount
FROM borrower
WHERE customerno = Acustomerno;
IF #LoanCount < 4 THEN
INSERT INTO borrower ( customerno, LoanNo )
VALUES ( Acustomerno, ALoanNo );
SET ResultCode = 0;
ELSE
-- Too many Entries
SET ResultCode = 1;
END IF;
END;;
DELIMITER ;
The ResultCode will inform your application if it was successful or not, and why not successful.
Another advantage is that you can modify the maximum entries or get the maximum entries per customer, without changing your application code.

MySQL Procedure check if record exists before insert not working

I have looked at the other questions on here about this. It isn't working.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `environment_admin`(
IN environment_id TEXT,
IN user_id TEXT,
IN username VARCHAR(75),
IN password VARCHAR(512)
)
BEGIN
DECLARE env_id INT;
DECLARE admin_id INT;
SET env_id = CAST(environment_id AS SIGNED INT);
SET admin_id = CAST(user_id AS SIGNED INT);
IF NOT EXISTS(SELECT 1 FROM `environment`.`environment_accounts` WHERE environment_id = env_id AND user_id = admin_id) THEN
BEGIN
INSERT INTO
`environment`.`environment_accounts`
(
`environment_id`,
`user_id`,
`username`,
`password`,
`is_active`,
`is_admin`,
`is_mod`
)
VALUES
(
env_id,
admin_id,
username,
password,
1,
1,
1
);
END;
END IF;
END
So if I run:
CALL `environment`.`environment_admin`('22','1','kacieh','512c9ad228332bbd30d09ce7ffb8896e00a1610e914a5fa180bf15ce702b90423e6a9540579f672315ae3c6cb1b8d06ee2b784b4761e806675aa88c2a915553e');
I get 0 row(s) effected
and sure enough, nothing happened. -_- I have been working on this hours
I tested the conditional query, it works.
I have tested just the insert statement inside a stored proc, it works as well.
Stop doing it like that, it's inefficient and it could be worse if two insertare running concurrently! :)
Use INSERT.... ON DUPLICATE KEY UPDATE ... see here
One trick is to do ON DUPLICATE KEY UPDATE environment_id = env_id (not changing the column, so nothing will be updated, ensuring the INSERT will not work without any error condition, you might check number of modified/inserted rows after that)
I realise you've solved your problem (and +1 for Parallelis's answer, especially for highlighting the concurrency issue), but just in case it helps someone else...
MySQL was probably getting confused between your parameters environment_id and user_id and the environment_accounts columns environment_id and user_id. I suspect the parameters were taking precedence in the WHERE clause, meaning as long as there's at least one row in environment_accounts, the NOT EXISTS clause would always return false, and your insert would never run.
For example, if your environment_id and user_id parameters had values of 1 and 2 respectively, the NOT EXISTS clause would evaluate as
IF NOT EXISTS(SELECT 1 FROM `environment`.`environment_accounts` WHERE 1 = 1 AND 2 = 2) THEN
Might be worth having a naming convention for your parameters (and other variables), such as adding a prefix like p_ for parameter.

MySQL time-based constraint

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