Cannot update value on insert if salary is greater than - mysql

I have a problem creating a trigger for a basic table that will check on insert if one of the values inserted is bigger than 3000 and replace it with 0. It throws this error:
Can't update table 'staff' in stored function/trigger because it is already used by statement which invoked this stored function/trigger
The structure of the table is very simple:
CREATE TABLE `staff` (
`ID` int(11) NOT NULL,
`NAZWISKO` varchar(50) DEFAULT NULL,
`PLACA` float DEFAULT NULL
)
And the trigger for it looks like this:
BEGIN
IF new.placa >= 3000 THEN
UPDATE staff SET new.placa = 0;
END IF;
END
I don't understand fully what occurs here, but I suspect some recursion, but I am quite new to the topic of triggers and I have lab coming, so I want to be prepared for it.

MySQL disallows triggers from doing UPDATE/INSERT/DELETE against the same table for which the trigger executed, because there is too great a chance of causing an infinite loop. That is, in UPDATE trigger, if you could UPDATE the same table, that would cause the UPDATE trigger to execute, which would UPDATE the same table, and so on and so on.
But I guess you only want to change the value of placa on the same row being handled by the trigger. If so, just SET it:
BEGIN
IF new.placa >= 3000 THEN
SET new.placa = 0;
END IF;
END
Remember that you must use a BEFORE trigger when changing column values.

Related

MySQL Before Delete trigger to avoid deleting multiple rows

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 |

stored procedure returning `ASCII \0` error but can't find what that is referring to

I am writing my first stored procedure as a trigger. I am doing this in a dev migration as we have two systems which don't speak to each other in dev, so I need to mock the data which would normally come from the other system.
My procedure is added as part of our dev migration script.
DELIMITER |;
CREATE TRIGGER `activity_insert` AFTER INSERT ON `activity`
FOR EACH ROW
BEGIN
UPDATE `activity` AS `a` JOIN `handle` AS `h` on `a.handle_id` = `h.handle_id` SET `path` = CONCAT(`h.handle`,'/',`a.activity_handle`) WHERE `a.path` IS NULL;
END;
|
DELIMITER;
I would expect the logic to be:
DELIMITER $$
CREATE TRIGGER activity_insert BEFORE INSERT ON activity
FOR EACH ROW
BEGIN
IF new.path IS NULL THEN
SET new.path = (SELECT CONCAT(h.handle, '/', new.activity_handle)
FROM handle h
WHERE new.handle_id = h.handle_id
);
END IF;
END;$$
DELIMITER;
There are numerous problem with your code:
You don't update the table being modified using update.
You want a "before" triggers, not an "after trigger".
Don't use | for the the delimited. It is a valid MySQL operator.
You have over-used the backtick, including putting the table alias in with the column alias.
This assumes that handle.handle_id is unique. This seems like a reasonable assumption based on the names, but you can add limit 1 to guarantee no more than one row is returned.

deleting the data after copying it into another database

I have a stored procedure , its contents are as follows:
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER=`MailMe`#`%` PROCEDURE `sp_archivev3`()
BEGIN
INSERT INTO
send.sgev3_archive(a_bi,
b_vc,
c_int,
d_int,
e_vc,
f_vc,
g_vc,
h_vc,
i_dt,
j_vc,
k_vc,
l_vc,
m_dt,
n_vch,
o_bit)
SELECT a_bi,
b_vc,
c_int,
d_int,
e_vc,
f_vc,
g_vc,
h_vc,
i_dt,
j_vc,
k_vc,
l_vc,
m_dt,
n_vch,
o_bit
FROM send.sgev3
WHERE m_dt BETWEEN '2014-06-09' AND CURDATE();
END
Since, my query is inserting the records into send.sgev3_archive from send.sgev3. I want to do one more thing. I want to delete the records present in the send.sgev3 table after selecting and inserting the same into send.sgev3_archive. Should I write the DELETE query right below the SELECT query in my code above? Just wanted to confirm as I don't want to mess up my real data and accidently delete any records without getting it copied. Please advise.
Yes exactly. Include a DELETE statement saying
DELETE FROM send.sgev3
WHERE m_dt BETWEEN '2014-06-09' AND CURDATE();
To be more sure that INSERT does completes before DELETE invokes; wrap the INSERT and DELETE in a Transaction Block saying
START TRANSACTION;
INSERT INTO send.sgev3_archive ...
SELECT ... FROM send.sgev3
COMMIT;
You can as well handle error condition in your procedure and ROLLBACK the entire transaction by using exit handler in stored procedure. Below post already shows an way to do the same. Take a look.
How can I use transactions in my MySQL stored procedure?
MySQL Rollback in transaction
EDIT:
why transaction is necessary? Can't I just proceed like the way I have
mentioned in my question?
Instead of explaining you why; let's show you an example (Quite resemble your scenario)
Let's say you have a table named parent declared as
create table parent(id int not null auto_increment primary key,
`name` varchar(10),city varchar(10));
Insert some records to it
insert into parent(`name`,city) values('sfsdfd','sdfsdfdf'),('sfsdfd','sdfsdfdf'),('sfsdfd',null)
Now, you have another table named child defined as below (Notice the last column has not null constraint)
create table child(id int not null auto_increment primary key,
`name` varchar(10),city varchar(10) not null)
Now execute both the below statement (what you are currently doing)
insert into child(`name`,city) select * from parent;
delete from parent;
Result: INSERT will fail due to the not null constraint in child table but delete will succeed.
To avoid this exact scenario you need Transaction in place. so that, if INSERT fails you don't go for delete at all.
A pseudo code on how you handle this in transaction
start transaction
insert into child(`name`,city) select * from parent;
if(ERROR)
rollback
exit from stored proc
else
commit
delete from parent;
SideNote: exit from stored proc can be implemented using LEAVE

