Mysql loop trigger - mysql

I'm trying to do Closure tables with parentId so I have two tables (Subjects and SubjectsTree). When I insert a record in Subjects I need to add records in SubjectsTree for every Subjects.Id = new.ParentId. I'm trying in that way:
delimiter //
CREATE TRIGGER test AFTER INSERT ON Subjects
FOR EACH ROW
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE ids INT;
DECLARE cur CURSOR FOR SELECT Id FROM Subjects inner join SubjectsTree on SubjectsTree.DescendantId = new.ParentId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
ins_loop: LOOP
FETCH cur INTO ids;
IF done THEN
LEAVE ins_loop;
END IF;
INSERT INTO SubjectsTree VALUES (ids, new.Id);
END LOOP;
CLOSE cur;
insert into SubjectsTree values (new.Id, new.Id);
END; //
But it doesn't work just because it repeats for all records in Subjects! And when I try without loop it doesn't.
Why does this code aren't executed for each record in Subjects?
create trigger test after insert on Subjects
for each row
insert into some_test_table values ('test value');
And how can I solve this problem? Or maybe there is a better way?
Subjects table:
Id int PRI
Name longtext
ParentId int
SubjectsTree table:
AncestorId int PRI
DescendantId int PRI

Have solved this problem much easier.
drop trigger if exists test;
create trigger test after insert on Subjects
for each row
insert into SubjectsTree select AncestorId, new.Id, 1 from SubjectsTree
where DescendantId = new.ParentId union all select new.Id, new.Id, 1;

Related

MySQL stored procedure insert multiple rows from list

