Substring, Count, and Concat in a Before Insert Trigger? - mysql

I have a dilemma where I need to have a MySQL database create a customer number based on the first 3 characters of a new client name entered and to append an incremenenting value. For example:
Table clients:
+----+----------------------------------------------+--------------+
| id | clientName | clientNumber |
+----+----------------------------------------------+--------------+
| 1 | Accelerated Learning | ACC00 |
| 2 | Acceleration Systems | ACC01 |
| 3 | Acme,Inc. | ACM00 |
Normally I would do this in the submission form on the front end, but for reasons unexplained, the spec wants this handled by the DB. I'm trying to write something like this, but can't get it right:
Trigger:
CREATE DEFINER = CURRENT_USER TRIGGER `crmdev`.`clients_BEFORE_INSERT` BEFORE INSERT ON `clients` FOR EACH ROW
BEGIN
DECLARE prefix varchar(3);
DECLARE suffix INT(3);
SET prefix = SUBSTRING(NEW.clientName,1,3);
SET suffix = (
SELECT COUNT(*),SUBSTRING(clientName,1,3) = prefix + 1;
SET NEW.clientNumber = CONCAT(prefix,suffix);
END
Thanks in advance for help/guidance!

You have syntax errors in the suffix assignment. You're missing the FROM and WHERE clauses, and you need to add 1 to the count, not to prefix.
SET suffix = (
SELECT COUNT(*)+1
FROM clients
WHERE SUBSTRING(clientName,1,3) = prefix);

Here's the finished product if helpful to others & thanks to Barmar:
Trigger:
DECLARE prefix varchar(3);
DECLARE suffix INT(3);
SET prefix = SUBSTRING(NEW.clientName,1,3);
SET suffix = (
SELECT COUNT(*)+1
FROM clients
WHERE SUBSTRING(clientName,1,3) = prefix);
SET NEW.clientNumber = UPPER(CONCAT(prefix,LPAD(suffix,3,0)));

Related

Creating map and use that within the MySQL stored procedure

I am working on a migration project and the data needs to be migrated from system A to system B. The id's in system A and system B will be different. So maintaining a table with source and target column.
As part of the migration, For each record from source A, I need to go to the mapping table to find the corresponding target value. I am just trying avoid querying the mapping table for each query. Is there any way to store the mapping table as a key value map in a stored procedure and query the map as opposed to going to the table directly?
I was searching for collections in MySQL stored procedure, but didn't find anything.
Database A:-
Table : employee
---------------------------------
id | name | email |
---------------------------------
10021 | First1 | first#test.com |
---------------------------------
10035 | First 2| first#test.com |
---------------------------------
Database B:-
Table : employee
---------------------------------
id | name | email |
---------------------------------
1 | First1 | first#test.com |
---------------------------------
2 | First 2| first#test.com |
---------------------------------
Table : emp_mapping
----------------------
source_id | target_id|
----------------------
10021 | 1 |
----------------------
10035 | 2 |
----------------------
Stored Procedure for Migration
DELIMITER //
CREATE PROCEDURE LoadEmployees()
BEGIN
DECLARE db1_empl_id INT;
DECLARE db1_empl_name VARCHAR(20);
DECLARE db1_empl_email VARCHAR(40);
DECLARE db2_empl_id INT;
DECLARE cur CURSOR FOR SELECT id, name, email FROM db1.employees;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_finished = 1;
open cur;
cur_loop: LOOP
FETCH cur INTO db1_empl_id, db1_empl_name, db1_empl_email;
IF cursor_finished = 1 THEN
LEAVE cur_loop;
END IF;
-- cursor loop statements
-- I would like to move this to a map instead of querying for each record
select target_id into db2_empl_id from db2.emp_mapping where source_id = db1_empl_id;
insert into db2.employee(id,name,email) values(db2_empl_id, db1_empl_name, db1_empl_email);
SET cursor_finished = 0; -- hence reset this value for cursor.
end LOOP;
close cur;
END //
DELIMITER ;

How to use a MySQL stored procedure that generates a random 8-char long key to encrypt data again each time a the user logs in

I am working on a stored procedure in MySQL that is used to Login in a user for my polling application. I am trying to implement an algorithm that generates a new random 8-char long string that is being used as a new passphrase to re-encrypt data again.
In my particular case I am re-encrypting given answer-ids (aid) using this passphrase combined with AES. To re-encrypt the data that is already stored encrypted inside my database, the user provides his/her password to decrypt the 8-char long value that has already been stored inside the database. In other words the password is used to decrypt the old encryption-key so this key can be used to decrypt the answer-id of the question the user has voted on. Then the procedure generates a new 8-char long key to re-encrypt these answer-ids again each time the user has successfully logged in.
Unfortunately when I test my procedure, and I check the "vote" table I get NULL as the new encrypted answer id. I have done many attempts to correct this error and probably it has to do something with semantics but I haven't been able to solve it so far and I am puzzling very hard to find out what went wrong. The Login procedure calls another procedure that is responsible for extracting and decrypting already encrypted answer-ids, therefore it is using a while-loop that inserts data into a temporary table. Could you guys please help me out ?
In my opinion it is a slightly advanced procedure, maybe too advanced for my purposes. That's why I provided some comments that will lead you through all the steps being executed. This is my code:
LoginUser procedure
--------------------
-- LoginUser --
-- uname is stored encrypted by passwordhash,
-- pw is stored encrypted by unamehash,
-- key is encrypted by passwordhash,
-- aid is encrypted by key
--------------------
DROP PROCEDURE IF EXISTS LoginUser;
DELIMITER //
CREATE PROCEDURE LoginUser(IN uname CHAR(64), IN pw CHAR(64))
BEGIN
-- declare variables
DECLARE validUser, validLogin, validCombination BOOLEAN default 0;
DECLARE encryptedUsername, oldEncryptedKey, newEncryptedKey, encryptedPassword VARBINARY(32);
DECLARE plainOldKey, plainNewKey CHAR(8);
-- set encrypted username
SET encryptedUsername = (SELECT AES_ENCRYPT(uname,SHA2(pw,256)));
-- check if username exists - returns 1 for true, 0 for false
SET validUser = (SELECT EXISTS(SELECT username FROM user WHERE username = encryptedUsername));
-- logic when valid user
IF (validUser = 1) THEN
-- get pkey from user
SET oldEncryptedKey = (SELECT pkey FROM user WHERE username = encryptedUsername);
-- decrypt pkey using passwordhash
SET plainOldKey = (SELECT AES_DECRYPT(oldEncryptedKey, SHA2(pw,256)));
-- encrypt pw using plainOldKey
SET encryptedPassword = (SELECT AES_ENCRYPT(pw,SHA2(uname,256)));
-- check combination
SET validCombination = (SELECT EXISTS(SELECT * FROM user WHERE username = encryptedUsername AND `password` = encryptedPassword));
-- logic when valid combination
IF(validCombination = 1) THEN
-- set valid login to true
SET validLogin = 1;
-- generate a plainNewKey
SET plainNewKey = (SELECT SUBSTRING(MD5(RAND()) FROM 1 FOR 8));
-- set new encrypted key
SET newEncryptedKey = (SELECT AES_ENCRYPT(plainNewKey,SHA2(pw,256)));
-- call procedure to re-encrypt answer ids.
CALL EncryptAidsAgain(plainOldKey,plainNewKey,encryptedUsername);
-- update pkey from user table by newKey
UPDATE user SET pkey = newEncryptedKey WHERE username = encryptedUsername;
END IF;
END IF;
SELECT validLogin;
END //
DELIMITER ;
EncryptAidsAgain procedure
DROP PROCEDURE IF EXISTS EncryptAidsAgain;
DELIMITER //
CREATE PROCEDURE EncryptAidsAgain(IN oldKeyValue CHAR(8), IN newKeyValue CHAR(8), IN encryptedUsername VARBINARY(32))
BEGIN
DECLARE i, answered INT DEFAULT 0;
DECLARE encryptedAid, newEncryptedAid VARBINARY(32);
DECLARE plainAid CHAR(7);
-- create temporary table to store aids
CREATE TEMPORARY TABLE aids(oldEncryptedAid VARBINARY(32));
-- get amount of given answers
SET answered = (SELECT COUNT(*) FROM vote WHERE username = encryptedUsername);
-- perform while loop
WHILE i < answered DO
-- insert single aid in temporary table aids
INSERT INTO aids (oldEncryptedAid) SELECT aid FROM vote WHERE username = encryptedUsername LIMIT i, 1;
-- retrieve stored aid from aids
SET encryptedAid = (SELECT oldEncryptedAid FROM aids LIMIT i, 1);
-- decrypt stored aid from aids using oldKeyValue
SET plainAid = (SELECT AES_DECRYPT(encryptedAid, SHA2(oldKeyValue,256)));
-- set new encrypted aid
SET newEncryptedAid = (SELECT AES_ENCRYPT(plainAid,SHA2(newKeyValue,256)));
-- update table vote (aid)
UPDATE vote SET aid = newEncryptedAid WHERE username = encryptedUsername AND aid = encryptedAid;
-- increase i
SET i = i + 1;
END WHILE;
DROP TABLE aids;
END //
DELIMITER ;
test results
When call the LoginUser procedure multiple times each time the key updates well:
mysql> select * from user;
+------------------+------------------+------------------+------------------------------------------------------------------+
| username | password | pkey | ip |
+------------------+------------------+------------------+------------------------------------------------------------------+
| EÞ¯k·\â¥çJ¤EÃîP | EÞ¯k·\â¥çJ¤EÃîP | 6░ÛõaǪhpK║ÔäV | 26b92be7c4ad202f842d1755c3db56f25b39c54c51cc56642b8f14ba4bda793d |
+------------------+------------------+------------------+------------------------------------------------------------------+
4 rows in set (0.00 sec)
mysql> call LoginUser("test4","test4");
+------------+
| validLogin |
+------------+
| 1 |
+------------+
1 row in set (0.02 sec)
Query OK, 0 rows affected (0.02 sec)
mysql> select * from user;
+------------------+------------------+------------------+------------------------------------------------------------------+
| username | password | pkey | ip |
+------------------+------------------+------------------+------------------------------------------------------------------+
| EÞ¯k·\â¥çJ¤EÃîP | EÞ¯k·\â¥çJ¤EÃîP | ┴O¥╝Îìöãñ#='¹ | 26b92be7c4ad202f842d1755c3db56f25b39c54c51cc56642b8f14ba4bda793d |
+------------------+------------------+------------------+------------------------------------------------------------------+
4 rows in set (0.00 sec)
But when I login the user multiple times, the new encrypted answer-ids go NULL
mysql> select * from vote;
+-----+------------------+------+------------------+
| vid | username | qid | aid |
+-----+------------------+------+------------------+
| 3 | EÞ¯k·\â¥çJ¤EÃîP | 2 | ðlözaò3OîÙW▓B═Ï |
| 4 | EÞ¯k·\â¥çJ¤EÃîP | 1 | z)ı╝├║%~╝V&7#"─V |
+-----+------------------+------+------------------+
2 rows in set (0.00 sec)
mysql> call LoginUser("test4","test4");
+------------+
| validLogin |
+------------+
| 1 |
+------------+
1 row in set (0.03 sec)
Query OK, 0 rows affected (0.03 sec)
mysql> select * from vote;
+-----+------------------+------+------+
| vid | username | qid | aid |
+-----+------------------+------+------+
| 3 | EÞ¯k·\â¥çJ¤EÃîP | 2 | NULL |
| 4 | EÞ¯k·\â¥çJ¤EÃîP | 1 | NULL |
+-----+------------------+------+------+
2 rows in set (0.00 sec)
Thank you very much in advance, if you have any tips for a better encryption policy they are very welcome!

