mysql trigger does not insert selected columns - mysql

CREATE DEFINER=`ir55`#`%` TRIGGER UpdateAverageBookRating
AFTER INSERT
ON Reviews FOR EACH ROW
BEGIN
DECLARE AverageRating double;
DECLARE lastbookid int;
SELECT BookID FROM Reviews WHERE ReviewID = LAST_INSERT_ID() INTO lastbookid;
SELECT AVG(Rating) FROM Reviews WHERE BookID = lastbookid INTO AverageRating;
UPDATE CentralCatelogue SET Rating = AverageRating WHERE BookID = lastbookid;
END
The above trigger does not update the Rating column in CentralCatelogue because the value of Rating and BookID are empty.
Not sure why, I tried to update the Rating with static values and it works, I believe there is something wrong with selecting the columns in 'Reviews' and assigning them into variables.
Appreciate your help.
Thanks

If your schema looks a bit like this
DROP TABLE REVIEWS,CENTRALCATALOGUE;
CREATE TABLE `reviews` (
`bookid` int(11) DEFAULT NULL,
`rating` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `centralcatalogue` (
`bookid` int(11) DEFAULT NULL,
`rating` double(10,2) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
DROP TRIGGER IF EXISTS T;
DELIMITER $$
CREATE TRIGGER `t` AFTER INSERT ON `reviews` FOR EACH ROW BEGIN
DECLARE AverageRating double(10,2);
if not exists (select 1 from centralcatalogue where bookid = new.bookid) then
insert into debug_table(msg) values (concat('inserting:',new.bookid,':',new.rating));
insert into centralcatalogue values(new.bookid,new.rating);
else
SELECT AVG(Rating) FROM Reviews WHERE BookID = new.bookid INTO AverageRating;
insert into debug_table(msg) values (concat('updating',new.bookid,':',averagerating));
UPDATE CentralCatalogue SET Rating = AverageRating WHERE BookID = new.bookid;
end if;
END $$
delimiter ;
Note 1) the test and insert to see if bookid exists in centralcatalogue 2) The debug_table to assist debugging 3) changed averagerating to double(10,2) to avoid rounding up
the insert
insert into reviews values(1,1),(1,2),(2,3);
produces
+--------+--------+
| bookid | rating |
+--------+--------+
| 1 | 1.50 |
| 2 | 3.00 |
+--------+--------+
2 rows in set (0.02 sec)
BTW I don't really like this trigger since it tests and calculates for each row. It might be better if you encapsulated the insert and update in a transaction.

Related

mysql trigger: after insert, +1 depending on what is entered?