How can I write a MySQL stored procedure to insert values from a variable sized list? More specifically I need to insert data into one parent table, get the ID from the insert, and then insert a variable number of child records along with the new ID into another table in a one-to-many relationship. My schema looks something like this:
TableA:
table_a_id -- Auto Increment
counter
some_data...
TableB:
table_b_id -- Auto Increment
table_a_id -- Foreign Key Constraint
some_data_from_list...
My stored procedure so far looks like this:
DELIMITER ;;
CREATE PROCEDURE insert_group_alert(
IN _some_data_a VARCHAR(255),
IN _data_list_b TEXT,
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO TableA (
some_data,
counter
)
VALUES (
_some_data_a,
1
)
ON DUPLICATE KEY UPDATE
counter = counter + 1;
SELECT last_insert_id()
INTO #newId;
LIST INSERT ???:
INSERT INTO TableB (
table_a_id, some_data
) VALUES (
#newId,
list_item,
);
END LIST INSERT ???
COMMIT;
END ;;
DELIMITER ;
My thought was to pass in a list of items to insert into table B via a comma delimited string. The values are strings. I am not sure what to do in the LIST INSERT section. Do I need a loop of some sort? Is this stored procedure I have so far the correct way to do this? I don't want to do a batch as I could potentially have hundreds or even thousands of items in the list. Is there a better solution? I am using straight JDBC.
Yes, you need a loop, in which you can use substring_index() to get the values within the list. The solution is based on the answers from this SO topic:
DELIMITER ;;
CREATE PROCEDURE insert_group_alert(
IN _some_data_a VARCHAR(255),
IN _data_list_b TEXT,
)
BEGIN
DECLARE strLen INT DEFAULT 0;
DECLARE SubStrLen INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO TableA (
some_data,
counter
)
VALUES (
_some_data_a,
1
) -- you do not really need this, since you do not provide an id
ON DUPLICATE KEY UPDATE
counter = counter + 1;
SELECT last_insert_id()
INTO #newId;
do_this:
LOOP
SET strLen = CHAR_LENGTH(_data_list_b);
INSERT INTO TableB (table_a_id, some_data) VALUES(#newId,SUBSTRING_INDEX(_data_list_b, ',', 1));
SET SubStrLen = CHAR_LENGTH(SUBSTRING_INDEX(_data_list_b, ',', 1))+2;
SET _data_list_b = MID(_data_list_b, SubStrLen, strLen); --cut the 1st list item out
IF _data_list_b = '' THEN
LEAVE do_this;
END IF;
END LOOP do_this;
COMMIT;
END ;;
DELIMITER ;

MYSQL | SP | CURSOR - Fetch cursor into variable return null

I want to:
Create temporary table.
Insert partial data into temporary table.
Run in Loop on temporary.user_id field.
Update each row after data manipulation and calculation.
The problem on step #3, i get user_id=NULL instead an integer from cursor.
CREATE PROCEDURE user_demo_sp()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE current_user INT;
DECLARE cur CURSOR FOR SELECT user_id FROM users_temp; -- Cursor on temp table
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- Create a table
DROP temporary table if exists `users_temp` ;
CREATE temporary table `users_temp` (
user_id INT(11) NOT NULL,
aggregation_column INT(11) NOT NULL
);
-- Fill table
INSERT INTO users_temp SELECT user_id from users where condition ="condition";
OPEN cur;
read_loop: LOOP
FETCH cur INTO current_user;
IF done THEN
LEAVE read_loop;
END IF;
select current_user; -- Return NULL
END LOOP;
CLOSE cur;
END;
you need to fill your temp table before declaring the cursor on it (just use nested Begin...END blocks):
CREATE PROCEDURE user_demo_sp()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE current_user INT;
temp table
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- Create a table
DROP temporary table if exists `users_temp` ;
CREATE temporary table `users_temp` (
user_id INT(11) NOT NULL,
aggregation_column INT(11) NOT NULL
);
-- Fill table
INSERT INTO users_temp SELECT user_id from users where condition ="condition";
Begin
DECLARE cur CURSOR FOR SELECT user_id FROM users_temp; -- Cursor on
OPEN cur;
read_loop: LOOP
FETCH cur INTO current_user;
IF done THEN
LEAVE read_loop;
END IF;
select current_user; -- Return NULL
END LOOP;
CLOSE cur;
End;
END;
Also in scenarios like this actually you don't need a temp table you
can declare the cursor on the select which you used to fill temptale

INSERT INTO if conditions are met

A user is only meant to have up to 3 keys registered to his account at any one time. To add a new key, the user must first delete another key to "make room" for a new one.
I want this to be checked server-side, but I can't get the query to work. Here is what I tried:
IF (SELECT COUNT(serial_key_nbr)
FROM keys_table WHERE user_id = 9) <= 2
THEN INSERT INTO keys_table (user_id, serial_key_nbr)
VALUES (9, 'abc123')
How to do this?
You can use the below mention Script for the same:
INSERT INTO keys_table (user_id, serial_key_nbr)
SELECT 9, 'abc123' FROM DUAL
WHERE
(SELECT COUNT(serial_key_nbr)
FROM keys_table WHERE user_id = 9)<=2
if you want to use an if to do a conditional select then I would put it in a variable like so.
BEGIN
DECLARE var1 INT;
SELECT COUNT(serial_key_nbr) INTO var1
FROM keys_table
WHERE user_id = 9;
IF var1 <= 2
THEN
INSERT INTO keys_table (user_id, serial_key_nbr)
VALUES (9, 'abc123')
END IF;
A trigger might be the way to go. If a condition is met, a trigger before inserting in the table can perform an invalid operation and cause the insert operation to fail:
delimiter $$
create trigger keep_three before insert on keys_table for each row
begin
if (select count(serial_key_nbr) from keys_table where user_id = new.user_id) >= 3 then
insert into non_existent_table (non_existent_field) values (new.user_id);
end if;
end$$
delimiter ;
Ugly, but it might work.
Reference:
"MySQL Triggers: How do you abort an INSERT, UPDATE or DELETE with a trigger?"
Another solution (better I think) is to forcibly delete an entry before attepting the insert. When there are less than 3 entries, the insert procedes normally:
delimiter $$
create trigger keep_three before insert on keys_table for each row
begin
while (select count(serial_key_nbr) from keys_table where user_id = new.user_id) >= 3 do
delete from keys_table where user_id = new.user_id
-- OPTIONAL: Add an ordering criteria to define which entry is deleted first
limit 1;
end while;
end$$
delimiter ;
I think this is cleaner.
A third way (I've found it here). It will return an error message (by signaling sqlstate 45000: Unhandled user defined exception) associated with the defined condition:
delimiter $$
create trigger keep_three before insert on keys table for each row
begin
declare msg varchar(255);
declare n int default 0;
set n = (select count(serial_key_nbr) from keys_table where user_id = new.user_id);
if n >= 3 then
set msg = "INSERT failed: There must be only three entries for each user. Delete an entry first";
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
end if;
end$$
delimiter ;
A cleaner version of my first option.

MySql stored procedure no data fetched

SQLFiddle
I need to loop throught my cursor and insert new values in another table.
I have two tables:
CREATE TABLE peoples
(
id int auto_increment primary key,
name varchar(20),
enabled varchar(1)
)
/
INSERT INTO peoples
(name, enabled)
VALUES
('john', 'F'),
('jane', 'T')
/
CREATE TABLE test
(
id int,
logins int
)
/
I need to select all peoples enabled = T and insert a new entry in my second table 'test'. I created a stored procedure:
CREATE PROCEDURE sp_a()
BEGIN
DECLARE p int;
DECLARE done INT DEFAULT FALSE;
DECLARE peoples_cur CURSOR FOR select p.id from peoples p where p.enabled='T';
OPEN peoples_cur;
REPEAT
FETCH peoples_cur INTO p;
IF NOT done THEN
INSERT INTO test
(id, logins)
VALUES
(p, '999');
END IF;
UNTIL done END REPEAT;
CLOSE peoples_cur;
END;
/
But i got the error:
No data - zero rows fetched, selected, or processed: CALL sp_a()
SQLFiddle
Simple way:
DROP PROCEDURE IF EXISTS sp_a;
DELIMITER $$
CREATE PROCEDURE `sp_a`()
BEGIN
INSERT INTO test (logins)
SELECT COUNT(id) AS l FROM peoples WHERE enabled = 'T';
END$$
CALL sp_a();

MySQL Trigger Insert After with Select query from different table

new to DBA thanks for bearing with me.
Overview:
I have Groups, Subgroups and Users.
User can be owner of Group so should be Owner of all its subgroups
User can be collaborator or follower of group so should be collaborator or follower of all its subgroups
User can be collaborator or follower of just subgroup
Tables are as follow (simplified):
Group(topic_id,title)
Subgroup (subtopic_id,title,topic_id)
rel_Group (user_id,topic_id,type)
//To Determine relationship of user to Group (Owner,Collaborator or Follower)
rel_Subgroup (user_id,subtopic_id,type)
//To Determine relationship of user to Subgroup (Owner,Collaborator or Follower)
User (user_id)
I want to create a trigger when a subgroup is created that will INSERT / UPDATE / DELETE rows in rel_Subgroup so users who are Owner, Collaborator or follower on group with respectively be Owner, Collaborator or follower on subgroup
This is the closest i got but am still getting:
#1415 - Not allowed to return a result set from a trigger.
SQL Query
delimiter //
create trigger Transfer_Rights_to_Subgroup
after insert
on Subgroup
for each row
begin
select user_id,type from rel_Group where rel_Group.topic_id = NEW.topic_id;
insert into rel_Subgroup VALUES (rel_Group.user_id,NEW.subtopic_id,rel_Group.type);
END; //
delimiter ;
I am hoping to sort the insert and then will figure out the update/delete.
Any help, much appreciated!
thx
Managed to solve it:
DROP TRIGGER IF EXISTS Transfer_Rights_to_Subgroup;
DELIMITER //
CREATE TRIGGER Transfer_Rights_to_Subgroup AFTER INSERT ON subgroup
FOR EACH ROW
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE c1 INT;
DECLARE c2 INT;
DECLARE cur CURSOR FOR SELECT User_ID,Type FROM rel_group WHERE rel_group.Topic_ID = NEW.Topic_ID;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
ins_loop: LOOP
FETCH cur INTO c1,c2;
IF done THEN
LEAVE ins_loop;
END IF;
INSERT INTO rel_Subgroup VALUES (c1,NEW.Subtopic_ID,c2);
END LOOP;
CLOSE cur;
END; //
DELIMITER ;
Try this one:
delimiter //
create trigger Transfer_Rights_to_Subgroup
after insert
on Subgroup
for each row
begin
select user_id,type into #userid, #type from group where rel_Group.topic_id = NEW.topic_id;
insert into rel_Subgroup VALUES (#userid,NEW.subtopic_id,#type);
END; //
delimiter ;