MySQL which trigger to use to limit entry number? - mysql

I have a table Products
CREATE TABLE `products` (
`transactionnumber` int(11) NOT NULL AUTO_INCREMENT,
`customerID` mediumint(6) unsigned zerofill NOT NULL,
`product` varchar(100) NOT NULL,
`datebought` date NOT NULL,
PRIMARY KEY (`transactionnumber`),
UNIQUE KEY `product_unique` (`customerID`,`product`,`datebought`),
UNIQUE KEY `product_unipr` (`customerID`,`product`),
KEY `product` (`product`),
CONSTRAINT `prod_ibfk_1` FOREIGN KEY (`customerID`) REFERENCES `custdetails` (`ID`),
CONSTRAINT `prod_ibfk_2` FOREIGN KEY (`product`) REFERENCES `product catalogue` (`Itemdescription`)
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=latin1 |
I am trying to add a trigger which would prevent anyone inserting any products if a customerID is already associated with 10 products.
I tried writing something like this
Delimiter ^^
CREATE TRIGGER maxfiveproducts BEFORE INSERT on Products
FOR EACH ROW BEGIN
if ((SELECT CustomerID, Count(*) from products GROUP BY CustomerID)>10) then
signal allstate '45000' SET MESSAGE_TEXT = 'No more than 10 products per customer!';
end if;
end
^^
Delimiter ;
I am aware that my code may be bad as I have just started learning about triggers and can't find this specific example anywhere

It would look more like this:
Delimiter ^^
CREATE TRIGGER maxfiveproducts BEFORE INSERT on Products
FOR EACH ROW BEGIN
set #cnt = (select count(*) from products p where p.customerId = new.customerId);
if (#cnt >= 10) then
signal allstate '45000' SET MESSAGE_TEXT = 'No more than 10 products per customer!';
end if;
end;
Delimiter ;
Why is the name "maxfiveproducts"?

You can use DECLARE to declare the variable, store the COUNT in it and write an IF condition, e.g.:
CREATE TRIGGER pgl_new_user
BEFORE INSERT ON Products FOR EACH ROW
BEGIN
DECLARE existing_products integer;
SET #existing_products := (SELECT COUNT(*) FROM Products WHERE customerID = NEW.customerID);
IF(#existing_products >= 10) THEN
SIGNAL ALLSTATE '45000' SET MESSAGE_TEXT = 'No more than 10 products per customer!';
END IF;
END//

Related

how to add a constraint that checks multiple rows of information?

I need to make a CONSTRAINT that checks data on multiple rows, an indefinite amount of them.
I've seen that CHECK can only reach data inside the current row, and I have also seen that to solve this problem you need to declare a function (or something that looks just like one).
For clarification:
CREATE TABLE IF NOT EXISTS `ownership`(
`owner_id` INT,
`property_id` INT,
`share` DECIMAL(5,4),
CONSTRAINT `chk_share` CHECK (`share` <= 1.0 AND `share` >= 0),
--CONSTRAINT
-- `share` should show the percentage of the property owner owns
-- I want to add a constraint that does not allow the sum of all
-- ownership rows with the same property_id be greater than 1.
PRIMARY KEY(`owner_id`, `property_id`),
CONSTRAINT `fk_owner`
FOREIGN KEY(`owner_id`)
REFERENCES `market`.`entity` (`entity_id`)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT `fk_property`
FOREIGN KEY(`property_id`)
REFERENCES `market`.`entity` (`entity_id`)
ON UPDATE CASCADE
ON DELETE CASCADE
)
I did not find this question answered on this site, so I am asking politely.
I use MySQL Workbench.
Like #Schwern said I used a trigger.
DELIMITER |
CREATE TRIGGER `tgr_share`
BEFORE INSERT
ON `market`.`ownership`
FOR EACH ROW
BEGIN
IF ( SELECT SUM(`share`) + NEW.`share`
FROM `market`.`ownership`
WHERE `property_id` = NEW.`property_id`
GROUP BY `property_id` > 1.0 )
THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'ownership total shares > 1';
END IF;
END;
|
DELIMITER ;
I tested it and it works as intended.
Since I needed to use it with both insert and update, the code bellow works even better:
DELIMITER |
CREATE PROCEDURE `fn_share` (`new_share` DECIMAL(5,4), `new_property_id` INT)
BEGIN
IF ( SELECT SUM(`share`) + `new_share`
FROM `market`.`ownership`
WHERE `property_id` = `new_property_id`
GROUP BY `property_id` > 1.0)
THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'ownership total shares > 1';
END IF;
END;
|
DELIMITER ;
CREATE TRIGGER `tgr_share_insert`
BEFORE INSERT
ON `market`.`ownership`
FOR EACH ROW
CALL `fn_share`(NEW.`share`, NEW.`property_id`);;
CREATE TRIGGER `tgr_share_update`
BEFORE UPDATE
ON `market`.`ownership`
FOR EACH ROW
CALL `fn_share`(NEW.`share`, NEW.`property_id`);;

Is there a way to stop incrementing rows at n when the primary key is set to AUTO_INCREMENT?

I want only 6400 number of rows in my newtable. How do I do this?
I have a table that looks like this:
CREATE TABLE `newtable` (
ID_NT int(10) NOT NULL AUTO_INCREMENT
)ENGINE=InnoDB DEFAULT CHARSET=latin1;
You can use a trigger on your table. This works of course only if you don't delete rows
DELIMITER $$
CREATE TRIGGER InsertPreventTrigger BEFORE INSERT ON yourtable
FOR EACH ROW
BEGIN
DECLARE idcount INT;
set idcount = ( select count(*) from id where request = new.request );
IF idcount> 6400
THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'You can not insert record';
END $$
DELIMITER ;
And you have ti change yourtable and id ti fit to your needs
Updates to with count rows, that fits the request better

MYSQL BETWEEN operator [duplicate]

I am having trouble with this table
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL,
PRIMARY KEY (`meetid`,`pid`),
CONSTRAINT `participants_ibfk_1` FOREIGN KEY (`meetid`) REFERENCES `Meetings` (`meetid`) ON DELETE CASCADE
CONSTRAINT `participants_ibfk_2` CHECK (status IN ('a','d','u'))
CONSTRAINT `participants_ibfk_3` CHECK (pid IN (SELECT name FROM Rooms) OR pid IN (SELECT userid FROM People))
);
I want to have a foreign key constraint, and that works. Then, I also want to add a constraint to the attribute status so it can only take the values 'a', 'd' and 'u'. It is not possible for me to set the field as Enum or set.
Can anyone tell me why this code does not work in MySQL?
CHECK constraints are not supported by MySQL. You can define them, but they do nothing (as of MySQL 5.7).
From the manual:
The CHECK clause is parsed but ignored by all storage engines.
The workaround is to create triggers, but they aren't the easiest thing to work with.
If you want an open-source RDBMS that supports CHECK constraints, try PostgreSQL. It's actually a very good database.
I don't understand why nobody here has mentioned that VIEW WITH CHECK OPTION can be a good alternative to the CHECK CONSTRAINT in MySQL:
CREATE VIEW name_of_view AS SELECT * FROM your_table
WHERE <condition> WITH [LOCAL | CASCADED] CHECK OPTION;
There is a doc on the MySQL site: The View WITH CHECK OPTION Clause
DROP TABLE `Participants`;
CREATE TABLE `Participants` (
`meetid` int(11) NOT NULL,
`pid` varchar(15) NOT NULL,
`status` char(1) DEFAULT NULL check (status IN ('a','d','u')),
PRIMARY KEY (`meetid`,`pid`)
);
-- should work
INSERT INTO `Participants` VALUES (1,1,'a');
-- should fail but doesn't because table check is not implemented in MySQL
INSERT INTO `Participants` VALUES (2,1,'x');
DROP VIEW vParticipants;
CREATE VIEW vParticipants AS
SELECT * FROM Participants WHERE status IN ('a','d','u')
WITH CHECK OPTION;
-- should work
INSERT INTO vParticipants VALUES (3,1,'a');
-- will fail because view uses a WITH CHECK OPTION
INSERT INTO vParticipants VALUES (4,1,'x');
P.S.: Keep in mind that your view should be updatable! See MySQL Updatable Views
(thanks Romeo Sierra for clarification in comments).
Beside triggers, for simple constraints like the one you have:
CONSTRAINT `participants_ibfk_2`
CHECK status IN ('a','d','u')
you could use a Foreign Key from status to a Reference table (ParticipantStatus with 3 rows: 'a','d','u' ):
CONSTRAINT ParticipantStatus_Participant_fk
FOREIGN KEY (status)
REFERENCES ParticipantStatus(status)
Starting with version 8.0.16, MySQL has added support for CHECK constraints:
ALTER TABLE topic
ADD CONSTRAINT post_content_check
CHECK (
CASE
WHEN DTYPE = 'Post'
THEN
CASE
WHEN content IS NOT NULL
THEN 1
ELSE 0
END
ELSE 1
END = 1
);
ALTER TABLE topic
ADD CONSTRAINT announcement_validUntil_check
CHECK (
CASE
WHEN DTYPE = 'Announcement'
THEN
CASE
WHEN validUntil IS NOT NULL
THEN 1
ELSE 0
END
ELSE 1
END = 1
);
Previously, this was only available using BEFORE INSERT and BEFORE UPDATE triggers:
CREATE
TRIGGER post_content_check BEFORE INSERT
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Post'
THEN
IF NEW.content IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Post content cannot be NULL';
END IF;
END IF;
END;
CREATE
TRIGGER post_content_update_check BEFORE UPDATE
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Post'
THEN
IF NEW.content IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Post content cannot be NULL';
END IF;
END IF;
END;
CREATE
TRIGGER announcement_validUntil_check BEFORE INSERT
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Announcement'
THEN
IF NEW.validUntil IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Announcement validUntil cannot be NULL';
END IF;
END IF;
END;
CREATE
TRIGGER announcement_validUntil_update_check BEFORE UPDATE
ON topic
FOR EACH ROW
BEGIN
IF NEW.DTYPE = 'Announcement'
THEN
IF NEW.validUntil IS NULL
THEN
signal sqlstate '45000'
set message_text = 'Announcement validUntil cannot be NULL';
END IF;
END IF;
END;
Here is a way of getting the checks you wanted quickly and easily:
drop database if exists gtest;
create database if not exists gtest;
use gtest;
create table users (
user_id integer unsigned not null auto_increment primary key,
username varchar(32) not null default '',
password varchar(64) not null default '',
unique key ix_username (username)
) Engine=InnoDB auto_increment 10001;
create table owners (
owner_id integer unsigned not null auto_increment primary key,
ownername varchar(32) not null default '',
unique key ix_ownername (ownername)
) Engine=InnoDB auto_increment 5001;
create table users_and_owners (
id integer unsigned not null primary key,
name varchar(32) not null default '',
unique key ix_name(name)
) Engine=InnoDB;
create table p_status (
a_status char(1) not null primary key
) Engine=InnoDB;
create table people (
person_id integer unsigned not null auto_increment primary key,
pid integer unsigned not null,
name varchar(32) not null default '',
status char(1) not null,
unique key ix_name (name),
foreign key people_ibfk_001 (pid) references users_and_owners(id),
foreign key people_ibfk_002 (status) references p_status (a_status)
) Engine=InnoDB;
create or replace view vw_users_and_owners as
select
user_id id,
username name
from users
union
select
owner_id id,
ownername name
from owners
order by id asc
;
create trigger newUser after insert on users for each row replace into users_and_owners select * from vw_users_and_owners;
create trigger newOwner after insert on owners for each row replace into users_and_owners select * from vw_users_and_owners;
insert into users ( username, password ) values
( 'fred Smith', password('fredSmith')),
( 'jack Sparrow', password('jackSparrow')),
( 'Jim Beam', password('JimBeam')),
( 'Ted Turner', password('TedTurner'))
;
insert into owners ( ownername ) values ( 'Tom Jones'),( 'Elvis Presley'),('Wally Lewis'),('Ted Turner');
insert into people (pid, name, status) values ( 5001, 'Tom Jones', 1),(10002,'jack Sparrow',1),(5002,'Elvis Presley',1);

How to use trigger in MySql to make foreign key

I want to use trigger to make foreign key in MySql. I have the following tables:
1) 'content' table:
teacher_id varchar(20)
sub_id varchar(20)
path varchar(100)
file_name varchar(100)
2) 'teacher' table:
teacher_id varchar(20)
teacher_name varchar(45)
and I am using the following code for trigger(delimiter //):
CREATE TRIGGER fk_content_teacher_temp BEFORE INSERT ON `content`
FOR EACH ROW
BEGIN
DECLARE has_row TINYINT;
SET has_row = 0;
SELECT 1 INTO has_row FROM `teacher` INNER JOIN `content` ON content.teacher_id=teacher.teacher_id;
IF has_row=0 THEN
INSERT error_msg VALUES ('Foreign Key Constraint Violated!');
END IF;
END//
The problem is, when am trying to insert in content table for a teacher_id which is not present in teacher table, I get the following error:
1172 - Result consists of more than one row
What can I do to make it work fine, or any other way i can use trigger to make foreign keys?
Thank you in advance!
While it is not clear what exactly you intend with the statement "use trigger to make foreign key", your current issue is that SELECT INTO cannot be used in queries that return more than one result.
SELECT 1 INTO has_row FROM teacher INNER JOIN content ON content.teacher_id=teacher.teacher_id;
returns EVERY match between the two tables.
If you were trying to check if teacher contains the teacher_id value being used in the new content record, you should just be able to drop the JOIN clause completely and just query like so:
SELECT 1 INTO has_row FROM `teacher` WHERE `teacher_id` = NEW.`teacher_id`;
While this is an oldish question I would like to provide some insight for future searchers on how one might deal with such issue.
In a recent project I was unable to use InnoDB but had to use the MyISAM engine (in reality it was MariaDB's Aria engine) for a database transfer which contained foreign keys.
I opted for implementing foreign keys using triggers as described here.
A great intro into the subject is provided here: https://dev.mysql.com/tech-resources/articles/mysql-enforcing-foreign-keys.html
However, I will outline my solution as I found some thing not fully workable for me in the above. E.g. Any update to a parent table was completely prohibited in their "restrict" example when a foreign child key existed even though the child was not affected.
For demonstration I use the following table definitions and test data:
CREATE TABLE `__parent` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`)) ENGINE=`Aria`;
CREATE TABLE `__child` (`id` int UNSIGNED NOT NULL AUTO_INCREMENT,`parent_id` int UNSIGNED, PRIMARY KEY (`id`), INDEX `parent_id_idx` USING BTREE (`parent_id`) ) ENGINE=`Aria`;
INSERT INTO __parent VALUES (1), (2), (3);
INSERT INTO __child VALUES (1,1), (2,2), (3,1), (4,2), (5,3), (6,1);
Prevent inserts into a child table when no corresponding linked parent entry exists:
DELIMITER //
CREATE TRIGGER __before_insert_child BEFORE INSERT ON __child FOR EACH ROW
BEGIN
IF (SELECT COUNT(*) FROM __parent WHERE __parent.id=new.parent_id) = 0 THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t insert record. Foreign parent key does not exist!';
END IF;
END //
DELIMITER ;
Prevent updates to a child table where it would unlink a child record:
DELIMITER //
CREATE TRIGGER __before_update_child BEFORE UPDATE ON __child FOR EACH ROW
BEGIN
IF (SELECT COUNT(*) FROM __parent WHERE __parent.id = new.parent_id) = 0 THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t update record. Foreign parent key does not exist!';
END IF;
END //
DELIMITER ;
Cascading updates to the child table when the parent is updated:
DELIMITER //
CREATE TRIGGER __after_update_parent AFTER UPDATE ON __parent FOR EACH ROW
BEGIN
UPDATE __child SET __child.parent_id=new.id WHERE __child.parent_id=old.id;
END //
DELIMITER ;
Cascade deletes to the child table when a parent is deleted:
DELIMITER //
CREATE TRIGGER __after_delete_parent AFTER DELETE ON __parent FOR EACH ROW
BEGIN
DELETE FROM __child WHERE __child.parent_id=old.id;
END;
END //
DELIMITER ;
Sometime you don't want to cascade but restrict. In this case use the following instead:
Restrict parent updates to the child table:
DELIMITER //
CREATE TRIGGER __before_update_parent BEFORE UPDATE ON __parent FOR EACH ROW
BEGIN
IF ( old.id <> new.id AND (SELECT COUNT(*) FROM __child WHERE __child.parent_id = old.id) <> 0 ) THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t update record. Foreign key updates to child table restricted!';
END IF;
END //
DELIMITER ;
Restrict parent deletes from the child table:
DELIMITER //
CREATE TRIGGER __before_delete_parent BEFORE DELETE ON __parent FOR EACH ROW
BEGIN
IF ( SELECT COUNT(*) FROM __child WHERE __child.parent_id = old.id) <> 0 THEN
SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO = 30001, MESSAGE_TEXT = 'Can\'t delete record. Foreign key exists in child table!';
END IF;
END //
DELIMITER ;
Hope this helps someone.

insert trigger: issue with constraint

I'm trying to create trigger on customer-order database where each customer has several orders and each order has several items.
I'm planning to create a trigger to ensure that
the total number of all orders place by the same customer cannot
exceed 10000
How can create the insert trigger for above constraint.
Here is my SQL file with sample data provided.
CREATE SCHEMA IF NOT EXISTS `mydb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `mydb` ;
CREATE TABLE customers
(`id` int not null auto_increment primary key, `first_name` varchar(64), `last_name`varchar(64) );
INSERT INTO customers(`first_name`, `last_name`)VALUES('Jhon', 'Doe');
CREATE TABLE items
(`id` int not null auto_increment primary key,`item` varchar(64),`price` decimal(19,2));
INSERT INTO items(`item`, `price`)VALUES('Item1', 10.5),('Item2', 25);
CREATE TABLE orders
(`id` int not null auto_increment primary key, `date` date, `customer_id` int,`status` int not null default 1, -- 1 new constraint fk_customer_id foreign key (customer_id) references customers (id));
INSERT INTO orders(`date`, `customer_id`, `status`)VALUES(CURDATE(), 1, 1);
CREATE TABLE order_items(`id` int not null auto_increment primary key,
`order_id` int not null, `item_id` int not null, `quantity` decimal(19,3) not null, `price` decimal(19,3) not null,
constraint fk_order_id foreign key (order_id) references orders (id),
constraint fk_item_id foreign key (item_id) references items (id));
INSERT INTO order_items(`order_id`, `item_id`, `quantity`, `price`)VALUES
(1, 1, 2, 10.5),(1, 2, 4, 25);
;
Although Jahul's answer would technically work, here is alternative logic:
DELIMITER $$
CREATE TRIGGER `customer_orders_check`
BEFORE INSERT ON `orders` FOR EACH ROW
BEGIN
IF ((select count(*)
from `orders`
where a.customer_id = NEW.customer_id
) >= 10000 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Too many orders already';
END IF;
END;
$$
DELIMITER ;
That said, I would suggest an alternative approach. Counting up to 10,000 rows for each insert seems like a lot of work. Instead, keep the counter in the customers table, using an after insert trigger (and perhaps after update/delete as well). Then when inserting a new row, you can just check the count in customers.
This trigger will stop insert --
CREATE TRIGGER `customer_orders_check`
BEFORE INSERT ON `orders` FOR EACH ROW
BEGIN
IF exists(select count(*)
from `orders` a
where a.customer_id= NEW.customer_id
having count(*)>=10000 ) THEN
SET NEW.id = 1 ;
END IF;
END;