CREATE TABLE `inventory` (
`id` int(11) NOT NULL,
`owner` int(11) DEFAULT NULL,
`grade1` int(11) DEFAULT NULL,
`grade2` int(11) NOT NULL,
`grade3` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `inventory`
--
INSERT INTO `inventory` (`id`, `owner`, `grade1`, `grade2`, `grade3`) VALUES
(3, 1, 2, 1, 1);
-- --------------------------------------------------------
--
-- Table structure for table `transfer`
--
CREATE TABLE `transfer` (
`id` int(11) NOT NULL,
`owner` int(11) DEFAULT NULL,
`total` char(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `transfer`
--
INSERT INTO `transfer` (`id`, `owner`, `total`) VALUES
(20, 1, 1);
--
-- Triggers `transfer`
--
DELIMITER $$
CREATE TRIGGER `t` AFTER INSERT ON `transfer` FOR EACH ROW update inventory t1
set t1.grade1 = t1.grade1 + 1
WHERE t1.owner = new.owner
AND `total` = '/1'
$$
DELIMITER ;
I have two tables as you can see from above code. I am in the process of using triggers in MySQL.
What I am trying to do, is that when someone enters something into transfer, and the owner matches the owner which is in the inventory- if what they have typed '(a number)/1' into total in transfer, it would add 1 to grade1. If they typed in '(a number)/2' into total, it will add 1 to grade2. And same for grade3. As you can see from the trigger above, this is what I have tried. I have tried it without the AND `total` = '/1' so I know the issue must be within that part. I have also tried without the ` around total, however it doesn't recognise this column without it.
I've had a look through SO and cannot find anything to resolve this.
I need this section done through a trigger- if anyone has any idea, can they please let me know. Thanks
The usual way to update different columns conditionally is to update all of them, but use a condition to determine whether to give them a new value or keep the old value. This can be used in a trigger just like any other UPDATE query.
CREATE TRIGGER `t` AFTER INSERT ON `transfer` FOR EACH ROW update inventory t1
set t1.grade1 = IF(new.total LIKE '%/1', t1.grade1 + 1, t1.grade1),
t1.grade2 = IF(new.total LIKE '%/2', t1.grade2 + 1, t1.grade2),
t1.grade3 = IF(new.total LIKE '%/3', t1.grade3 + 1, t1.grade3)
WHERE t1.owner = new.owner
It seems like what you really need is a fourth column grade in the transfer table so your trigger code can know which grade to increment. All you have in the trigger is OLD.* and NEW.*, the columns of the row that changed. You can't make one of your current integer columns carry extra information that is more than a simple integer.
ALTER TABLE transfer ADD COLUMN grade TINYINT UNSIGNED;
Then you can use this in the trigger to tell which grade to increment.
CREATE TRIGGER `t` AFTER INSERT ON `transfer` FOR EACH ROW
UPDATE inventory
SET grade1 = grade1 + CASE NEW.grade WHEN 1 THEN 1 ELSE 0 END,
grade2 = grade2 + CASE NEW.grade WHEN 2 THEN 1 ELSE 0 END,
grade3 = grade3 + CASE NEW.grade WHEN 3 THEN 1 ELSE 0 END;
WHERE owner = NEW.owner

MySQL Non Sequential ID

Challenge:
Create a method to set "auto_increment" values for tables in a non-sequential way.
The goal is to override the "auto_increment" mechanism and allow the function "LAST_INSERT_ID()" to continue working as expected (returning an INT), so that no changes are needed in software side.
My Solution
The method I found is based on an auxiliary table (unique_id), that stores values available to be assigned. Values are then selected randomly, and removed from the tables as used. When the table gets empty, a new set of ID's is created.
This example is working as expected, but with one problem.
Tables for the demo:
CREATE TABLE `unique_id` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=100;
CREATE TABLE `test_unique_id` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
Defined a stored procedure and a function:
DELIMITER $$
DROP PROCEDURE IF EXISTS `UNIQUE_ID_REFILL`$$
CREATE PROCEDURE UNIQUE_ID_REFILL()
BEGIN
DECLARE a INT Default 0 ;
simple_loop: LOOP
SET a=a+1;
INSERT INTO unique_id (id) values(null);
IF a=100 THEN
LEAVE simple_loop;
END IF;
END LOOP simple_loop;
END $$
DROP FUNCTION IF EXISTS `UNIQUE_ID_GET`$$
CREATE FUNCTION UNIQUE_ID_GET()
RETURNS INT(11)
MODIFIES SQL DATA
BEGIN
DECLARE new_id INT(11);
DECLARE unique_id_count INT(11);
SET new_id = 0;
SELECT COUNT(*) INTO unique_id_count FROM unique_id;
IF unique_id_count=0 THEN
CALL UNIQUE_ID_REFILL();
END IF;
SELECT id INTO new_id FROM unique_id ORDER BY RAND() LIMIT 1;
DELETE FROM unique_id WHERE id = new_id;
RETURN new_id;
END $$
Created a Trigger on the destination table (test_unique_id):
CREATE TRIGGER test_unique_id__unique_id BEFORE INSERT ON test_unique_id
FOR EACH ROW
SET NEW.id = UNIQUE_ID_GET();
The solution is getting the random ID's as expected:
INSERT INTO test_unique_id(name) VALUES ('A'),('B'),('C');
Creates the rows:
id name
154 'A'
129 'B'
173 'C'
The Problem
The main problem is that LAST_INSERT_ID() stops working... and the software side is broken:
SELECT LAST_INSERT_ID();
0
Any ideas on how to solve this problem? or any other different approach to the challenge?
Thank you very much.

MySQL - While in SELECT clause

Is it possible to call a while statement inside a SELECT clause in MySQL ?
Here is a example of what I want to do :
CREATE TABLE `item` (
`id` int,
`parentId` int,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `FK_parentId` (`parentId`),
CONSTRAINT `FK_parentId` FOREIGN KEY (`parentId`) REFERENCES `item` (`id`)
);
I would like to select the root of each item, i.e. the higher ancestor (the item that has no parentId). In my mind, I would do something like this :
select
`id` as 'ID',
while `parentId` is not null do `id` = `parentId` end while as 'Root ID'
from
`item`
Of course this can't work. What is the better way to achieve something like that ?
EDIT
Here a sample data :
id | parentId
1 | NULL
2 | 1
3 | 2
4 | 2
5 | 3
6 | NULL
7 | 6
8 | 7
9 | 7
And expected result :
ID | RootId
1 | NULL
2 | 1
3 | 1
4 | 1
5 | 1
6 | NULL
7 | 6
8 | 6
9 | 6
Thank you.
just use CASE
select
`id` as 'ID',
CASE `parentId` WHEN is not null THEN `parentId` END as 'Root ID'
from
`item`
Here is the procedure:
BEGIN
-- declare variables
DECLARE cursor_ID INT;
DECLARE cursor_PARENTID INT;
DECLARE done BOOLEAN DEFAULT FALSE;
-- declare cursor
DECLARE cursor_item CURSOR FOR SELECT id, parentId FROM item;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- create a temporary table
create TEMPORARY table IF NOT EXISTS temp_table as (select id, parentId from item);
truncate table temp_table;
OPEN cursor_item;
item_loop: LOOP
-- fetch row through cursor
FETCH cursor_item INTO cursor_ID, cursor_PARENTID;
IF done THEN
-- end loop if cursor is empty
LEAVE item_loop;
END IF;
-- insert into
insert into temp_table
select MAX(t.id) id, MIN(#pv := t.parentId) parentId
from (select * from item order by id desc) t
join (select #pv := cursor_ID) tmp
where t.id = #pv;
END LOOP;
-- close cursor
CLOSE cursor_item;
-- get the results
SELECT id id, parentid RootId from temp_table order by id ASC;
END
I created a temporary table and kept the results into it while running cursor. I couldn't think of a solution with just one query. I had to go for a cursor.
I took help from the following links:
How to do the Recursive SELECT query in MySQL?
How to create a MySQL hierarchical recursive query

Two primary keys & auto increment

I have a MySQL table with two fields as primary key (ID & Account), ID has AUTO_INCREMENT.
This results in the following MySQL table:
ID | Account
------------------
1 | 1
2 | 1
3 | 2
4 | 3
However, I expected the following result (restart AUTO_INCREMENT for each Account):
ID | Account
------------------
1 | 1
2 | 1
1 | 2
1 | 3
What is wrong in my configuration? How can I fix this?
Thanks!
Functionality you're describing is possible only with MyISAM engine. You need to specify the CREATE TABLE statement like this:
CREATE TABLE your_table (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
account_id INT UNSIGNED NOT NULL,
PRIMARY KEY(account_id, id)
) ENGINE = MyISAM;
If you use an innoDB engine, you can use a trigger like this:
CREATE TRIGGER `your_table_before_ins_trig` BEFORE INSERT ON `your_table`
FOR EACH ROW
begin
declare next_id int unsigned default 1;
-- get the next ID for your Account Number
select max(ID) + 1 into next_id from your_table where Account = new.Account;
-- if there is no Account number yet, set the ID to 1 by default
IF next_id IS NULL THEN SET next_id = 1; END IF;
set new.ID= next_id;
end#
Note ! your delimiter column is # in the sql statement above !
This solution works for a table like yours if you create it without any auto_increment functionality like this:
CREATE TABLE IF NOT EXISTS `your_table` (
`ID` int(11) NOT NULL,
`Account` int(11) NOT NULL,
PRIMARY KEY (`ID`,`Account`)
);
Now you can insert your values like this:
INSERT INTO your_table (`Account`) VALUES (1);
INSERT INTO your_table (`Account`, `ID`) VALUES (1, 5);
INSERT INTO your_table (`Account`) VALUES (2);
INSERT INTO your_table (`Account`, `ID`) VALUES (3, 10205);
It will result in this:
ID | Account
------------------
1 | 1
2 | 1
1 | 2
1 | 3

MSSQL Try Catch and Set Identity Insert in MySQL?

Do you know how to rewrite these this query in MySQL ?
I can't find Identity insert, I can't find any try catch,
I don't understand it.
CREATE TRIGGER T1 ON DB1.dbo.A
AFTER INSERT AS
BEGIN TRY
SET IDENTITY_INSERT DB2.dbo.B ON
INSERT INTO dbo.B(id, text) SELECT A.id,A.text FROM dbo.A INNER JOIN inserted I ON I.id = A.id
SET IDENTITY_INSERT DB2.dbo.B OFF
SET IDENTITY_INSERT DB2.dbo.D ON
INSERT INTO dbo.D(id, text) SELECT A.id,A.text FROM dbo.A INNER JOIN inserted I ON I.id = A.id
SET IDENTITY_INSERT DB2.dbo.D OFF
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SET IDENTITY_INSERT DB2.dbo.B OFF
SET IDENTITY_INSERT DB2.dbo.D OFF
END CATCH
GO
MySQL triggers have implicit transaction support, so the trigger cannot use statements that explicitly or implicitly begin or end a transaction such as START TRANSACTION, COMMIT, or ROLLBACK.
It is not necessary in MySQL to enable the insertion of values into primary key columns - this is already allowed. You can, however, toggle foreign key constraint checking and unique index checking:
http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html
http://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_unique_checks
A common way to do this is to store the existing values in user variables, change the settings, then restore the settings after your script is complete:
SET #OLD_UNIQUE_CHECKS=##UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
-- Your SQL statements here.
SET FOREIGN_KEY_CHECKS=#OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=#OLD_UNIQUE_CHECKS;
I'm not sure why you would need to do that in your trigger, so your MySQL trigger would look something like this:
DELIMITER |
CREATE TRIGGER T1 AFTER INSERT ON A FOR EACH ROW
BEGIN
INSERT INTO B (id, text) VALUES (NEW.id, NEW.text);
INSERT INTO C (id, text) VALUES (NEW.id, NEW.text);
END;|
DELIMITER ;
Here's the results of a quick test:
CREATE TABLE `A` (
`id` int(11) NOT NULL auto_increment,
`text` varchar(255) default NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `B` (
`id` int(11) NOT NULL auto_increment,
`text` varchar(255) default NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `C` (
`id` int(11) NOT NULL auto_increment,
`text` varchar(255) default NULL,
PRIMARY KEY (`id`)
);
DELIMITER |
CREATE TRIGGER T1 AFTER INSERT ON A FOR EACH ROW
BEGIN
INSERT INTO B (id, text) VALUES (NEW.id, NEW.text);
INSERT INTO C (id, text) VALUES (NEW.id, NEW.text);
END;|
DELIMITER ;
INSERT INTO `A` (id, text) VALUES (1, 'Line 1');
INSERT INTO `A` (id, text) VALUES (2, 'Line 3');
INSERT INTO `A` (id, text) VALUES (3, 'Line 3');
SELECT * FROM `A`;
+----+--------+
| id | text |
+----+--------+
| 1 | Line 1 |
| 2 | Line 3 |
| 3 | Line 3 |
+----+--------+
SELECT * FROM `B`;
+----+--------+
| id | text |
+----+--------+
| 1 | Line 1 |
| 2 | Line 3 |
| 3 | Line 3 |
+----+--------+
SELECT * FROM `C`;
+----+--------+
| id | text |
+----+--------+
| 1 | Line 1 |
| 2 | Line 3 |
| 3 | Line 3 |
+----+--------+
If you want something similar to TRY ... CATCH, you'll need to use handlers instead:
http://dev.mysql.com/doc/refman/5.1/en/declare-handler.html
Here's the documentation on MySQL triggers:
http://dev.mysql.com/doc/refman/5.1/en/commit.html
Just set id column to AUTO_INCREMENT. You don't need to toggle something on and off.
Read more in documentation