Sum of column values constraint MySQL - mysql

I'm using MySQL with PhpMyAdmin. I'm looking for a way to put a constraint on a table so that:
column1(int) + column2(int) + column3(int) <= 100

In mysql as of now you can not have such constraints in the table definition. You may use the application layer to validate this. If you want to have it using mysql then you can use trigger and raise error using signal
https://dev.mysql.com/doc/refman/5.5/en/signal.html
Here is how the trigger would look like
delimiter //
create trigger check_col_sum before insert on test
for each row
begin
if (new.column1+new.column2+new.column3) > 100 then
signal sqlstate '45000' set message_text = 'Sum of column1,column2 and column3 must be less than equal to 100';
end if;
end;//
delimiter ;
Test case
mysql> create table test (id int auto_increment primary key, column1 int, column2 int, column3 int);
Query OK, 0 rows affected (0.14 sec)
mysql> delimiter //
mysql> create trigger check_col_sum before insert on test
-> for each row
-> begin
-> if (new.column1+new.column2+new.column3) > 100 then
-> signal sqlstate '45000' set message_text = 'Sum of column1,column2 and column3 must be less than equal to 100';
-> end if;
-> end;//
Query OK, 0 rows affected (0.09 sec)
mysql>
mysql> delimiter ;
mysql> insert into test (column1,column2,column3) values (10,20,30);
Query OK, 1 row affected (0.08 sec)
mysql> select * from test ;
+----+---------+---------+---------+
| id | column1 | column2 | column3 |
+----+---------+---------+---------+
| 1 | 10 | 20 | 30 |
+----+---------+---------+---------+
1 row in set (0.00 sec)
mysql> insert into test (column1,column2,column3) values (10,20,80);
ERROR 1644 (45000): Sum of column1,column2 and column3 must be less than equal to 100
mysql> select * from test ;
+----+---------+---------+---------+
| id | column1 | column2 | column3 |
+----+---------+---------+---------+
| 1 | 10 | 20 | 30 |
+----+---------+---------+---------+
1 row in set (0.00 sec)

You can use New feature of MySQL5.7 that is Generated columns, there you can define the sum of two columns in third column while creating table or can add the column by ALTER command. Example
CREATE TABLE triangle(
sidea DOUBLE,
sideb DOUBLE,
sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb))
);
Refer the link:
MySQL Doc

Related

Error when creating Index for table Mysql

