create a trigger to alter table add column based on inserted row - mysql

I have two tables in a database named as follows:
1. state_master
+---------------+-------------------+
| state_id | state_name |
+---------------+-------------------+
| 1 | new |
| 2 | assigned |
| 3 | in_progress |
| 4 | on_hold |
| 5 | closed |
+---------------+-------------------+
2. store_complaint_state_count
+----------+-----+--------+------------+-------+------+
| store_id | new |assigned| in_progress|on_hold|closed|
| 101 | 1 |2 | 2 |0 |0 |
| 102 | 5 |4 | 1 |0 |2 |
+----------+-----+--------+------------+-------+------+
Now I want to add another row in state_master state_id =6 and state_name=reopen.
I want to create a trigger which can alter table store_complaint_state_count and add column reopen in it.
I've created a procedure:
CREATE DEFINER=`root`#`localhost` PROCEDURE `alterTablestorewisecomplaintcount`(in state int )
BEGIN
alter table storewisecomplaintcount
add column state INT UNSIGNED ZEROFILL NOT NULL DEFAULT 0;
END
And a trigger:
CREATE DEFINER = CURRENT_USER
TRIGGER `nxtlife_sfcms_db_v2`.`complaint_state_AFTER_INSERT`
AFTER INSERT ON `complaint_state`
FOR EACH ROW
BEGIN
call alterTablestorewisecomplaintcount(new.state_value);
END
But it throws an error at the time of insertion:
ERROR 1422: 1422: Explicit or implicit commit is not allowed in stored function or trigger.

you are not allowed to do ALTER or DROP operation on triggers. these are the implicit commits that you see in the error message (see more details here)
Even if it would be possible by some workaround, this is wrong design. If you could do what you wanted- the first insert to the table would add the column, and from then on- all other inserts would have failed since the column already exists

Trigger shouldn't be used that way; instead of every time altering the able and adding a column; you should use a single column say Status which will contain all the different status values like
store_complaint_state_count: Status varchar(20);
Status
new
assigned
in_progress
on_hold
closed
reopen
Then you can modify your trigger code to populate that value to store_complaint_state_count table
CREATE DEFINER = CURRENT_USER
TRIGGER `nxtlife_sfcms_db_v2`.`complaint_state_AFTER_INSERT`
AFTER INSERT ON `complaint_state`
FOR EACH ROW
BEGIN
INSERT INTO store_complaint_state_count (store_id, `Status`) VALUES(105, new.state_name);
END

