I'm trying to create a trigger which sets selectoin allow to 1 when main state is updated to 3. However I can't get the query to work correctly. So far I have:
create table main
(main_id varchar(30) primary key,
name varchar(30) null,
state int null,
update_timestamp timestamp null);
create table selection
(id varchar(30) primary key,
allow varchar(30) null,
last_update_timestamp timestamp null);
//
create trigger upd_selectoin
before update on main
for each row
IF new.state = 3
THEN
UPDATE selection s
JOIN main m
ON m.main_id = s.id
SET s.allow = 1
WHERE s.id = NEW.main_id;
END IF;
END;
//
insert into main values (1,'row1',1,null);
insert into main values (2,'row2',0,null);
insert into selection values (1,null,null);
insert into selection values (2,null,null);
Error message:
Schema Creation Failed: 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 '//
You have at least two problems with the syntax:
You meant to use DELIMITER //
You forgot BEGIN
The correct definition might look like
DELIMITER //
CREATE TRIGGER upd_selectoin
BEFORE UPDATE ON main
FOR EACH ROW
BEGIN
IF NEW.state = 3 THEN
UPDATE selection s JOIN main m
ON m.main_id = s.id
SET s.allow = 1
WHERE s.id = NEW.main_id;
END IF;
END //
DELIMITER ;
Here is SQLFiddle demo
Now in your case
You probably want to use AFTER event instead of BEFORE to make sure that update in main has been made successfully before you make any updates to selection.
Also there is no point in JOINing selection with main since you already know main_id.
You can utilize INSERT ... ON DUPLICATE KEY UPDATE to make sure that a row in selection will exist even if it wasn't before
Looking at the selection schema you probably meant to assign a timestamp during that update
That being said a more succinct version of your trigger might look like
CREATE TRIGGER upd_selection
AFTER UPDATE ON main
FOR EACH ROW
INSERT INTO selection (id, allow, last_update_timestamp)
VALUES (NEW.main_id, IF(NEW.state = 3, 1, NULL), NOW())
ON DUPLICATE KEY UPDATE allow = VALUES(allow),
last_update_timestamp = VALUES(last_update_timestamp);
This version of a trigger doesn't even need DELIMITER and BEGIN ... END block because it contains only one statement.
Here is SQLFiddle demo
Related
I have a SQL table that can reference another record in the table as its parent but should not reference itself. I have attempted to enforce this with a CHECK constraint but my attempts have failed as the id is an auto-increment column. Is there any other way to ensure that parent_id <> id?
My current attempt, which fails with error Check constraint 'not_own_parent' cannot refer to an auto-increment column. (errno 3818):
CREATE TABLE `content` (
`id` serial PRIMARY KEY NOT NULL,
`item_id` int NOT NULL,
`nested_item_id` int,
`block_id` int,
`order` int NOT NULL,
CONSTRAINT not_own_parent CHECK (nested_item_id <> id)
);
Here's a demo of using a trigger to cancel an insert that violates the condition you describe. You must use an AFTER trigger because in a BEFORE trigger the auto-increment value has not yet been generated.
mysql> delimiter ;;
mysql> create trigger t after insert on content
-> for each row begin
-> if NEW.nested_item_id = NEW.id then
-> signal sqlstate '45000' set message_text = 'content cannot reference itself';
-> end if;
-> end;;
mysql> delimiter ;
mysql> insert into content set item_id = 1, nested_item_id = 1, `order` = 1;
ERROR 1644 (45000): content cannot reference itself
mysql> insert into content set item_id = 1, nested_item_id = 2, `order` = 1;
Query OK, 1 row affected (0.01 sec)
Don't put this kind of thing in a constraint. For one thing, you can't do it directly in MySql. You'd have to use a trigger or something.
Instead:
write your CRUD code carefully, so it avoids generating incorrect rows. You have to do that anyway.
write a little program called "database_consistent" or something. Have it run a bunch of queries looking for any errors like the one you're trying to avoid. Have it send emails or SMSs if it finds problems. Run it often during development and at least daily in production.
One way to control auto-generated live values is by using triggers to manage new values.
For example, create instead of insert trigger to control newly generated ID. In triggers, you can make decisions based on the new value.
I am trying to avoid deletion of more than 1 row at a time in MySQL by using a BEFORE DELETE trigger.
The sample table and trigger are as below.
Table test:
DROP TABLE IF EXISTS `test`;
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
PRIMARY KEY (`id`));
INSERT INTO `test` (`id`, `a`, `b`)
VALUES (1, 1, 2);
INSERT INTO `test` (`id`, `a`, `b`)
VALUES (2, 3, 4);
Trigger:
DELIMITER //
DROP TRIGGER IF EXISTS prevent_multiple_deletion;
CREATE TRIGGER prevent_multiple_deletion
BEFORE DELETE ON test
FOR EACH STATEMENT
BEGIN
IF(ROW_COUNT()>=2) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Cannot delete more than one order per time!';
END IF;
END //
DELIMITER ;
This is still allowing multiple rows deletion. Even if I change the IF to >= 1, still allows the operation.
I my idea is to avoid operations such as:
DELETE FROM `test` WHERE `id`< 5;
Can you help me? I know that the current version of MySQL doesn't allow FOR EACH STATEMENT triggers.
Thank you!
Firstly, getting some syntax error(s) out of our way, from your original attempt:
Instead of FOR EACH STATEMENT, it should be FOR EACH ROW.
Since you have already defined the Delimiter to //; you need to use // (instead of ;) in the DROP TRIGGER IF EXISTS .. statement.
Row_Count() will have 0 value in a Before Delete Trigger, as no rows have been updated yet. So this approach will not work.
Now, the trick here is to use Session-level Accessible (and Persistent) user-defined variables. We can define a variable, let's say #rows_being_deleted, and later check whether it is already defined or not.
For Each Row runs the same set of statements for every row being deleted. So, we will just check whether the session variable already exists or not. If it does not, we can define it. So basically, for the first row (being deleted), it will get defined, which will persist as long as the session is there.
Now if there are more rows to be deleted, Trigger would be running the same set of statements for the remaining rows. In the second row, the previously defined variable would be found now, and we can simply throw an exception now.
Note that there is a chance that within the same session, multiple delete statements may get triggered. So before throwing exception, we need to set the #rows_being_deleted value back to null.
Following will work:
DELIMITER //
DROP TRIGGER IF EXISTS prevent_multiple_deletion //
CREATE TRIGGER prevent_multiple_deletion
BEFORE DELETE ON `test`
FOR EACH ROW
BEGIN
-- check if the variable is already defined or not
IF( #rows_being_deleted IS NULL ) THEN
SET #rows_being_deleted = 1; -- set its value
ELSE -- it already exists and we are in next "row"
-- just for testing to check the row count
-- SET #rows_being_deleted = #rows_being_deleted + 1;
-- We have to reset it to null, as within same session
-- another delete statement may be triggered.
SET #rows_being_deleted = NULL;
-- throw exception
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Cannot delete more than one order per time!';
END IF;
END //
DELIMITER ;
DB Fiddle Demo 1: Trying to delete more than row.
DELETE FROM `test` WHERE `id`< 5;
Result:
Query Error: Error: ER_SIGNAL_EXCEPTION: Cannot delete more than one
order per time!
DB Fiddle Demo 2: Trying to delete only one row
Query #1
DELETE FROM `test` WHERE `id` = 1;
Deletion successfully happened. We can check the remaining rows using Select.
Query #2
SELECT * FROM `test`;
| id | a | b |
| --- | --- | --- |
| 2 | 3 | 4 |
I need a bit of help with creating a trigger in mysql:
I have a column named “country” and another one named “tag”.
Everytime when someone insert in the city “Los Angeles” for example, I want that my trigger to insert in “tag” column the text “is from California”.
Edit:
delimiter //
CREATE TRIGGER update_tag AFTER UPDATE ON users
FOR EACH ROW
BEGIN
IF (city = 'Los Angeles') THEN
INSERT INTO users(tag) VALUES (California);
END IF;
END;//
delimiter ;
That seems to be executed with no errors, but is not inserting anything in "tag" column,
Any ideea why?
PS. I would appreciate from the ones that rated this post with "-" to write me a PM and tell me what I did wrong :). Thank you.
You cannot use an insert statement to update the row you are currently processing. You should use the SET NEW.cxy = "" syntax.
I have prepared a working sqlfiddle for you, which hopefully shows want you wanted to achieve.
CREATE TABLE users (
id int auto_increment PRIMARY KEY,
`city` varchar(255),
`tag` varchar(255)
)//
CREATE TRIGGER update_tag BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF (NEW.city = 'Los Angeles') THEN
SET NEW.tag = "California";
END IF;
END//
INSERT INTO users VALUES (1, 'test', '')//
UPDATE users SET `city` = 'Los Angeles'//
Please notice that this is also a BEFORE UPDATE trigger, so that your changes are saved as well.
If one issues a SELECT * FROM users one receives a single row with
1 Los Angeles California
There is also a page in the MySQL manual containing trigger examples. You should read that thoroughly.
I have been trying to create a Trigger, however my attempts have been unsuccessful. I seem to be getting an error (#1064), which I have no solution for. Can somebody explain or demonstrate any faults in the syntax.
Let me specify:
I have delivery_id as primary key in delivery table,
I also have delivery_id as a foreign key in entry_log table.
By comparing both id's(if true), will return a text referring to the output of the bit (either 0 or 1)
DELIMITER //
DROP TRIGGER IF EXISTS entry_trigger//
CREATE TRIGGER entry_trigger BEFORE INSERT ON entry_log
FOR EACH ROW
BEGIN
DECLARE #xentry VARCHAR(45)
DECLARE #inta bit
SET #inta = SELECT allowed
FROM delivery
WHERE delivery.delivery_id = entry_log.delivery_id;
CASE
when #inta = 0 then #xentry = 'Acces Denied'
when #inta = 1 then #xentry = 'Acces Allowed'
END CASE
INSERT INTO entry_log(entry_time,access_allowed) VALUES(now(),#xentry);
END
//
This is assuming that you use MySQL. In the body of the trigger you use
WHERE delivery.delivery_id = entry_log.delivery_id;
I think you want to compare to the entry_log entry that the trigger is running on, right? In that case you must use this syntax:
WHERE delivery.delivery_id = NEW.delivery_id;
see here for more examples.
UPDATE
I see that also you try to do an INSERT INTO entry_log within the TRIGGER. This will of course not work, because you would create an infinite recursive loop. Within the
body of the trigger you can do unrelated table access, but not into the table you are inserting. You can change the values to be inserted by the trigger by setting NEW.xyz = whatever
UPDATE 2
I doubt, that your CASE statement is correct. At least it must end with END CASE. You can use IF here, since you don't have many cases to address. If you must use CASE this post might help you: MYSQL Trigger set datetime value using case statement
UPDATE 3
I am not sure, but I think you need brackets around the variable setting statement. try this trigger definition:
DELIMITER //
DROP TRIGGER IF EXISTS entry_trigger//
CREATE TRIGGER entry_trigger BEFORE INSERT ON entry_log
FOR EACH ROW
BEGIN
SET #inta = (SELECT allowed
FROM delivery
WHERE delivery.delivery_id = NEW.delivery_id);
SET NEW.access_allowed = #inta;
SET NEW.entry_time = NOW();
END
//
Note, that this is written out of my head, so beware of syntax errors in my script.
Is it possible to set a column to its default value (or any specified value) on update when no value is specifically given in the statement? I was thinking that a trigger might accomplish this. Something like
IF ISNULL(NEW.column) THEN
NEW.column = value
END IF;
didn't work.
MySQL has function called DEFAULT(), which gets the default value from specified column.
UPDATE tbl SET col = DEFAULT(col);
MySQL Reference
UPDATE:
#JanTraenkner As far as I can tell, this is not possible. You can however make sure in your application code, that all columns are mentioned in your update statement and for those that do not have a value your use NULL as value. Then your trigger code is almost right, you just need to change it to
IF (NEW.column IS NULL) THEN
SET NEW.column = value
END IF;
Original answer:
I understood your question like, "set column to default value, if I don't specify the column in an update statement (which updates other columns from that table)".
To check with ISNULL() or col IS NULL doesn't work here, because when you don't specify it in the update statement it simply isn't there. There's nothing to check for.
I wrote this little example script which makes it work like I understood the question.
drop table if exists defvalue;
create table defvalue (id int auto_increment primary key, abc varchar(255) default 'default');
insert into defvalue (id) values (null);
insert into defvalue (id, abc) values (null, 'not_default_value');
insert into defvalue (id, abc) values (null, 'another_not_default_value');
drop trigger if exists t_defval;
delimiter $$
create trigger t_defval before update on defvalue
for each row
begin
set #my_def_value = (select default(abc) from defvalue limit 1);
if (new.abc = old.abc) then
set new.abc = #my_def_value;
end if;
end $$
delimiter ;
select * from defvalue;
update defvalue set id = 99 where id = 1;
select * from defvalue;
update defvalue set id = 98 where id = 2;
select * from defvalue;
I also had to save the default value of the column in a variable first because the function needs to know from which table. Unfortunately one can't specify that as parameter, not even as default(tablename.column).
All in all, please note, that this is rather a proof of concept. I'd recommend to solve this on application layer, not database layer. Having a trigger for this seems a bit dirty for me.