mySQL trigger fails on UPDATE statement - mysql

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.

Related

Get data of table with some data from other table

I have a simple table visitor and another table visitor_tokens.
SQL creation script of visitor:
CREATE TABLE `visitor` (
`id` int(7) UNSIGNED NOT NULL,
`phone` varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
ALTER TABLE `visitor`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `phone_index` (`phone`);
ALTER TABLE `visitor`
MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
INSERT INTO `visitor` (`phone`) VALUES
('111111111');
SQL creation script of visitor_tokens:
CREATE TABLE `visitor_tokens` (
`id` int(7) UNSIGNED NOT NULL,
`visitor` int(7) UNSIGNED NOT NULL,
`token` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `visitor_tokens`
ADD PRIMARY KEY (`id`);
ALTER TABLE `visitor_tokens`
MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT;
INSERT INTO `visitor_tokens` (`visitor`, `token`) VALUES
(1, 'abc_token'),
(1, 'xyz_token');
I want to get some data of visitor by id:
I want to get the phone number (phone column) and the visitor tokens (visitor_tokens.token). All - according to given id.
My current SQL script is: SELECT visitor.phone, visitor_tokens.token FROM visitor JOIN visitor_tokens ON visitor_tokens.visitor=visitor.id WHERE id=1. This gives me only the phone and the first token: abc. But I also want to get the tokens of the visitor from the second table. To get something like [abc_token, xyz_token]. How can I do it?
So this is my solution (also recommend on reading the main comments):
SELECT visitor.phone, GROUP_CONCAT(visitor_tokens.token) AS tokens FROM visitor LEFT JOIN visitor_tokens ON visitor_tokens.visitor=visitor.id WHERE visitor.id=1 LIMIT 1;
It returns result regardless of missing visitor tokens (LEFT JOIN), and also if there are tokens - it returns the tokens separated by comma in one row.

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

Using Auto-Increment value in MYSQL Before Insert Trigger?

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

mysql trigger work not always

I try to insert trigger to mysql table of 3Th party windows program.
In original it was seample table:
CREATE TABLE `data` (
`id` int(11) DEFAULT NULL,
`dt` datetime DEFAULT NULL,
`x` double DEFAULT NULL,
`y` double DEFAULT NULL,
`sent` tinyint(4) DEFAULT NULL,
KEY `fast` (`dt`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1$$
My goal is mark a data, inserted from my php scripts. For this purpose i create 2 new field in table.
`sent` tinyint(4) DEFAULT NULL,
`sourceColumn` int(11) DEFAULT NULL,
sent - for marking is data alredy used or not, and sourceColumn - to mark insertion reason.
sometimes i need to set send=99 in ponts, are inserted of 3Th party windows program. For this i create trigger:
CREATE
DEFINER=`root`#`%`
TRIGGER `abc`.`data`
BEFORE INSERT ON `abc`.`data`
FOR EACH ROW
BEGIN
IF (EXISTS (select ID FROM OrigLock where id=NEW.id AND NEW.dt between date_start AND date_stop) ) THEN
SET NEW.sent=99;
END IF;
END
$$
This work fine, but some times approximaly 1 of 1000 in table exist rows, where dt inside interval date_start AND date_stop and sent=NULL and sourceColumn=NULL, i.e. looks like rows inserted Windows programm without trigger.
Have any ideas on how data can get to the table, without processing the trigger?
OrigLock Structure:
enter code here
CREATE TABLE `OrigLock` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`DataID1` int(11) NOT NULL,
`DataID2` int(11) NOT NULL,
`date_start` datetime DEFAULT NULL,
`date_stop` datetime DEFAULT NULL,
PRIMARY KEY (`ID`,`DataID1`)
) ENGINE=InnoDB AUTO_INCREMENT=450 DEFAULT CHARSET=latin1

MySQL trigger with join

I have two tables, a logins table which captures login information, and a ranges table which associates IP info with countries. I am trying to create a trigger which updates the logins.country column after an insert by performing a join to the ranges table.
The tables are structured as such:
CREATE TABLE logins (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
users_id int(10) unsigned NOT NULL,
ip int(10) unsigned NOT NULL,
country varchar(2) DEFAULT NULL,
status tinyint(4) NOT NULL,
timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE ranges (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
first int(10) unsigned NOT NULL,
last int(10) unsigned NOT NULL,
country varchar(2) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY first_UNIQUE (first),
UNIQUE KEY last_UNIQUE (last)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have this trigger but it doesn't seem to work:
CREATE TRIGGER update_country
AFTER INSERT ON logins
FOR EACH ROW
UPDATE logins l
JOIN ranges r ON l.ip >= first AND l.ip <= last
SET l.country = r.country
WHERE l.id = NEW.id;
Any idea where I'm going wrong here?
After playing around with this for a while, I realized a join wasn't necessary and I was making this far more complex than it needed to be. Here is the final trigger:
CREATE TRIGGER update_country BEFORE INSERT
ON logins FOR EACH ROW
SET NEW.country = (SELECT country FROM ranges WHERE NEW.ip >= first AND NEW.ip <= last);