MySQL: Set to default value on update - mysql

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.

Related

Conditional values in MySQL Trigger

I have a question about MySQL triggers—say I have the following table:
CREATE TABLE test (
id INT(6),
value_1 INT(6),
value_2 INT(6),
values_were_set BOOL
)
Now, every time I insert a value into this table I want to have a trigger check if value_1 and value_2 have been set. And if they are, it should set values_were_set to true.
NULL values are allowed.
How would I go about this? In the real table there are about ten columns that I want to check for, so I would fancy not to have to use a bunch of IF statements.
Maybe it's better to do it in the app?
Then trigger it is:
DELIMITER $$
CREATE TRIGGER value_check BEFORE INSERT ON test
FOR EACH ROW
BEGIN
IF NEW.value_1 IS NOT NULL AND NEW.value_2 IS NOT NULL THEN
SET NEW.values_were_set = 1;
ELSE
SET NEW.values_were_set = 0;
END IF;
END;
$$
Unfortunately yes, I think you would need a nasty if/else section in the trigger if you want to check multiple combinations of fields

How to create a Trigger within sql

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.

MySQL Procedure check if record exists before insert not working

I have looked at the other questions on here about this. It isn't working.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `environment_admin`(
IN environment_id TEXT,
IN user_id TEXT,
IN username VARCHAR(75),
IN password VARCHAR(512)
)
BEGIN
DECLARE env_id INT;
DECLARE admin_id INT;
SET env_id = CAST(environment_id AS SIGNED INT);
SET admin_id = CAST(user_id AS SIGNED INT);
IF NOT EXISTS(SELECT 1 FROM `environment`.`environment_accounts` WHERE environment_id = env_id AND user_id = admin_id) THEN
BEGIN
INSERT INTO
`environment`.`environment_accounts`
(
`environment_id`,
`user_id`,
`username`,
`password`,
`is_active`,
`is_admin`,
`is_mod`
)
VALUES
(
env_id,
admin_id,
username,
password,
1,
1,
1
);
END;
END IF;
END
So if I run:
CALL `environment`.`environment_admin`('22','1','kacieh','512c9ad228332bbd30d09ce7ffb8896e00a1610e914a5fa180bf15ce702b90423e6a9540579f672315ae3c6cb1b8d06ee2b784b4761e806675aa88c2a915553e');
I get 0 row(s) effected
and sure enough, nothing happened. -_- I have been working on this hours
I tested the conditional query, it works.
I have tested just the insert statement inside a stored proc, it works as well.
Stop doing it like that, it's inefficient and it could be worse if two insertare running concurrently! :)
Use INSERT.... ON DUPLICATE KEY UPDATE ... see here
One trick is to do ON DUPLICATE KEY UPDATE environment_id = env_id (not changing the column, so nothing will be updated, ensuring the INSERT will not work without any error condition, you might check number of modified/inserted rows after that)
I realise you've solved your problem (and +1 for Parallelis's answer, especially for highlighting the concurrency issue), but just in case it helps someone else...
MySQL was probably getting confused between your parameters environment_id and user_id and the environment_accounts columns environment_id and user_id. I suspect the parameters were taking precedence in the WHERE clause, meaning as long as there's at least one row in environment_accounts, the NOT EXISTS clause would always return false, and your insert would never run.
For example, if your environment_id and user_id parameters had values of 1 and 2 respectively, the NOT EXISTS clause would evaluate as
IF NOT EXISTS(SELECT 1 FROM `environment`.`environment_accounts` WHERE 1 = 1 AND 2 = 2) THEN
Might be worth having a naming convention for your parameters (and other variables), such as adding a prefix like p_ for parameter.