Case statement in stored procedure - Unknown System Variable

I'm trying to use a case statement to update a column based on the value of another column. My table called 'rma' in MySQL is as follows:
ID | rma_number | serial_number | ref_status_id
1 | 9898 | 123456 | 19
2 | 7869 | 098768 | 3
Here is my stored procedure:
CREATE DEFINER=`admin`#`localhost` PROCEDURE `delete_update_rma`(in selectedID int, in selectedRMAID int)
begin
declare rmaStatus int(5);
select ref_status_id into rmaStatus
from rma
where id = selectedRMAID;
case rmaStatus
when 19 then
set ref_status_id = 0;
end case;
delete from dropbox where id = selectedID;
end
When I try to save to create the procedure I receive the error #1193 - Unknown system variable 'ref_status_id'.
Can anybody help me identify what may be wrong with my stored procedure?
Try this:
CREATE DEFINER=`admin`#`localhost` PROCEDURE `delete_update_rma`(IN selectedID INT, IN selectedRMAID INT)
BEGIN
UPDATE rma
SET ref_status_id =
CASE ref_status_id
WHEN 19 THEN 0
WHEN 3 THEN 2
ELSE ref_status_id
END
WHERE id = selectedRMAID;
DELETE FROM dropbox WHERE id = selectedID;
END
Check the SQL FIDDLE DEMO
OUTPUT
| ID | RMA_NUMBER | SERIAL_NUMBER | REF_STATUS_ID |
|----|------------|---------------|---------------|
| 1 | 12345 | 67898 | 0 |
| 2 | 45645 | 89056 | 2 |
The case syntax is correct, however you cannot change a database field inside a case statement, you need to use an update statement for that.
UPDATE rma
SET ref_status_id = 0
WHERE ref_status_id = 19
AND id = selectedrmaid;
You can only change local vars inside this kind of case statement.
Yours is a typical case of mixing procedural and query logic, a common error when doing stored procedures.
The line ==>
set ref_status_id = 0;
Is causing the error. You are treating set ref_status_id as a variable when its a column in the rma table.
What are you trying to do in that case statement? Since the checking for a hard coded values of 19 doesn't seem to align with what you are trying to do.