Hi you can create table and log all events in table.
create table dbLOG (Id Int Identity(1,1),PostTime
VARCHAR(50),ServerName VARCHAR(25),UserName VARCHAR(15),CommandText
VARCHAR(MAX))
go
CREATE TRIGGER [db_LOG]
ON DATABASE
FOR create_table,alter_table,drop_table
,create_PROCEDURE, alter_PROCEDURE,drop_PROCEDURE
,ALTER_function,create_function,drop_function
,ALTER_trigger,create_trigger,drop_trigger
AS
SET NOCOUNT ON
DECLARE #xEvent XML
SET #xEvent = eventdata() --capture eventdata regarding SQL statement user have fired
INSERT INTO dbLOG VALUES(
REPLACE(CONVERT(VARCHAR(50), #xEvent.query('data(/EVENT_INSTANCE/PostTime)')),'T', ' '),
CONVERT(VARCHAR(25), #xEvent.query('data(/EVENT_INSTANCE/ServerName)')),
CONVERT(VARCHAR(15), #xEvent.query('data(/EVENT_INSTANCE/UserName)')),
CONVERT(VARCHAR(MAX), #xEvent.query('data(/EVENT_INSTANCE/TSQLCommand/CommandText)'))
)

Related

How to create a trigger updating 2 tables in MySql

I have 2 tables (in MySQL):
sales(sale_id, customer_id, sale_date, dicount, stock_item_id, seller_id, quantity)
record example:
a0018 | m9795 | 2017-10-2020 | 5 | MarFT | 0 | B-77028
stock(stock_item_id,supplier_name,supplier_email,supplier_phone,item_category item_name,wholesale_price,markup_price,items_in_stock)
record example:
B-77001 |BSN |direct#bsn.com | 1877333665 | Gainers | True Mass | 2.6kg | 33.75 |44.99 | 500
I need to create a trigger that will add a new record into sales table (recording a new sale, that will autoincrement). At the same time I want stock table to update 'items_in_stock' value (that should decrease by whatver quantity was just sold when there is match on stock_item_id)? I hope this makes sense. I'd appreciate any help. Thanks.
Use this:
DELIMITER $$
CREATE
TRIGGER `OnSalesInsert` BEFORE INSERT ON `Sales`
FOR EACH ROW BEGIN
UPDATE Stock
SET items_in_stock = items_in_stock - new.quantity
WHERE stock_item_id = new.stock_item_id;
END;
$$
DELIMITER ;

mySQL trigger that notes when and what data is being accessed from a table in a special access table

I have two table in my MySQL database access & user. I would like to create a trigger that activates every time the user table is accessed and that notes both a time stamp and all columns that are being requested.
So for example if someone requested name from user where ID=20 then the trigger would create a new row in the accessible table that noted the userID, timestamp in unix format, rows column so userID=20, timestamp=1515147950, rowAccessed=name.
How would such a trigger roughly look?
Edit:
User table (InnoDB):
| ID | name | email | age |
+----+--------+-------------+-----+
| 1 | Alice | alice#a.com | 20 |
| 2 | Bo b | bob#b.com | 12 |
| 3 | Carl | carl#c.com | 32 |
Access table (InnoDB):
| ID | userID | timeStamp | column |
+----+--------+-------------+--------+
| 1 | 2 | 1515149281 | name |
| 2 | 1 | 1515148251 | email |
The data in the access table is what I would like the trigger to fill in.
The userID column in the Access table is linked to the ID of the user table through a InnoDB relation
No need to say that the best option for your question is to handle it from code. But if it is necessary to do it from Mysql... this is a approach, probably won't work, I don't have access to a MySQL to test it, but this is where I would start from:
create table user (
ID int primary key,
name text,
email text,
age int
)
create table access (
ID int primary key auto_increment,
userID int,
time timestamp,
columnName text
)
insert into user values (1, 'Alice', 'alice#alice.com', 20), (2, 'Bob', 'bob#bob.com', 25)
create procedure selectUser(colName Boolean, colEmail Boolean, colAge Boolean, id INTEGER)
BEGIN
DECLARE justNow timestamp;
select now() into justNow;
IF colName THEN
insert into access(userID, time, columnName) values (id, justNow, 'name');
END IF;
IF colEmail THEN
insert into access(userID, time, columnName) values (id, justNow, 'email');
END IF;
IF colAge THEN
insert into access(userID, time, columnName) values (id, justNow, 'age');
END IF;
SET #s = CONCAT('SELECT name FROM user');
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
call selectUser(true, true, true, 1)
I haven't finished the part of the column query, but that's easy. Let us know if this approach works for you.

MySQL: Reference current row in delete trigger

I'm having trouble referencing the current row in an AFTER DELETE trigger in MySQL. Pretend I have the following books table:
+----+------+----------+
| id | name | ordering |
+----+------+----------+
| 1 | It | 3 |
| 2 | Cujo | 1 |
| 3 | Rage | 2 |
+----+------+----------+
I want to create a trigger that will decrement all rows whose ordering value is greater than the ordering value in a row that is deleted. For example, if I do DELETE FROM books WHERE id = 2, I want the resulting table to look like:
+----+------+----------+
| id | name | ordering |
+----+------+----------+
| 1 | It | 2 |
| 3 | Rage | 1 |
+----+------+----------+
I've tried:
DROP TRIGGER IF EXISTS reorder_books_on_delete;
DELIMITER $$
CREATE TRIGGER reorder_books_on_delete
AFTER DELETE ON books
FOR EACH ROW
BEGIN
IF ordering > OLD.ordering
THEN
UPDATE books SET ordering = ordering - 1
WHERE id = id;
END IF;
END$$
DELIMITER ;
But this results in an error when I execute a DELETE on the table:
ERROR 1054 (42S22): Unknown column 'ordering' in 'where clause'
This refers to the if statement, so how do I reference the current row in an ON DELETE trigger? The column definitely does exist.
The reason why it fails is because there is no current row and hence, ordering column doesn't exist, it should be used in WHERE clause, like this:
DROP TRIGGER IF EXISTS reorder_books_on_delete;
DELIMITER $$
CREATE TRIGGER reorder_books_on_delete
AFTER DELETE ON books
FOR EACH ROW
BEGIN
UPDATE books SET ordering = ordering - 1
WHERE ordering > OLD.ordering;
END$$
DELIMITER ;
However, as per MySQL's documentation, you can't do this, it will return the following error:
SQL Error (1442): Can't update table 'books' in stored
function/trigger because it is already used by statement which invoked
this stored function/trigger.
So, you will have to run another UPDATE query after DELETE query in a single transaction to achieve this functionality.
As said by Darshan, you can't do this with a Trigger.... But, Procedure can make if for you !
DROP PROCEDURE IF EXISTS delete_book;
DELIMITER //
CREATE PROCEDURE delete_book(IN pId INT)
BEGIN
set #a = (
SELECT ordering
FROM books
WHERE id = pId
);
UPDATE books SET ordering = ordering - 1
WHERE ordering > #a;
delete from books
WHERE id = pId;
END
//
DELIMITER ;
And you just have, instead of your DELETE FROM books WHERE id = 4 to make a CALL delete_book(4);

Mysql for every n-th update to do a special update

Is it possible to do something like this with mysql?
Imagine I've update query, that runs every time user gives successful answer. Now I'd like to count updates and give +1 bonus point every fourth time...
I could just count rows and divide them by 4, but that would give me non spendable bonus points, because for every update it will get recalculated...
Is there any mysql solution to my problem?
I think you may use trigger and calculate additional bonuses when user gives successful answer.
Here is working example:
DROP TABLE IF EXISTS answer;
CREATE TABLE answer
(
id int not null auto_increment,
bonus int not null,
primary key(id)
);
DELIMITER //
CREATE TRIGGER lucky_trigger BEFORE INSERT ON answer
FOR EACH ROW BEGIN
IF MOD((SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'answer'), 4) = 0 THEN
SET NEW.bonus = NEW.bonus + 1;
END IF;
END //
DELIMITER ;
INSERT INTO answer(bonus) VALUES(1);
INSERT INTO answer(bonus) VALUES(1);
INSERT INTO answer(bonus) VALUES(1);
INSERT INTO answer(bonus) VALUES(1);
SELECT id, bonus FROM answer;
Will give you next output:
+----+-------+
| id | bonus |
+----+-------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
+----+-------+
4 rows in set (0.00 sec)

Mysql Trigger: Result consisted of more than one row

I have 3 tables:
Module:
id_module | name
--------------------
1 users
2 roles
...
Profile:
id_profile | name
--------------------
1 admin
2 promoter
And Permission
id | id_profile | id_module | read | write | modify | delete
---------------------------------------------------------------
What I want is a trigger is to fill the table permission every time I insert a new module ... but for every existing profile.
Thus:
INSERT INTO `module` (`name`) VALUES
('user'),
('roles');
Existing profiles admin and promoter permission table would be filled this way:
id | id_profile | id_module | read | write | modify | delete
---------------------------------------------------------------
1 1 1 0 0 0 0
2 1 2 0 0 0 0
3 2 1 0 0 0 0
4 2 2 0 0 0 0
For this, I created the following trigger:
CREATE TRIGGER `create_permission` AFTER INSERT ON `module`
FOR EACH ROW
BEGIN
DECLARE `my_profile` INT(11) ;
SELECT id_profile INTO `my_profile` FROM profile WHERE state <>3;
INSERT INTO `permission` (`id_profile`, `id_module`, `read`, `write`, `modify`, `delete`) VALUES(`my_profile`, NEW.`id_module `,0,0,0,0);
END;
It works well when there is a profile ... when more than one when I return "Result consisted of more than one row"
I read that this problem arises when you declare a variable that supports a single value... You can insert records for each id of the table profile?
As you found out, you may only assign scalar values to a variable.
To iterate over the many results of a SELECT query, you need to use a CURSOR.
I personnaly dislike the cumbersome CURSOR syntax. In your particular case, I would advise the much simpler INSERT INTO...SELECT trick:
INSERT INTO permission (id_profile, id_module, read, write, modify, delete)
SELECT id_profile, NEW.id_module,0,0,0,0 FROM profile WHERE state <> 3;