MySQL Non Sequential ID - mysql

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.

Related

Stored Procedure in mySQL issues

For class I am to make a stored procedure named add_parts that will be used to add records to the parts_service table. It has 2 input parameters that match the part_id and parts_qty field/data types from that table. I have to also declare a variable that will hold onto the service_id value from another table services and set it to MAX using a select into. Here is the code I have:
/*Set DB context and drop the procedure if it exists (2 lines)*/
USE cf;
DROP PROCEDURE IF EXISTS add_parts;
/*Delimiter statement (1 line)*/
DELIMITER //
/*Create procedure statement & 2 int parameters.*/
CREATE PROCEDURE add_parts (
IN part_id_param INT,
IN parts_qty_param INT
)
BEGIN
/*Declare the internal int variable (1 line)*/
DECLARE service_id_var INT;
/*Declare sql_error variable, Declare continue handler, set sql_error variable (3 lines)*/
DECLARE sql_error TINYINT DEFAULT FALSE;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
SET sql_error = TRUE;
/*Set the internal int variable */
/*From the appropriate table (3 lines)*/
SELECT MAX(service_id) INTO service_id_var FROM service;
START TRANSACTION;
/*Insert into parts_service values statement */
INSERT INTO parts_service VALUES(DEFAULT, part_id, part_qty, service_id_var);
/* If/else conditional; if sql_error variable is false, commit the transaction and select the appropriate message*/
/* else, rollback the transaction and select the appropriate message */
IF sql_error = FALSE THEN
COMMIT;
SELECT 'Record was added!' AS Message;
ELSE
ROLLBACK;
SELECT 'The part id you entered does not exist' AS Message;
END IF;
END//
CALL add_parts(15,7);
The CALL in the last line should return "Record was added!" but instead returns "The part id you entered does not exist". How do I tell if I am doing this correctly?
CREATE TABLE parts_service (
parts_service_id int(11) NOT NULL AUTO_INCREMENT,
part_id int(11) NOT NULL,
service_id int(11) NOT NULL,
parts_qty int(11) DEFAULT NULL,
PRIMARY KEY (parts_service_id),
KEY ps_fk_parts (part_id),
KEY ps_fk_service (service_id),
CONSTRAINT ps_fk_parts FOREIGN KEY (part_id) REFERENCES parts (part_id),
CONSTRAINT ps_fk_service FOREIGN KEY (service_id) REFERENCES service (service_id)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Aha, these two are out of order:
service_id int(11) NOT NULL,
parts_qty int(11) DEFAULT NULL,
The query (with constraint on the service table) must be failing when trying to insert the parts_qty to the service_id.
Switch your insert to this:
INSERT INTO parts_service VALUES(DEFAULT, part_id, service_id_var, part_qty);
Alternatively, and this is probably better, you could explicitly set the columns your values are to be placed into:
INSERT INTO parts_service
(`parts_service_id`, `part_id`, `parts_qty`, `service_id`)
VALUES
(DEFAULT, part_id, part_qty, service_id_var);

Is there a way to stop incrementing rows at n when the primary key is set to AUTO_INCREMENT?

I want only 6400 number of rows in my newtable. How do I do this?
I have a table that looks like this:
CREATE TABLE `newtable` (
ID_NT int(10) NOT NULL AUTO_INCREMENT
)ENGINE=InnoDB DEFAULT CHARSET=latin1;
You can use a trigger on your table. This works of course only if you don't delete rows
DELIMITER $$
CREATE TRIGGER InsertPreventTrigger BEFORE INSERT ON yourtable
FOR EACH ROW
BEGIN
DECLARE idcount INT;
set idcount = ( select count(*) from id where request = new.request );
IF idcount> 6400
THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'You can not insert record';
END $$
DELIMITER ;
And you have ti change yourtable and id ti fit to your needs
Updates to with count rows, that fits the request better

How to insert multiple records from one table into another on update of third table in MySQL

I'm having difficulty with developing the logic in MySQL. I don't know how to INSERT multiple records from a Table AFTER UPDATE.
CREATE TABLE primeira(
ID int PRIMARY KEY AUTO_INCREMENT,
nome varchar(30) NOT NULL,
valor int DEFAULT 0
);
CREATE TABLE segunda(
ID int PRIMARY KEY AUTO_INCREMENT,
ID_primeira int,
ultimo_valor int DEFAULT 0,
credito int NOT NULL,
limite int DEFAULT 0,
FOREIGN KEY(ID_primeira) references primeira(ID)
);
CREATE TABLE terceira(
ID int PRIMARY KEY AUTO_INCREMENT,
ID_segunda int,
`data` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
estado boolean DEFAULT false,
FOREIGN KEY(ID_segunda) references segunda(ID)
);
CREATE TRIGGER tr_segundaLimite_INS
BEFORE INSERT ON segunda FOR EACH ROW
SET NEW.limite = New.ultimo_valor + New.credito;
DELIMITER //
CREATE TRIGGER tr_primeira_UPD
AFTER UPDATE ON primeira FOR EACH ROW
IF (SELECT limite FROM segunda WHERE segunda.ID_primeira = New.ID AND
(limite - NEW.valor)< 50) THEN
INSERT INTO terceira(ID_segunda)
VALUES ((SELECT ID FROM segunda WHERE segunda.ID_primeira = New.ID
AND (limite - NEW.valor)< 50));
END IF;
END
//
DELIMITER ;
I'm going to use procedures with functions to SELECT the data. The problem with this TRIGGER is that it's not working when there are multiple matching records.
The error that I am getting is-
subquery returns more than 1 row.
The objective is: After an update of the primeira.valor, the trigger would subtract segunda.limite - New.valor. If this difference is < 50 then all the matching segunda.ID would be registered at terceira.ID_segunda on terceira table.
I'm using data below:
INSERT INTO primeira(nome,valor)
VALUES
('Burro',800),
('Chiconizio',300),
('Xerosque',400),
('Shrek',600);
INSERT INTO segunda(ID_primeira,ultimo_valor,credito)
VALUES
(1,600,800),
(1,700,400),
(1,800,500),
(2,150,200),
(2,200,180),
(2,250,300);
UPDATE primeira
SET Valor = 330
WHERE ID = 2;
You need CURSOR for this. You can try the following trigger code. I hope this will fix your issue.
DELIMITER //
CREATE TRIGGER tr_primeira_UPD
AFTER UPDATE ON primeira FOR EACH ROW
BEGIN
DECLARE v_limite_diff INT;
DECLARE v_seg_id INT;
DECLARE done INT DEFAULT FALSE;
DECLARE c1 CURSOR FOR SELECT (s.limite - NEW.valor), s.id
FROM segunda s WHERE s.ID_primeira = New.ID;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN c1;
my_loop: LOOP
FETCH c1 INTO v_limite_diff, v_seg_id;
IF done THEN
LEAVE my_loop;
END IF;
IF( v_limite_diff < 50 ) THEN
INSERT INTO terceira(ID_segunda) VALUES(v_seg_id);
END IF;
END LOOP;
END//
DELIMITER ;

Split string procedure can't find table

I am trying to get this procedure to work and it is stumping me. I simply want this procedure to populate a temp table with the separated values from a table of CSV values.
DELIMITER $$
DROP PROCEDURE IF EXISTS String_Split $$
CREATE PROCEDURE String_Split
(
vString VARCHAR(255),
vSeparator VARCHAR(5)
)
BEGIN
DECLARE vDone tinyint(1) DEFAULT 1;
DECLARE vIndex INT DEFAULT 1;
DECLARE vSubString VARCHAR(15);
DROP TABLE IF EXISTS tmpValues;
CREATE TEMPORARY TABLE tmpValues (tmpVal VARCHAR(255));
WHILE vDone > 0 DO
SET vSubString = SUBSTRING(vString, vIndex,
IF(LOCATE(vSeparator, vString, vIndex) > 0,
LOCATE(vSeparator, vString, vIndex) - vIndex,
LENGTH(vString)
));
IF LENGTH(vSubString) > 0 THEN
SET vIndex = vIndex + LENGTH(vSubString) + 1;
INSERT INTO tmpValues VALUES (vSubString);
ELSE
SET vDone = 0;
END IF;
END WHILE;
END; $$
I call on it:
CALL String_Split(my_csv.keywords, ',');
And I get this:
Error Code: 1109. Unknown table 'my_csv' in field list
I'm not getting this because the table is there and the appropriate database is selected.
CREATE TABLE `my_csv` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`keywords` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 $$
INSERT INTO `my_csv` () VALUES
(1, 'featured, 3/8, Diamond, Engagement Ring, 14K, White Gold, Gold'),
(2, '1/3, Diamond, Engagement Ring, 14K, White Gold, Gold'),
(3, 'featured') $$
Instead of calling the table name and field in the call function, you need to get the value first, then call the procedure. Here's a working example.
SELECT #keyword :=keywords from my_csv;
CALL String_Split(#keyword, ',');
Edit:
That only grabbed the last row. This one selects all of them. Also, doing this the vSeparator variable in the procedure was too small, so I bumped that up to 255.
SELECT #keyword :=group_concat(keywords) FROM my_csv;
CALL String_Split(#keyword, ',');
select * from tmpValues;

SELECT returning no rows using declared variable inside MySQL trigger

I am trying to perform an INSERT...SELECT to create rows in a 'tasks' table once a row is created in a 'workflow' table. (The process_index used in the workflow creation looks up those tasks required in the 'process_tasks' table then creates them in the tasks table).
The problem, however, is that after performing an insert with the process_index of 'process_one' on the workflow table, the SELECT in the trigger finds no rows. I considered that the #process_id was not being set properly, but with the alternative insert i've commented out in the trigger, it demonstrates that the #process_index is being set correctly. Can anyone advise?
Here is some simplified code to demonstrate the problem:
DROP TABLE IF EXISTS workflow;
CREATE TABLE workflow (
id INT(10) PRIMARY KEY AUTO_INCREMENT,
process_index VARCHAR(12)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS tasks;
CREATE TABLE tasks (
id INT(10) PRIMARY KEY AUTO_INCREMENT,
process_index_used VARCHAR(12),
target_field VARCHAR(12)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS process_tasks;
CREATE TABLE process_tasks (
id INT(10) PRIMARY KEY AUTO_INCREMENT,
process_index VARCHAR(12),
source_field VARCHAR(12)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO process_tasks SET process_index = 'process_one', source_field = 'alpha';
INSERT INTO process_tasks SET process_index = 'process_one', source_field = 'beta';
DROP TRIGGER IF EXISTS workflow_tasks;
DELIMITER //
CREATE TRIGGER workflow_tasks AFTER INSERT ON workflow
FOR EACH ROW BEGIN
DECLARE process_index VARCHAR(12);
SET #process_index = NEW.process_index;
-- INSERT INTO tasks (process_index_used) VALUES (#process_index);
INSERT INTO tasks (target_field) SELECT source_field FROM process_tasks WHERE process_index = #process_index;
END//
DELIMITER ;