How to display result and or any message in trigger body?

Hi I want to create trigger if it's condition satisfy then it's body should be executed and I want to display some message or any data that should be displayed if trigger body executed.
I want that if quantity of product went less then 50 then it should display message or some data.
Is it possible to display message ?
Here testdata is table name.
Code :
delimiter //
create trigger trigger2 before update on test.testdata
for each row
begin
if new.qty < 50 then
**display here some message that quantity is less**
end if;
end;
//
delimiter ;
You cannot do it, there is no place to output them in MySQL. As a work around you can add your message to the table, and then read this table.
Short example -
CREATE TABLE table1 (
column1 VARCHAR(255) DEFAULT NULL
);
CREATE TABLE messages (
id INT(11) NOT NULL AUTO_INCREMENT,
message VARCHAR(255) DEFAULT NULL,
time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
DELIMITER $$
CREATE TRIGGER trigger1
AFTER INSERT
ON table1
FOR EACH ROW
BEGIN
INSERT INTO messages(message) VALUES ('new action');
END
$$
DELIMITER ;
Also, you could use UDF function write your logic.
More information - CREATE FUNCTION Syntax for User-Defined Functions.
For Quick and plain answering: You cannot Display Messages From Triggers. You may Only Throw errors.
You are propably not knowing the reason u use triggers in databases assuming from your question. We all have passed that level so dont worry. U have understood the syntax when we use triggers but not how and what they can do and not.
A trigger will do (for your case BEFORE an UPDATE) something concerning the database and only that.
That means the trigger cannot display any message on your screen. You can only handle database staff and not all of the actions are allowed for that too or some actions arent even recommended!.
That is for the theory part.
To give you a solution to your problem now.
The only thing you can do to know when the trigger has worked (that means when the new.qua<50) or basically check anything with any other trigger is the following. (Just a small fast solution):
You need to create a Table that will handle all logging of the
triggers.
Add in it an ID field, a descr field that will hold the action of
the triggerex. BefUpdate, BefInsert etc. another field for the
propably the condition that triggered the logging and antyhing else
u want displayed later in the application.
Then inside the if condition u are using write and insert
statemement to fill the info in the new (logging) table.
in your app later select that logging table to see the messages.
That is a useful and fast way to log, not only triggers but also functions (stored procs).
Judt for reference i give you s sample code with the CREATE, and the INSERT statement for your trigger.
CREATE TABLE LOGGER (
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
DESCR_ACTIVITY VARCHAR(10),
ACTIVITY VARCHAR(50),
DT TIMESTAMP,
CONDITIONVALUE VARCHAR(50)
)
In the IF of your code now make it as :
if new.qty < 50 then
INSERT INTO LOGGER VALUES ('BEFINS','CHECKING QUA',NULL,'QUANTITY IS LOWER THAN 50')
end if;
And even from the workbench or from your application u can just :
SELECT * FROM LOGGER
to see the loggings.
But if i am confused from the reading and you want just to throw an error u can read the Mysql Documentation concerning throwing errors:
enter link description here
What u can do is in your if condition write something like:
if new.qty < 50 then
SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = 'Lower than 50', MYSQL_ERRNO = 1000;
endif;
What u should always NOT DO is alter the same table that a trigger is assigned and use only small portion of not so complex code in the trigger.
Hope i helped a bit.
Also, you can display any message using the select command.
IF (NEW.qty < 50) THEN
SELECT "message that quantity is less" AS Output;
END IF
Place above code inside the trigger. It will print the output

Creating an immutable field in mysql

I'd like to make a TIMESTAMP field DEFAULT CURRENT_TIMESTAMP, for 'creation time' purpose. But if someone or somehow something changes that TIMESTAMP, my data won't be consistent.
Is there a way I can ensure it won't change unless I delete the row and reinsert it, other than application level?
With the suggested answer provided, i could work around with something like this
CREATE TRIGGER consistency1 BEFORE UPDATE ON table1
FOR EACH ROW
BEGIN
IF NEW.creationtime != OLD.creationtime THEN
SET NEW.creationtime = OLD.creationtime;
END IF;
END;
Since my comment has been appreciated, here's the extended version.
I personally don't think that it's possible.
Anyway, there are a couple of things you can try:
Make sure that only your application can write on the database
Write a trigger like this (pseudocode!)
create trigger prevent_change_timestamp on tbl_name
before update
#fetch old row value
#verify if the timestamp field has been changed
#raise an error (any SQL error will do)
Or like this
create trigger revert_change_timestamp on tbl_name
after update
#fetch pre-change row value
#update the row with the "old" value in place of the new one
I'd personally go with the 3rd option, if possible. Anyway, the 2nd one is good too. I'd not rely on the 1st option unless necessary (eg: no access to trigger functionality)
More info here: reference
It's funny in a way that database apps don't offer this functionality as standard: not only for a "created" timestamp field, but for things like autoincrement id fields, and any miscellaneous values which you may want to set on creating a record and then never allow to be changed... wonder what the rationale is?
What you can do here is, you can write a TRIGGER on the table when a row is being updated. In that trigger, you can compare the old and new values, and if they are different then you can just overwrite the new value with the old one.
I tried this in MySQL 5.1 and got an error
DELIMITER //
CREATE TRIGGER member_update_0
-> AFTER UPDATE ON members
-> FOR EACH ROW
-> BEGIN
-> IF NEW.id != OLD.id THEN
-> SET NEW.id = OLD.id;
-> END IF;
-> END;//
ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger
The same trigger with AFTER replaced by BEFORE is accepted;
to me, this is a counter-intuitive way to do it, but it works
delimiter ;
UPDATE members SET id=11353 WHERE id=1353;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
It is actually possible to do this very neatly if you are using InnoDB.
Create another table with just one column. That column should have a foreign key (hence the innodb requirement in this solution) that points to the immutable column of the original table in question.
Put a restriction like "ON UPDATE RESTRICT".
In summary:
CREATE TABLE original (
....
immutable_column ...
INDEX index1(immutable_column)
....
) ENGINE=INNODB;
CREATE TABLE restricter (
.....
col1,
INDEX index2(col1),
FOREIGN KEY (col1) REFERENCES original (immutable_colum) ON UPDATE RESTRICT ON DELETE CASCADE
) ENGINE=INNODB;
Taking the idea a step further (for those of us still stuck with a legacy version of MySQL) you can have BOTH a protected & defaulted create_stamp AND an auto-updating update_stamp as follows:
If you have a table such as
CREATE TABLE `csv_status` (
`id` int(11) NOT NULL primary key AUTO_INCREMENT,
`create_stamp` datetime not null,
`update_stamp` timestamp default current_timestamp on update current_timestamp,
`status` enum('happy','sad') not null default 'happy'
);
Then you can define these triggers on it
drop trigger if exists set_create_stamp ;
create definer = CURRENT_USER trigger set_create_stamp BEFORE INSERT on
csv_status for each row
set NEW.create_stamp = now();
drop trigger if exists protect_create_stamp ;
delimiter //
create definer = CURRENT_USER trigger protect_create_stamp BEFORE UPDATE on
csv_status for each row
begin
if NEW.create_stamp != OLD.create_stamp then
set NEW.create_stamp = OLD.create_stamp;
end if;
end;//
delimiter ;