Using Auto-Increment value in MYSQL Before Insert Trigger? - mysql

The users table:
CREATE TABLE `users` (
`id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(45) DEFAULT NULL,
`username` varchar(16) DEFAULT NULL,
`salt` varchar(16) DEFAULT NULL,
`password` varchar(128) DEFAULT NULL,
`lastlogin` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`joined` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`loggedin` tinyint(1) unsigned NOT NULL DEFAULT '0',
`sessionkey` varchar(60) DEFAULT NULL,
`verifycode` varchar(16) DEFAULT NULL,
`verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
`banned` tinyint(1) unsigned NOT NULL DEFAULT '0',
`locked` tinyint(1) unsigned NOT NULL DEFAULT '0',
`ip_address` varchar(45) DEFAULT NULL,
`failedattempts` tinyint(1) unsigned NOT NULL DEFAULT '0',
`unlocktime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;
The user_records table:
CREATE TABLE `user_records` (
`id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`userid` int(8) unsigned DEFAULT NULL,
`action` varchar(100) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;
The before insert trigger on the users table:
USE `gknet`;
DELIMITER $$
CREATE DEFINER=`root`#`localhost` TRIGGER `before_create_user` BEFORE INSERT ON `users` FOR EACH ROW BEGIN
INSERT INTO user_records (action, userid, timestamp)
VALUES ('CREATED', ID, NOW() );
END
Basically, my problem here is that on the trigger when I try to put in the id of the user that's automatically assigned by MySQL (PK, NN, Auto-Increment), it just puts in 0 for userid on the user_records table. How would I do it so it would select the id that the user is being assigned by SQL, and put it in as userid on the records entry (where the ID is right after 'CREATED')?
Also, if you see any other optimizations that could be made on the tables, feel free to let me know :D

OP's comment:
How would I do it before, thou?
You can find current auto_increment value that is to be assigned to a new record.
And use the same in the before trigger as a parent user id for user_records table.
You have to query information_schema.tables table to find the value.
Example:
use `gknet`;
delimiter $$
drop trigger if exists before_create_user; $$
create definer=`root`#`localhost` trigger `before_create_user`
before insert on `users`
for each row begin
declare fk_parent_user_id int default 0;
select auto_increment into fk_parent_user_id
from information_schema.tables
where table_name = 'users'
and table_schema = database();
insert into user_records ( action, userid, timestamp )
values ( 'created', fk_parent_user_id, now() );
end;
$$
delimiter ;
Observations:
As per mysql documentation on last_insert_id(),
"if you insert multiple rows using a single INSERT statement,
LAST_INSERT_ID() returns the value generated for the first inserted
row only."
hence, depending on last_insert_id() and auto_increment field values in batch inserts seems not reliable.

Change the trigger to after insert instead of before insert and use NEW to get the last inserted id
USE `gknet`;
DELIMITER $$
CREATE DEFINER=`root`#`localhost`
TRIGGER `after_create_user` AFTER INSERT ON `users`
FOR EACH ROW
BEGIN
INSERT INTO user_records (action, userid, timestamp)
VALUES ('CREATED', NEW.ID, NOW() );
END; $$

PLEASE USE AFTER INSERT AND UPDATE
Do not make auto_increment any column you want to manipulate explicitly. That can confuse an engine and cause serious problems. If no column you have used for primary key are auto_increment you can do anything you want with them via triggers. Sure generated values will be rejected if they violate the mandatory uniqness of the primary key.

maybe this solution can
BEGIN
DECLARE id int;
SELECT MAX(table_id)
FROM table
INTO id;
IF id IS NULL THEN
SET NEW.column=(CONCAT('KTG',1));
ELSE
SET NEW.column=(CONCAT('KTG',id+1));
END IF;
END

Related

Is there a way to use a MySQL trigger to duplicate a record in a table, but modify the data before insert?

I am applying a series of triggers to a few tables in our database - these will serve the purpose of creating soft-delete/shadow records when certain relevant data is updated.
An example, consider this table:
CREATE TABLE `cms_content` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(40) NOT NULL,
`tag` varchar(20) NOT NULL,
`data` text NOT NULL,
`is_active` tinyint(1) unsigned NOT NULL DEFAULT '0',
`created_by` int(10) unsigned DEFAULT NULL,
`updated_by` int(10) unsigned DEFAULT NULL,
`created` datetime DEFAULT CURRENT_TIMESTAMP,
`modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT=' ';
Using this query
UPDATE cms_content SET `tag` = 'user-cities-field' WHERE id = 3;
When this query is executed, I want to essentially perform the following manipulations:
Set the INSERT id of the incoming record to NULL, so that AUTO_INCREMENT will set the next value accordingly.
Insert a copy of the record that was updated, with the only modification being that the is_active flag needs to get set to 1
I know that you cannot update the table from within here as MySQL will block it to prevent recursion.
If there is a way, can you essentially select the record being updated, prior to being updated, and then INSERT the modified record?
Would it be more useful to use a stored function to handle this?
What I need to do is to select the OLD record pre-update, and then change the value of the is_active flag on that record before it is inserted. The code I have been working with is below.
Trigger
CREATE DEFINER=`root`#`localhost` TRIGGER `cms_content_AFTER_UPDATE` BEFORE UPDATE ON `cms_content` FOR EACH ROW BEGIN
IF (NEW.is_active = 1) THEN
SET NEW.id = NULL;
INSERT INTO `tester`.`cms_content` (SELECT *, 0 as is_active from `cms_content` WHERE `id` = OLD.`id`);
#UPDATE `tester`.`cms_content` SET is_active = 0 WHERE `id` = OLD.`id`;
END IF;
END

Trigger for inserting and updating multiple records in the same table in MySQL

I have table
CREATE TABLE `seniority_master` (
`EMPNO` varchar(4) NOT NULL,
`NAME` varchar(40) DEFAULT NULL,
`GENDER` varchar(10) DEFAULT NULL,
`CATGRY` varchar(10) DEFAULT NULL,
`DOB` date DEFAULT NULL,
`DESG` varchar(40) DEFAULT NULL,
`DESGTYPE` varchar(100) DEFAULT NULL,
`SENIORITY` int(4) NOT NULL,
`EMPTYPE` varchar(45) DEFAULT NULL,
`TYPE` varchar(45) DEFAULT NULL,
`QUAL` varchar(200) DEFAULT NULL,
`BRANCH` varchar(30) DEFAULT NULL,
`DOJ` date DEFAULT NULL,
`DOCA` date DEFAULT NULL,
`PAYSCALE` varchar(45) DEFAULT NULL,
`OTHERDESC` varchar(45) DEFAULT NULL,
PRIMARY KEY (`EMPNO`,`SENIORITY`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `seniority_master` VALUES ('1389','MUNNA','Male','OBC','1959-10-31','SENIOR RECORD SORTER (ASS-I)','',2,'STAFF','P03','4th','AGRA','1982-07-01','2007-07-01','12100-24500','');
INSERT INTO `seniority_master` VALUES ('1391','RAJENDER SINGH','MALE','UR','1965-07-09','AM (GENERAL)','GENERAL',40,'OFFICERS','H1','INTER ','AGRA','1984-06-22','2013-09-05','16400-40500','');
INSERT INTO `seniority_master` VALUES ('1392','MADAN GOPAL','Male','OBC','1966-07-04','SENIOR OFFICE MANAGERS-I (GENERAL)','',6,'STAFF','L1','10TH','AGRA','1984-08-17','2015-04-17','15600-30500','');
In the above data the seniority(2,40,6) of the employee is going to change every time when the promotion takes place based on the type(P03,H1,L1) of the employee.
What we really need is a Trigger that updates the seniority of all the other employees for the type when one of the employee is updated or inserted or deleted.
Ex: Suppose empno 4620 has seniority 5 and type A1, when we delete the particular empno in seniority_master table, the seniority of below employees of the same type to be updated automatically by increment or decrement based on the seniority. Similar procedure for updation and insertion operations to done.
I am able to create AFTER INSERT and AFTER UPDATE triggers for my program requirement.
DELIMITER $$
CREATE TRIGGER after_seniority_insert
AFTER INSERT ON seniority_master
FOR EACH ROW
BEGIN
INSERT INTO emp_positions
( empno,
position,
dop)
VALUES
( NEW.empno,
NEW.desg,
NEW.doca );
END$$
DELIMITER ;
After update:
DELIMITER $$
CREATE TRIGGER after_seniority_update
AFTER UPDATE ON seniority_master
FOR EACH ROW
BEGIN
IF (old.desg != new.desg &&
old.doca != new.doca) then
INSERT INTO emp_positions
( empno,
position,
dop)
VALUES
( NEW.empno,
NEW.desg,
NEW.doca );
END IF;
END$$
DELIMITER ;
Help me out for above scenario for creating trigger.

Error executing stored procedure fails

I created a stored procedure in mysql database, which fires multiple insert statemets.
As shown below:
DELIMITER //
DROP PROCEDURE IF EXISTS insert_user_group_info
//
CREATE PROCEDURE insert_group_user_info(groupname varchar(50), groupdesc varchar(100),
createddate varchar(50), createdby varchar(100))
BEGIN
DECLARE RETURN_VAL INT UNSIGNED DEFAULT 0;
DECLARE NEWGROUPID INT UNSIGNED DEFAULT 0;
START TRANSACTION;
Insert into group_tbl (`groupname`,
`groupdesc`,
`groupusers`,
`createdon`,
`createdby`,
`groupstatus`)
values
(groupname,
groupdesc,
'1',
createddate,
createdby,
'1');
SET NEWGROUPID = LAST_INSERT_ID();
INSERT INTO useringroup_tbl
( groupid,
username,
regdate,
joindate,
userstatus,
roleid)
VALUES
( NEWGROUPID,
createdby,
createddate,
createddate,
'1',
'1');
INSERT INTO userinrole_tbl
( username,
groupid,
roleid)
VALUES
(createdby,
NEWGROUPID,
'1');
SET RETURN_VAL = LAST_INSERT_ID();
SELECT RETURN_VAL;
COMMIT;
END//
DELIMITER ;
Schema for tables are as follows
Table 1 group_tbl
CREATE TABLE `group_tbl`
(
`groupid` int(11) NOT NULL auto_increment,
`groupname` varchar(50) NOT NULL,
`groupdesc` varchar(100) NOT NULL,
`groupusers` int(11) NOT NULL,
`createdon` datetime NOT NULL,
`createdby` varchar(50) NOT NULL,
`groupstatus` tinyint(4) NOT NULL,
PRIMARY KEY (`groupid`)
);
Table 2 useringroup_tbl
CREATE TABLE IF NOT EXISTS `useringroup_tbl`
(
`groupid` int(11) NOT NULL,
`username` varchar(50) NOT NULL,
`regdate` datetime NOT NULL,
`joindate` datetime NOT NULL default '0000-00-00 00:00:00',
`userstatus` tinyint(4) NOT NULL,
`closingdate` datetime NOT NULL default '0000-00-00 00:00:00',
`roleid` int(11) NOT NULL default '0',
PRIMARY KEY (`groupid`,`username`)
);
Table 3 userinrole_tbl
CREATE TABLE IF NOT EXISTS `userinrole_tbl`
(
`id` int(11) NOT NULL auto_increment,
`username` varchar(50) NOT NULL,
`groupid` int(11) NOT NULL,
`roleid` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
While executing this procedure by this statement
call insert_group_user_info('test name','test description','2015-05-10 12:10:12','XYZuser')
I get an error
1062 - Duplicate entry '1-XYZuser' for key 1
and each time it the number appended to the username gets incremented
like this
1062 - Duplicate entry '2-XYZuser' for key 1
So please if anybody can give me direction what I am doing wrong,
Thanks in advance.
According to the error you have pasted,
1062 - Duplicate entry '1-XYZuser' - PRIMARY KEY (groupid,username) - It doesn't matter if you truncate the tables or not. You might be inserting the same data again.
You might be using last_insert_id() incorrectly.
Why is there no auto_increment in useringroup_tbl?

mySQL trigger fails on UPDATE statement

I have two tables and one trigger. The trigger fails on the UPDATE on table sensors. I have tested the trigger updating another table and that just works fine so I expect this to be a problem with locking on sensors. I'm certainly not an expert on mySQL and I did some searching. I have tried to add SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; before the first SELECT in the trigger but that did not make any difference.
Table measurements:
CREATE TABLE `measurements` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sensorid` int(16) DEFAULT NULL,
`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`distance` int(11) NOT NULL,
`temperature` float DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26727 DEFAULT CHARSET=latin1;
Table sensors:
CREATE TABLE `sensors` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` char(32) DEFAULT '',
`zeropoint` int(11) unsigned DEFAULT NULL,
`threshold` int(11) unsigned DEFAULT NULL,
`hysteresis` int(11) DEFAULT NULL,
`status` enum('normal','alarm') DEFAULT 'normal',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
Trigger raise alarm:
CREATE TRIGGER `raise alarm` BEFORE INSERT ON `measurements`
FOR EACH ROW
begin
declare zp integer;
declare st char(32);
select zeropoint into zp from sensors where id = new.sensorid;
select status into st from sensors where id = new.sensorid;
if new.distance > zp then
if st = 'normal' then
update sensors set status = 'alarm' where id = new.sensorid;
end if;
end if;
end;
There is something in the documentation that you might be interested in:
https://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html
In a BEFORE trigger, the NEW value for an AUTO_INCREMENT column is 0,
not the sequence number that is generated automatically when the new
row actually is inserted.
It means, that all queries in your trigger are always looking for a record with id=0, since id column in measurement table is auto-increment.
In case there is no record id=0 in sensors table, then zp variable is null, and this condition: if new.distance > zp then is always false.

MySql - can BEFORE INSERT TRIGGER insert into 2 columns?

Can this trigger be changed so that the sortorder table gets 2 column values (sortOrderId, sortOrder) inserted?
How is the value of sortOrder found?
If it is known and can be inserted into image table then can it also be inserted into the sortorder table?
-- Trigger DDL Statements
DELIMITER $$
USE `nextcart`$$
CREATE
DEFINER=`root`#`localhost`
TRIGGER `nextcart`.`insert_sortorderid`
BEFORE INSERT ON `nextcart`.`image`
FOR EACH ROW
BEGIN
INSERT INTO sortorder SET sortOrderId = NULL, sortOrder = NEW.sortOrder;
SET NEW.sortOrderId = (SELECT LAST_INSERT_ID());
END;
$$
CREATE TABLE sortorder:
delimiter $$
CREATE TABLE `sortorder` (
`sortOrderId` int(11) NOT NULL AUTO_INCREMENT,
`sortOrder` tinyint(4) NOT NULL,
PRIMARY KEY (`sortOrderId`),
KEY `sort_order` (`sortOrderId`,`sortOrder`),
CONSTRAINT `fk_sortOrderId` FOREIGN KEY (`sortOrderId`) REFERENCES `image` (`imageId`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8$$
CREATE TABLE image:
delimiter $$
CREATE TABLE `image` (
`imageId` int(11) NOT NULL AUTO_INCREMENT,
`imageFileName` varchar(45) DEFAULT NULL,
`imagePath` varchar(255) DEFAULT NULL,
`imageTitle` varchar(100) DEFAULT NULL,
`imageAlt` varchar(100) DEFAULT NULL,
`imageWidth` int(11) DEFAULT NULL,
`imageHeight` int(11) DEFAULT NULL,
`classId` int(11) DEFAULT NULL,
`imageSizeId` tinyint(4) NOT NULL,
`isImageEnabled` bit(1) DEFAULT b'0',
`sortOrderId` int(11) DEFAULT NULL,
PRIMARY KEY (`imageId`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8$$
ERROR MESSAGE:
Error 1054: Unknown column 'sortOrder' in 'NEW' SQL Statement:
CREATE TRIGGER insert_sortorderid BEFORE INSERT ON image FOR EACH
ROW BEGIN INSERT INTO nextcart.sortorder SET sortOrderId = NULL,
sortOrder = NEW.sortOrder; SET NEW.sortOrderId = ( SELECT
LAST_INSERT_ID()); END; Error when running failback script. Details
follow. Error 1050: Table 'image' already exists SQL Statement: CREATE
TABLE image ( imageId int(11) NOT NULL AUTO_INCREMENT,
imageFileName varchar(45) DEFAULT NULL, imagePath varchar(255)
DEFAULT NULL, imageTitle varchar(100) DEFAULT NULL, imageAlt
varchar(100) DEFAULT NULL, imageWidth int(11) DEFAULT NULL,
imageHeight int(11) DEFAULT NULL, classId int(11) DEFAULT NULL,
imageSizeId tinyint(4) NOT NULL, isImageEnabled bit(1) DEFAULT
b'0', sortOrderId int(11) DEFAULT NULL, PRIMARY KEY (imageId)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
There is no column named sortOrder in the image table.
So, the reference to NEW.sortOrder (on the insert statement in the trigger) is invalid.
To answer your first question: No. Since there is no value supplied for that in the INSERT statement (which fires the BEFORE INSERT TRIGGER), you don't really have a source for that value.
The easy option is to provide a default value for it.
If you want to supply a value for the sortOrder column, then one option is to add a sortOrder column to the image table, and then the value can be supplied in the INSERT INTO image statement. Then it would available in the trigger.
(The purpose of the sortorder table is not at all clear.)