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 ;
Related
I'm trying to assign multiple values in a variable and execute a query using it. For example below:
SET #ledger = "'Cash','Special Offer'";
SELECT `_ledger` FROM `acc_ledger` WHERE `_ledger` IN(#ledger);
But this doesn't work as planned. Is there a way to define multiple values in a variable? If yes, how? If no, can I have a suggestion on how to tackle this issue?
You can pass multiple values with comma separated and then split those variables int table and perform a join
create function to split comma separated parameter
DELIMITER $$
DROP FUNCTION IF EXISTS `SPLIT_STR` $$
CREATE FUNCTION SPLIT_STR(id_list VARCHAR(500), delimeter VARCHAR(10), position INT)
RETURNS VARCHAR(10)
DETERMINISTIC
BEGIN
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(id_list, delimeter, position),
LENGTH(SUBSTRING_INDEX(id_list, delimeter, position - 1)) + 1),
delimeter, '');
END$$
DELIMITER ;
call SPLIT_STR function from query
SET #ledger = "Cash,Special Offer";
CREATE TEMPORARY TABLE IF NOT EXISTS `selected_types` (type varchar(50));
#inserting splitted values to temp table
simple_loop: LOOP
SET indx=indx+1;
SET str=SPLIT_STR(x_id_list,',',indx);
IF str='' THEN
LEAVE simple_loop;
END IF;
INSERT INTO selected_types VALUES(str);
END LOOP simple_loop;
#filter with temp table
SELECT `_ledger` FROM
`acc_ledger` led
inner join selected_types tmp on tmp.type = led._ledger;
CREATE TABLE t1 (s1 INT, PRIMARY KEY (s1));
DELIMITER ;
CREATE PROCEDURE handlerdemo ()
BEGIN
DECLARE x INTEGER;
SET #x = 1;
INSERT INTO t1 VALUES (1);
SET #x = 2;
INSERT INTO t1 VALUES (1);
SET #x = 3;
END;
WHEN I run this query I get 1064 error at line 4.
Any hint on how to deal with it is highly appreciated.
You need to set Delimiter to anything except ;
Optionally, put a check if the same name stored procedure already exists.
At the end, redefine the Delimiter back to ;
Unless, you are going to use variable x outside this stored procedure; you really dont need to use #; it makes the variable available everywhere in that particular session).
Try (more explanation in the comments):
CREATE TABLE t1 (s1 INT, PRIMARY KEY (s1)); -- create the table
DELIMITER $$ -- redefine the delimiter to $$ (for eg)
DROP PROCEDURE IF EXISTS `handlerdemo` $$ -- drop previous if exists
CREATE PROCEDURE handlerdemo ()
BEGIN
DECLARE x INT DEFAULT 0; -- datatype is INT
-- also a good practice to set default value
SET x = 1; -- no need to use in Session context
INSERT INTO t1 VALUES (x); -- use variable name here instead of literal value
SET x = 2;
INSERT INTO t1 VALUES (x);
END $$ -- remember that delimiter is $$ right now
-- redefine the Delimiter back to ;
DELIMITER ;
You just need to remove ; (semicolon) after DELIMITER it will work fine
CREATE TABLE t1 (s1 INT, PRIMARY KEY (s1));
DELIMITER -- just remove this ; from your query it will work fine
CREATE PROCEDURE handlerdemo ()
BEGIN
DECLARE x INTEGER;
SET #x = 1;
INSERT INTO t1 VALUES (1);
SET #x = 2;
INSERT INTO t1 VALUES (1);
SET #x = 3;
END;
I want to grab a variable (between 1-365) and use this value to create the number of empty rows in a table:
insert into tblCustomer (ID) values (), (), ();
is there an easier way to do this or is using a loop the best way?
Any help would be appreciated.
A procedure with an IN parameter is quite easy
DELIMITER $$
DROP PROCEDURE IF EXISTS test_loop$$
CREATE PROCEDURE test_loop(IN number INT)
BEGIN
DECLARE x INT(11);
SET x = 1;
WHILE x <= number DO
INSERT INTO tblCustomer(id) VALUES('');
SET x = x + 1;
END WHILE;
END$$
DELIMITER ;
How to use it
CALL test_loop(20);
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.
I can enforce data restriction at the time of data insertion in a table using a trigger like following:
create table com(id int);
DELIMITER ;;
CREATE TRIGGER checkage_bi BEFORE INSERT ON com FOR EACH ROW
BEGIN
DECLARE dummy,baddata INT;
SET baddata = 0;
IF NEW.id > 20 THEN
SET baddata = 1;
END IF;
IF NEW.id < 1 THEN
SET baddata = 1;
END IF;
IF baddata = 1 THEN
SELECT CONCAT('Cannot Insert This Because id ',NEW.id,' is Invalid')
INTO dummy FROM information_schema.tables;
END IF;
END;;
this will restrict the values which will not meet the restriction condition.
But the problem is that if I insert multiple values along then for some values which do not meet the condition the other values will also be restricted from entry to table.
What I want is that when I insert multiple values along then the values that do not meet the condition should be skipped and the rest get inserted into the table
i.e. no any error is given.
Only the the bad values will be skipped and the other values will be inserted that's it.
Please give me some idea to do so.Thanks in advance...
You should better use stored Procedures rather than triggers for this purpose because you are not permitted within a stored function or trigger to modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger itself.
DELIMITER ;;
create table com(id int,var char(40));;
create procedure Validate(IN id int,IN var char(40))
BEGIN
declare entry_id int;
SET entry_id=id;
IF (id > 20) OR (id < 1) THEN
SET entry_id=NULL;
END IF;
insert into com values(entry_id,var);
END;;
Here is the output screen