how to call a stored procedure using table data?

i want to create a stored procedure by using below requirement.
i tried and written a stored procedure it is working fine with the static values.
how a stored procedure will work with the dynamic values.
Please find my requirement here.
Create a stored proc “skillsparse” that accepts a string of text and and breaks it up into 1,2,3 word phrases.
a. For example: I love java because it’s fun should have these 15 phrases
i. I
ii. I love
iii. I love java
iv. Love
v. Love java
vi. Love java because
vii. Java
viii. Java because
ix. Java because it’s
x. Because
xi. Because it’s
xii. Because it’s fun
xiii. It’s
xiv. It’s fun
xv. fun
3. Store these phrases in a new table called: github_skills_phrases with fields: ID, userid, skills_id (from github_skills_source) and skills_phrase
4. Create a storedproc that compares the skills_phrases against the skills table (ref Table) and store the values into the github_skills table for each user. If possible, please maintain the source of where the skills came from (repodesc, repolang, starred, bio)
5. NOTE: Aside from the info in the new table Kishore is creating, you will also need to run the github_users.bio field against the Skillsparse procedure. You can start this first (for testing logic, etc) since the github_users.bio already exists and has data.
We don’t need to go this for users who have not yet been processed for skills
How i written is:
++++++++++++++++++++++++++++++++++++++++++++++++++++++
DELIMITER $$
CREATE procedure testing(IN id varchar(20),IN usr_id varchar(20),IN str varchar(200))
begin
DECLARE wordCnt varchar(20);
DECLARE wordCnt1 varchar(20);
DECLARE idx INT DEFAULT 1;
DECLARE splt varchar(200);
declare strng varchar(200);
create temporary table tmp.hello1(id varchar(200),usr_id varchar(200),st varchar(200));
set strng = str;
set wordCnt = LENGTH(trim(strng)) - LENGTH(REPLACE(trim(strng), ' ', ''))+1;
set wordCnt1 = LENGTH(trim(strng)) - LENGTH(REPLACE(trim(strng), ' ', ''))+1;
myloop: WHILE idx <= wordCnt DO
set splt = substring_index(trim(strng),' ',idx);
insert into tmp.hello1 values (id,usr_id,splt);
set idx=idx+1;
IF idx = 4 THEN
set strng = substring(trim(strng),length(substring_index(trim(strng),' ',1))+1);
set idx = 1;
set wordCnt = wordCnt -1;
END IF;
end while ;
insert into tmp.hello1 values (id,usr_id,trim(substring(trim(str),length(substring_index(trim(str),' ',wordCnt1-1))+1)));
end $$
Out put ::
mysql> call testing('10','200','I am the my fine the kks hhh nanj kell');
Query OK, 1 row affected (0.77 sec)
mysql> select * from hello1;
+------+--------+---------------+
| id | usr_id | st |
+------+--------+---------------+
| 10 | 200 | I |
| 10 | 200 | I am |
| 10 | 200 | I am the |
| 10 | 200 | am |
| 10 | 200 | am the |
| 10 | 200 | am the my |
| 10 | 200 | the |
| 10 | 200 | the my |
| 10 | 200 | the my fine |
........
..........
| 10 | 200 | kell |
+------+--------+---------------+
27 rows in set (0.00 sec)
my stored procedure is working fine with static values .how to call dynamically a stored procdure by using table data.
Please help me to write a stored procedure to calling with the table data.
If you means you need to call this stored procedure inside select statement on certain data table, this is not available.
You have two options:
1- transfer your procedure to function and then you can call it easily from inside the select statement.
2- write plsql code to call this procedure and you can check the below link about this point
oracle call stored procedure inside select

