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);
Related
I have 3 Mysql tables and I want to make one of the fields generated from multiplying two fields from two different tables. These are my tables:
ITEMS
id_item | price
1 | 20
2 | 30
3 | 50
DETAIL TRANSACTIONS
id_trans(fk) | id_item | total_items
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
TRANSACTIONS
id_trans | total_price
1 | 100
A total price field inside TRANSACTIONS is what I wanted, and I have tried making a trigger like:
CREATE TRIGGER total_price
AFTER INSERT ON detail_transactions
FOR EACH ROW
UPDATE transactions
SET transactions.`total_price`=
(SELECT SUM(items.'price'*detail_transactions.'total_items')
FROM items
JOIN detail_transactions
ON items.'id_item'= detail_transactions.`id_item`)
WHERE transactions.`id_trans` = NEW.`id_trans`;
But the result is not what I wanted. Any help will be appreciated!
Key words are FOR EACH ROW - ie update 1 row at a time..And do not assume transactions exists test and create if need be
drop trigger if exists t;
delimiter $$
create trigger t after insert on detail_transactions
for each row begin
if not exists (select 1 from transactions t where t.id_trans = new.id_trans) then
insert into transactions
select new.id_trans,new.total_items * price
from items
where items.id_item = new.id_item ;
else
update transactions join items on items.id_item = new.id_item
set total_price = total_price + (new.total_items * price);
end if;
end $$
CREATE TRIGGER tr_ai_update_total_price
AFTER INSERT
ON detail_transactions
FOR EACH ROW
REPLACE INTO transactions (id_trans, total_price)
SELECT NEW.id_trans, SUM(items.price * detail_transactions.total_items)
FROM items
JOIN detail_transactions USING (id_item)
WHERE transactions.id_trans = NEW.id_trans;
This query assumes that transactions (id_trans) is defined as UNIQUE (maybe PRIMARY KEY).
If the row for this id_trans already exists it will be replaced with new values. If it not exists then it will be inserted.
The trigger creation statement contains 1 statement, so neither BEGIN-END nor DELIMITER needed.
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 ;
I'm working with MySQL through phpMyAdmin. I need to reset the ID fields in one of the tables in my database, but I need to do it based on the publication date of each row. I've been looking everywhere and I can't seem to find a solution :(
The following lines of code work fine, but do not do exactly what I require based on the datetime column:
SET #count = 0;
UPDATE `table_name` SET `table_name`.`ID` = #count:= #count + 1;
So this is what I have:
+----+---------------------+
| ID | post_date |
+----+---------------------+
| 1 | 2013-11-04 20:06:28 |
| 2 | 2012-03-30 11:20:22 |
| 3 | 2014-06-26 22:59:51 |
+----+---------------------+
And this is what I need:
+----+---------------------+
| ID | post_date |
+----+---------------------+
| 1 | 2005-08-02 16:51:48 |
| 2 | 2005-08-02 16:59:36 |
| 3 | 2005-08-02 17:01:54 |
+----+---------------------+
Thanks in advance, guys :)
Try this, a simple approach though.
But you will lose all the relations to other tables since you are
resetting the PRIMARY ID Keys.
# Copy entire table to a temporary one based on the publication date
CREATE TEMPORARY TABLE IF NOT EXISTS `#temp_table` AS SELECT * FROM `wp_posts` ORDER BY `post_date`;
# Drop `ID` column from the temporary table
ALTER TABLE `#temp_table` DROP COLUMN `ID`;
# Reset the table `wp_posts` as well as its `ID`
TRUNCATE TABLE `wp_posts`;
# Exclude `ID` column in the INSERT statement below
INSERT INTO `wp_posts`(`post_author`, `post_date`, ..., `comment_count`) SELECT * FROM `#temp_table`;
# Remove the temporary table
DROP TABLE `#temp_table`;
Also see the ERD for WP3.0 below,
Ref: https://codex.wordpress.org/Database_Description/3.3
Try doing it with the following script. It selects every row of your table and orders the rows by its date ascending. Then your Update-Command will be executed within a loop.
Add the type of the ID of your table to the DECLARE-Statement and change the
field-Name in the UPDATE-Statement to your ID-Column name.
BEGIN
DECLARE col_id BIGINT;
DECLARE stepLoopDone BOOLEAN DEFAULT FALSE;
DECLARE counter INT DEFAULT 1;
DECLARE ORDER_CURSOR CURSOR FOR
SELECT id
FROM wp_posts
ORDER BY post_date ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET stepLoopDone = TRUE;
OPEN ORDER_CURSOR;
myLoop: LOOP
FETCH ORDER_CURSOR INTO col_id;
IF stepLoopDone THEN
LEAVE myLoop;
END IF;
/*YOUR UPDATE COMMAND*/
UPDATE wp_posts
SET id = counter
WHERE id = col_id;
/*YOUR UPDATE COMMAND*/
SET counter = counter + 1;
END LOOP;
CLOSE ORDER_CURSOR;
END
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)'))
)
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)