Mysql trigger issue (i think it's firing)

I have an issue with a trigger on a mysql database. I have a table such as follows:
id int not null auto_increment (PK)
parent_id int not null,
rank int not null
What I'm trying to do is use a trigger to update the rank to the next highest +10 when they have the same parent_id, but this doesn't seem to be working.
DELIMITER $$
DROP TRIGGER IF EXISTS after_insert $$
create trigger after_insert
after insert on mytable
FOR EACH row
BEGIN
IF EXISTS (SELECT rank FROM mytable WHERE parent_id = new.parent_id AND id != new.id ORDER BY rank DESC LIMIT 1) THEN
UPDATE mytable SET rank = 10
WHERE id = new.id;
ELSE
UPDATE mytable SET rank = 20
WHERE id = new.id;
END IF;
END
$$
I've tried setting the new rank to a variable and calling the update statement using that, and again it didn't work. I even created another table to log what values were being selected and that worked perfectly so I can't quite understand what's going on. Is it a case of, although the trigger is "AFTER INSERT" the insert hasn't actually happened so it can't update the row it's just inserted? Another reason I ask this is, I've even tried updating the rank to different values e.g 1 and 2 depending on which statement it goes to, but it always ends up being 0.
I think you're on the right track with this thought:
Is it a case of, although the trigger is "AFTER INSERT" the insert hasn't actually happened so it can't update the row it's just inserted?
From the FAQ:
B.5.9: Can triggers access tables?
A trigger can access both old and new data in its own table. A trigger can also affect other tables, but it is not permitted to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger.
The documentation isn't clear that what you're doing won't work. OTOH, the documentation isn't clear that what you're trying to do will work either.
I think you'd be better off using a BEFORE INSERT trigger and setting NEW.rank in there. Then, the new row would have the right rank value when it is actually inserted into the table rather than patching it after. Also, you'd be able to simplify your existence check to just this:
EXISTS(SELECT rank FROM mytable WHERE parent_id = new.parent_id)
as NEW.id wouldn't have a useful value and the new row wouldn't be in the table anyway; the ORDER BY and LIMIT are also unnecessary as you're just checking if something exists so I took them out.
A BEFORE INSERT trigger seems to match your intent better anyway and that will give you correct data as soon as it is inserted into your table.
If you want the rank to be set +10 more than highest "brother's" rank, you could use:
DELIMITER $$
DROP TRIGGER IF EXISTS whatever $$
create trigger whatever
BEFORE INSERT ON mytable
FOR EACH row
BEGIN
SET NEW.rank = 10 + COALESCE(
( SELECT max(rank)
FROM mytable
WHERE parent_id = NEW.parent_id
), 0 ) ;
END
$$

MySQL Trigger - Storing a SELECT in a variable

I have a trigger in which I want to have a variable that holds an INT I get from a SELECT, so I can use it in two IF statements instead of calling the SELECT twice. How do you declare/use variables in MySQL triggers?
You can declare local variables in MySQL triggers, with the DECLARE syntax.
Here's an example:
DROP TABLE IF EXISTS foo;
CREATE TABLE FOO (
i SERIAL PRIMARY KEY
);
DELIMITER //
DROP TRIGGER IF EXISTS bar //
CREATE TRIGGER bar AFTER INSERT ON foo
FOR EACH ROW BEGIN
DECLARE x INT;
SET x = NEW.i;
SET #a = x; -- set user variable outside trigger
END//
DELIMITER ;
SET #a = 0;
SELECT #a; -- returns 0
INSERT INTO foo () VALUES ();
SELECT #a; -- returns 1, the value it got during the trigger
When you assign a value to a variable, you must ensure that the query returns only a single value, not a set of rows or a set of columns. For instance, if your query returns a single value in practice, it's okay but as soon as it returns more than one row, you get "ERROR 1242: Subquery returns more than 1 row".
You can use LIMIT or MAX() to make sure that the local variable is set to a single value.
CREATE TRIGGER bar AFTER INSERT ON foo
FOR EACH ROW BEGIN
DECLARE x INT;
SET x = (SELECT age FROM users WHERE name = 'Bill');
-- ERROR 1242 if more than one row with 'Bill'
END//
CREATE TRIGGER bar AFTER INSERT ON foo
FOR EACH ROW BEGIN
DECLARE x INT;
SET x = (SELECT MAX(age) FROM users WHERE name = 'Bill');
-- OK even when more than one row with 'Bill'
END//
CREATE TRIGGER clearcamcdr AFTER INSERT ON `asteriskcdrdb`.`cdr`
FOR EACH ROW
BEGIN
SET #INC = (SELECT sip_inc FROM trunks LIMIT 1);
IF NEW.billsec >1 AND NEW.channel LIKE #INC
AND NEW.dstchannel NOT LIKE ""
THEN
insert into `asteriskcdrdb`.`filtre` (id_appel,date_appel,source,destinataire,duree,sens,commentaire,suivi)
values (NEW.id,NEW.calldate,NEW.src,NEW.dstchannel,NEW.billsec,"entrant","","");
END IF;
END$$
Dont try this # home
`CREATE TRIGGER `category_before_ins_tr` BEFORE INSERT ON `category`
FOR EACH ROW
BEGIN
**SET #tableId= (SELECT id FROM dummy LIMIT 1);**
END;`;
I'm posting this solution because I had a hard time finding what I needed. This post got me close enough (+1 for that thank you), and here is the final solution for rearranging column data before insert if the data matches a test.
Note: this is from a legacy project I inherited where:
The Unique Key is a composite of rridprefix + rrid
Before I took over there was no constraint preventing duplicate unique keys
We needed to combine two tables (one full of duplicates) into the main table which now has the constraint on the composite key (so merging fails because the gaining table won't allow the duplicates from the unclean table)
on duplicate key is less than ideal because the columns are too numerous and may change
Anyway, here is the trigger that puts any duplicate keys into a legacy column while allowing us to store the legacy, bad data (and not trigger the gaining tables composite, unique key).
BEGIN
-- prevent duplicate composite keys when merging in archive to main
SET #EXIST_COMPOSITE_KEY = (SELECT count(*) FROM patientrecords where rridprefix = NEW.rridprefix and rrid = NEW.rrid);
-- if the composite key to be introduced during merge exists, rearrange the data for insert
IF #EXIST_COMPOSITE_KEY > 0
THEN
-- set the incoming column data this way (if composite key exists)
-- the legacy duplicate rrid field will help us keep the bad data
SET NEW.legacyduperrid = NEW.rrid;
-- allow the following block to set the new rrid appropriately
SET NEW.rrid = null;
END IF;
-- legacy code tried set the rrid (race condition), now the db does it
SET NEW.rrid = (
SELECT if(NEW.rrid is null and NEW.legacyduperrid is null, IFNULL(MAX(rrid), 0) + 1, NEW.rrid)
FROM patientrecords
WHERE rridprefix = NEW.rridprefix
);
END
Or you can just include the SELECT statement in the SQL that's invoking the trigger, so its passed in as one of the columns in the trigger row(s). As long as you're certain it will infallibly return only one row (hence one value). (And, of course, it must not return a value that interacts with the logic in the trigger, but that's true in any case.)
As far I think I understood your question
I believe that u can simply declare your variable inside "DECLARE"
and then after the "begin" u can use 'select into " you variable" ' statement.
the code would look like this:
DECLARE
YourVar varchar(50);
begin
select ID into YourVar from table
where ...