I'm working on create index for table with mysql.
I've 2 tables:
1. account
2. x_activity (x is the account_id related to "account" table, EX: 1_activity, 2_activity).
So i've created an "Index" for activity table:
Here are my code:
DROP PROCEDURE if exists update_index_for_table;
DELIMITER $$
CREATE PROCEDURE update_index_for_table()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE accountid INT;
--
-- GET ALL ACCOUNT ID
--
DECLARE accountids CURSOR FOR SELECT account_id FROM account;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
--
-- LOOP
--
OPEN accountids;
read_loop: LOOP
FETCH accountids INTO accountid;
IF done THEN
LEAVE read_loop;
END IF;
--
-- INDEX FOR ACTIVITY
--
SET #update_activity_table_1 = CONCAT("
IF (
SELECT COUNT(1) FROM INFORMATION_SCHEMA.STATISTICS
WHERE `TABLE_SCHEMA` = DATABASE() AND TABLE_NAME='",accountid,"_activity' AND
INDEX_NAME='IDX_",accountid,"_ACTIVITY_ACTIVITY_ID'
) != 1
THEN
ALTER TABLE ",accountid,"_activity
ADD KEY `IDX_",accountid,"_ACTIVITY_ACTIVITY_ID` (`activity_id`);
END IF;
");
PREPARE stmt from #update_activity_table_1;
EXECUTE stmt;
END LOOP;
CLOSE accountids;
END$$
DELIMITER ;
CALL update_index_for_table();
But then, for some php/mysql version (i think), its cause an error like this:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF (
SELECT COUNT(1) FROM INFORMATION_SCHEMA.STATISTICS
WHERE `TABLE_SCHEM' at line 1
I've tested this code and its work fine:
SELECT COUNT(1) FROM INFORMATION_SCHEMA.STATISTICS
WHERE `TABLE_SCHEMA` = DATABASE() AND TABLE_NAME='",accountid,"_activity' AND
INDEX_NAME='IDX_",accountid,"_ACTIVITY_ACTIVITY_ID'
Here are my php/sql version:
phpmyadmin: 4.8.5, php version: 7.2.7, mysql: 5.6.45
Please help, thanks.
There are a couple of constraints on what you are trying to do here 1) you cannot run an if statement outwith a stored program 2)if you pass a query to dynamic sql and the query does not find anything the continue handler will be invoked and the loop will terminate (unexpectedly) early. The approach then is to split the functionality to first check existence by amending the 'find' to insert a value to a user defined variable and at the same time ensure the handler is not hijacked by a not found by including a look up on a table which will definitely contain something (in this case information.schema_tables.
So given
DROP PROCEDURE if exists p;
DELIMITER $$
CREATE PROCEDURE p()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE accountid INT;
--
-- GET ALL ACCOUNT ID
--
DECLARE accountids CURSOR FOR SELECT account_id FROM account;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
--
-- LOOP
--
OPEN accountids;
read_loop: LOOP
FETCH accountids INTO accountid;
select accountid;
IF done = true THEN
select accountid, 'leaving';
LEAVE read_loop;
END IF;
--
-- INDEX FOR ACTIVITY
--
SET #test := 0;
SET #update_activity_table_1 :=
(concat('SELECT case when index_name is null then 1 else 0 end into #test FROM
information_schema.tables it
left join INFORMATION_SCHEMA.STATISTICS iss ',
' on iss.table_schema = it.table_schema and iss.table_name = it.table_name and ',
'INDEX_NAME=',char(39),'IDX_',accountid,'_ACTIVITY_ACTIVITY_ID',char(39),
' WHERE it.TABLE_SCHEMA = ', char(39),'test',char(39), ' AND ',
'it.TABLE_NAME=',char(39),accountid,'_activity', char(39),
';'
)
)
;
select #update_activity_table_1;
PREPARE stmt from #update_activity_table_1;
EXECUTE stmt;
deallocate prepare stmt;
if #test = 1 then
select 'Did not find index for ' , accountid, '_extract';
else
select 'Found index for ' , accountid, '_extract';
end if;
END LOOP;
CLOSE accountids;
END $$
DELIMITER ;
call p();
I'll leave you to build the alter statement and insert into the if statement.
given
use test;
drop table if exists account,`1_activity`,`2_activity`,`64_activity`;
create table account (account_id int);
create table `1_activity`(id int);
create table `2_activity`(id int);
create table `64_activity`(id int);
insert into account values (1),(2),(64);
MariaDB [test]> call p();
+-----------+
| accountid |
+-----------+
| 1 |
+-----------+
1 row in set (0.00 sec)
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| #update_activity_table_1 |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| SELECT case when index_name is null then 1 else 0 end into #test FROM
information_schema.tables it
left join INFORMATION_SCHEMA.STATISTICS iss on iss.table_schema = it.table_schema and iss.table_name = it.table_name and INDEX_NAME='IDX_1_ACTIVITY_ACTIVITY_ID' WHERE it.TABLE_SCHEMA = 'test' AND it.TABLE_NAME='1_activity'; |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)
+-------------------------+-----------+----------+
| Did not find index for | accountid | _extract |
+-------------------------+-----------+----------+
| Did not find index for | 1 | _extract |
+-------------------------+-----------+----------+
1 row in set (0.28 sec)
+-----------+
| accountid |
+-----------+
| 2 |
+-----------+
1 row in set (0.30 sec)
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| #update_activity_table_1 |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| SELECT case when index_name is null then 1 else 0 end into #test FROM
information_schema.tables it
left join INFORMATION_SCHEMA.STATISTICS iss on iss.table_schema = it.table_schema and iss.table_name = it.table_name and INDEX_NAME='IDX_2_ACTIVITY_ACTIVITY_ID' WHERE it.TABLE_SCHEMA = 'test' AND it.TABLE_NAME='2_activity'; |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.30 sec)
+-------------------------+-----------+----------+
| Did not find index for | accountid | _extract |
+-------------------------+-----------+----------+
| Did not find index for | 2 | _extract |
+-------------------------+-----------+----------+
1 row in set (0.47 sec)
+-----------+
| accountid |
+-----------+
| 64 |
+-----------+
1 row in set (0.49 sec)
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| #update_activity_table_1 |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| SELECT case when index_name is null then 1 else 0 end into #test FROM
information_schema.tables it
left join INFORMATION_SCHEMA.STATISTICS iss on iss.table_schema = it.table_schema and iss.table_name = it.table_name and INDEX_NAME='IDX_64_ACTIVITY_ACTIVITY_ID' WHERE it.TABLE_SCHEMA = 'test' AND it.TABLE_NAME='64_activity'; |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.50 sec)
+-------------------------+-----------+----------+
| Did not find index for | accountid | _extract |
+-------------------------+-----------+----------+
| Did not find index for | 64 | _extract |
+-------------------------+-----------+----------+
1 row in set (0.66 sec)
+-----------+
| accountid |
+-----------+
| 64 |
+-----------+
1 row in set (0.67 sec)
+-----------+---------+
| accountid | leaving |
+-----------+---------+
| 64 | leaving |
+-----------+---------+
1 row in set (0.67 sec)
Query OK, 0 rows affected (0.69 sec)
Firstly, i believe accound_id and activity_id are both your unique keys, but what am not sure if you are auto incrementing it, check to see if the auto increment is check.

MySQL - Trigger - Before Insert and using the SK (Auto Increment)

I have a simple posts table in MySQL which has a POST_ID as the SK (surrogate key).
Replies to the original post ID are stored in the same table in a PARENT_POST_ID column, but I want to perform the following logic:
BEFORE INSERT (I think ...)
IF a PARENT_POST_ID has not been defined on the INSERT, then default the row value to the newly generated POST_ID (from the auto-int sequence)
IF a PARENT_POST_ID has been defined on the INSERT, then set it to whatever has been passed.
Example
post_id | parent_post_id | date_time | message
12 12 2015-04-14 21:10 A new post (start of a thread)
13 12 2015-04-14 21:12 A reply to the post ID 12
The answer here: https://stackoverflow.com/a/11061766/1266457 looks like it might be what I need to do, although I am not sure what it's doing.
Thanks.
For before insert trigger you can not get the last inserted primary key , the other way of doing it is to get the max value from the table and increment it.
Here is a way to do it
delimiter //
create trigger posts_before_ins before insert on posts
for each row
begin
declare last_id int;
if new.parent_post_id is null then
select max(post_id) into last_id from posts ;
if last_id is null then
set new.parent_post_id = 1 ;
else
set new.parent_post_id = last_id+1 ;
end if ;
end if ;
end ;//
delimiter ;
So the trigger will check if there is no value of parent_post_id in the insert query it will get the max post_id. For the first entry it will be null so we are setting it as 1 i.e. and after that max post_id + 1 after each entry.
Here is a test case of this in mysql
mysql> select * from test ;
Empty set (0.00 sec)
mysql> delimiter //
mysql> create trigger test_is before insert on test
-> for each row
-> begin
-> declare last_id int;
-> if new.parent_id is null then
-> SELECT auto_increment into last_id
-> FROM INFORMATION_SCHEMA.TABLES WHERE table_name = 'test'
-> and TABLE_SCHEMA = 'test';
-> set new.parent_id = last_id ;
-> end if ;
-> end ;//
Query OK, 0 rows affected (0.12 sec)
mysql>
mysql> delimiter ;
mysql> insert into test (val) values ('aa');
Query OK, 1 row affected (0.10 sec)
mysql> insert into test (val) values ('bb');
Query OK, 1 row affected (0.04 sec)
mysql> select * from test ;
+---------+-----------+------+
| post_id | parent_id | val |
+---------+-----------+------+
| 1 | 1 | aa |
| 2 | 2 | bb |
+---------+-----------+------+
2 rows in set (0.00 sec)

mysql trigger after insert not working

I tried from several examples and could not reach my goal - so I am asking here...
I have a table :
table1
columns : col1 , col2
I want to change col2 after an insert :
If (col2 = 0) then
alter col2 to be max(col2) + 1
else
leave the value as it is
Thanks
You need to do it using before insert since before insert you can check if the col2 is 0 and then set the value to max col2 + 1
Here is a way to do it
delimiter //
create trigger test_ins before insert on table1
for each row
begin
declare max_col2 int;
if new.col2 = 0 then
set max_col2 = (select max(col2) from table1);
end if ;
if max_col2 is null then
set new.col2 = 1 ;
else
set new.col2 = max_col2 + 1 ;
end if ;
end ;//
delimiter ;
Note that in the above trigger I have added a check such that if the max(col2) is null which will happen when you add the first record in the table and col2 as 0 I am setting it as 1
if max_col2 is null then
set new.col2 = 1 ;
You can set it as you want.
Here is a test case
mysql> create table table1 (col1 int, col2 int);
Query OK, 0 rows affected (0.13 sec)
mysql> delimiter //
mysql> create trigger test_ins before insert on table1
-> for each row
-> begin
-> declare max_col2 int;
-> if new.col2 = 0 then
-> set max_col2 = (select max(col2) from table1);
-> end if ;
-> if max_col2 is null then
-> set new.col2 = 1 ;
-> else
-> set new.col2 = max_col2 + 1 ;
-> end if ;
-> end ;//
Query OK, 0 rows affected (0.10 sec)
mysql> delimiter ;
mysql> insert into table1 values (1,0);
Query OK, 1 row affected (0.04 sec)
mysql> select * from table1;
+------+------+
| col1 | col2 |
+------+------+
| 1 | 1 |
+------+------+
1 row in set (0.00 sec)
mysql> insert into table1 values (2,0);
Query OK, 1 row affected (0.04 sec)
mysql> select * from table1;
+------+------+
| col1 | col2 |
+------+------+
| 1 | 1 |
| 2 | 2 |
+------+------+
2 rows in set (0.00 sec)
Again - thanks for your reply.
However, I tested it and it seems :
Not to add + 1 to the next value (and I really do not know why it is like that).
Leave the value if it is defined (I wrote above: "else leave the value as it is")
I changed your code a bit and put some extra IFs.
Now it is working OK (for me).
DELIMITER //
CREATE TRIGGER trigCF7Submits
BEFORE INSERT ON wp_cf7dbplugin_submits
FOR EACH ROW
BEGIN
DECLARE nMaxInternalNumber INT;
IF ((new.form_name='ActionForm') AND (new.field_name='txtActionFormInternalNumber')) then
IF (new.field_value = 0) THEN
SET nMaxInternalNumber = (SELECT max(field_value) FROM wp_cf7dbplugin_submits WHERE ((form_name='ActionForm') AND (field_name='txtActionFormInternalNumber')));
IF (nMaxInternalNumber IS NULL) THEN
SET nMaxInternalNumber = 0;
END IF;
SET nMaxInternalNumber = nMaxInternalNumber + 1;
ELSE
SET nMaxInternalNumber = new.field_value;
END IF;
SET new.field_value = nMaxInternalNumber ;
END IF ;
END ;//
DELIMITER ;

Mysql select statement only works on first iteration in while loop, insert working fine

Why is the SELECT statement inside the WHILE loop only returning value for the first iteration ?
Both of the INSERT IGNORE and the second INSERT is working, and are inserting rows equal to amount.
If I set amount to 10, I only get the results from the first inserted row. However, the procedure will INSERT amount rows to rand_strings and rand_strings_info tables.
The Procedure:
DROP PROCEDURE if exists test_while;
DELIMITER $$
CREATE PROCEDURE test_while(amount INT, description VARCHAR(255))
BEGIN
WHILE amount > 0 DO
INSERT IGNORE INTO rand_strings(rand_string) /*WORKS EVERY ITERATION*/
SELECT generate_rand_string(); /*function to generate a random string.*/
SELECT * FROM rand_strings WHERE id = LAST_INSERT_ID(); /*ONLY WORKS FIRST TIME */
INSERT INTO rand_strings_info(id, col2, col3) /*WORKS EVERY ITERATION*/
VALUES (LAST_INSERT_ID(), now(), description);
SET amount = amount - 1;
END WHILE;
END$$
DELIMITER ;
CALL test_while(10, 'This is the description of the string…')
RESULTS:
id | rand_string
1 | jgdlkjaht
Some interfaces do not show all the results as expected, but the code runs correctly.
You can see in the following SQL Fiddle that only shows the first record in the rand_strings table when stored procedure runs, but running the same code on the MySQL command line the result is as follows:
mysql> CALL `test_while`(5, 'This is the description of the string...');
+----+------------------------------------------+
| id | rand_string |
+----+------------------------------------------+
| 1 | f4c77a3155d95ad1e818b1b06a62deec8e0b6754 |
+----+------------------------------------------+
1 row in set (0.00 sec)
+----+------------------------------------------+
| id | rand_string |
+----+------------------------------------------+
| 2 | 7cbcca49596262836f5af91643303d10b3804900 |
+----+------------------------------------------+
1 row in set (0.00 sec)
+----+------------------------------------------+
| id | rand_string |
+----+------------------------------------------+
| 3 | 2ba2c7276c0b66e3dcbb971b7f54af9bced578a4 |
+----+------------------------------------------+
1 row in set (0.00 sec)
+----+------------------------------------------+
| id | rand_string |
+----+------------------------------------------+
| 4 | d52426b19a59c515b02268347c508383873d4d73 |
+----+------------------------------------------+
1 row in set (0.00 sec)
+----+------------------------------------------+
| id | rand_string |
+----+------------------------------------------+
| 5 | fbc25c6204b609e8f4f4f8a33b534bff9a011e5f |
+----+------------------------------------------+
1 row in set (0.00 sec)
Query OK, 1 row affected (0.00 sec)
UPDATE
DELIMITER $$
CREATE PROCEDURE `test_while`(`amount` INT, `description` VARCHAR(255))
BEGIN
DECLARE `_LAST_INSERT_ID`, `_first_inserted_in_this_run` INT UNSIGNED DEFAULT NULL;
CREATE TEMPORARY TABLE IF NOT EXISTS `temp_generate_rand_string` (
`insert_id` INT UNSIGNED PRIMARY KEY,
`first_inserted_in_this_run` INT UNSIGNED
) ENGINE=MEMORY;
WHILE `amount` > 0 DO
INSERT IGNORE INTO `rand_strings`(`rand_string`) /*WORKS EVERY ITERATION*/
SELECT `generate_rand_string`(); /*function to generate a random string.*/
SET `_LAST_INSERT_ID` := LAST_INSERT_ID();
IF (`_first_inserted_in_this_run` IS NULL) THEN
SET `_first_inserted_in_this_run` := `_LAST_INSERT_ID`;
END IF;
INSERT INTO `temp_generate_rand_string` (`insert_id`, `first_inserted_in_this_run`)
VALUES
(`_LAST_INSERT_ID`, `_first_inserted_in_this_run`);
-- SELECT * FROM `rand_strings` WHERE `id` = `_LAST_INSERT_ID`; /*ONLY WORKS FIRST TIME */
INSERT INTO `rand_strings_info`(`id`, `col2`, `col3`) /*WORKS EVERY ITERATION*/
VALUES (`_LAST_INSERT_ID`, NOW(), `description`);
SET `amount` := `amount` - 1;
END WHILE;
SELECT `rs`.`id`, `rs`.`rand_string`
FROM `rand_strings` `rs`
INNER JOIN `temp_generate_rand_string` `tgrs` ON
`tgrs`.`first_inserted_in_this_run` = `_first_inserted_in_this_run` AND
`rs`.`id` = `tgrs`.`insert_id`;
DELETE
FROM `temp_generate_rand_string`
WHERE `first_inserted_in_this_run` = `_first_inserted_in_this_run`;
END$$
DELIMITER ;
SQL Fiddle demo

My MySQL after INSERT trigger isn't working? Why?

I've created a trigger that resembles the following:
delimiter //
CREATE TRIGGER update_total_seconds_on_phone AFTER INSERT
ON cdr
FOR EACH ROW
BEGIN
IF NEW.billsec > 0 AND NEW.userfield <> NULL THEN
UPDATE foo
SET total_seconds = total_seconds + NEW.billsec
WHERE phone_id = NEW.userfield;
END IF;
END;//
It seems to go through okay. However it doesn't appear to be invoking when I need it to. Here's an example:
mysql> select total_seconds from foo where phone_id = 1;
+---------------+
| total_seconds |
+---------------+
| 0 |
+---------------+
1 row in set (0.00 sec)
mysql> insert into cdr (billsec, userfield) VALUES(60, 1);
Query OK, 1 row affected, 12 warnings (0.00 sec)
mysql> select total_seconds from foo where phone_id = 1;
+---------------+
| total_seconds |
+---------------+
| 0 |
+---------------+
EDIT: The warnings in this particular case do not affect the trigger. It's mostly additional columns which do not have default values in the cdr table that cause the warnings. For the sake of simplicity, I kept my INSERT statement brief.
There's 12 warnings generated. What are they?
ETA: Oh, wait... you need to use is not null rather than <> null. Any comparison with null will always return null.