Mysql:Query for the table as below

I have the following table:
|-------------------|
| id | Homephone |
|-------------------|
| 1 | 454454125 |
| 2 | 47872154587 |
| 3 | 128795423 |
| 4 | 148784474 |
|-------------------|
I have around 40.000 rows in the table.
I want to format Homephone values as following:
454-454-125
478-721-545-87
128-795-423
148-784-474
i.e. after every 3 numbers I want - (hyphen).
How to achieve this using MySQL?
You need to wite a udf for this
Basically, you need to create your own function (so called UDF - User Defined Function) and run it on the table.
There is a nice function by Andrew Hanna posted in String Functions chapter of the MySQL Reference Manual. I fixed a small mistake there (replaced WHILE (i < str_len) DO by WHILE (i <= str_len) DO.
There are two steps (two SQL queries):
Create the function. It has three parameters: str - the string to be modified, pos - position of the character being inserted into the string, delimit - character(s) to be inserted:
DELIMITER //
CREATE FUNCTION insert_characters(str text, pos int, delimit varchar(124))
RETURNS text
DETERMINISTIC
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE str_len INT;
DECLARE out_str text default '';
SET str_len = length(str);
WHILE (i <= str_len) DO
SET out_str = CONCAT(out_str, SUBSTR(str, i, pos), delimit);
SET i = i + pos;
END WHILE;
-- trim delimiter from end of string
SET out_str = TRIM(trailing delimit from out_str);
RETURN(out_str);
END//
DELIMITER ;
Run the function...
...for testing purpose (select, no update):
SELECT insert_characters(Homephone, 3, "-") AS new_phone FROM my_table;
...to update the records:
UPDATE my_table SET Homephone = insert_characters(Homephone, 3, "-");
Please try to analyze the function line by line. This example may help you to understand the subject.