Mysql stored procedure creation gets 2014 error - php

Trying to create my first stored procedure, I have researched extensively and could not find a way to input the code via phpMyAdmin so downloaded MYSQL Workbench 6.0 and put my script in that way- but it throws a 2014 error (Commands out of sync, you cannot run this command now). However I notice that Workbench seems to create two lines - the first says running and the second the error.
The routine is to update three tables and insert into another when a contract is issued. Contracts are made up of bundles and they in turn consist of items which relate to specific problems in another table.
Is it my code or should I include some clearing command at the end and if so what should that be? The procedure input is a contract id, user id and their IP address (for logging).
In PHP when, eventually, using a CALL how would I clear results, as I keep seeing Stackoverflow 2014 answers where second calls fail? Also how can I get this onto an ubuntu server either via phpMyAdmin or if with putty where should I ftp the sql script to?
DELIMITER //
DROP PROCEDURE IF EXISTS ContrctAwardStatusLog;
CREATE PROCEDURE ContrctAwardStatusLog( IN c_Id INT(8), IN u_Id INT(11), IN u_Ip varchar(20) )
BEGIN
Block1: BEGIN
DECLARE done INT DEFAULT 0;
DECLARE b_id INT DEFAULT 0;
DECLARE citm_id INT DEFAULT 0;
DECLARE r_id INT DEFAULT 0;
DECLARE c_1 CURSOR FOR SELECT bundle_id FROM bundles WHERE contract_id = c_Id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN c_1;
REPEAT
FETCH c_1 INTO b_id ;
Block2: BEGIN
DECLARE done2 INT DEFAULT 0;
DECLARE c_2 CURSOR FOR SELECT contitem_id, issue_id FROM c_items WHERE bundle_id= b_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = 1;
OPEN c_2;
REPEAT
FETCH c_2 INTO citm_id r_id;
INSERT INTO track_status (rowid, WStatusBy, WStatus, WStatusWhen) VALUES(r_id, u_Id, 13,NOW());
UPDATE h_issues SET WStatus='13' WHERE RowID = r_id;
UPDATE c_items SET act_state='13' WHERE contitem_id=citm_id;
UNTIL done2
END REPEAT;
CLOSE c_2;
END Block2;
UPDATE bundles SET bundle_stat = '13' WHERE bundle_id = b_id;
UNTIL done
END REPEAT;
CLOSE c_1;
END Block1;
END //
DELIMITER ;

I finally sorted this myself.
I needed to put the DROP ..if exists BEFORE the DELIMITER Setting and there was a syntax error in the FETCH after OPEN c_2. It was missing a comma separator.
DROP PROCEDURE IF EXISTS ContrctAwardStatusLog;
DELIMITER //
CREATE PROCEDURE ContrctAwardStatusLog( IN c_Id INT(8), IN u_Id INT(11), IN u_Ip varchar(20) )
BEGIN
Block1: BEGIN
DECLARE done INT DEFAULT 0;
DECLARE b_id INT DEFAULT 0;
DECLARE citm_id INT DEFAULT 0;
DECLARE r_id INT DEFAULT 0;
DECLARE c_1 CURSOR FOR SELECT bundle_id FROM bundles WHERE contract_id = c_Id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN c_1;
REPEAT
FETCH c_1 INTO b_id ;
Block2: BEGIN
DECLARE done2 INT DEFAULT 0;
DECLARE c_2 CURSOR FOR SELECT contitem_id, issue_id FROM c_items WHERE bundle_id= b_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = 1;
OPEN c_2;
REPEAT
FETCH c_2 INTO citm_id, r_id;
INSERT INTO track_status (rowid, WStatusBy, WStatus, WStatusWhen) VALUES(r_id, u_Id, 13,NOW());
UPDATE h_issues SET WStatus='13' WHERE RowID = r_id;
UPDATE c_items SET act_state='13' WHERE contitem_id=citm_id;
UNTIL done2
END REPEAT;
CLOSE c_2;
END Block2;
UPDATE bundles SET bundle_stat = '13' WHERE bundle_id = b_id;
UNTIL done
END REPEAT;
CLOSE c_1;
END Block1;
END //
DELIMITER ;

Related

How to remove all non-numeric characters from a MySQL table entry

I'm trying to write a PHP program to update a MySQL table entry according to a phone number. The phone numbers in the database are entered without limitations and are typically formatted in the XXX-XXX-XXXX way, but sometimes have other characters due to typos. In order to ensure the query works every time, I want to remove all non-numeric characters from the entries so that I can compare the entries to phone numbers formatted like XXXXXXXXXX coming from a separate source.
I've done some research and found some solutions but am unsure how to incorporate them into the PHP script. I am fairly new to MySQL and most of the solutions provided user defined MySQL functions and I don't know how to put them into the PHP script and use them with the query I already have.
Here's one of the solutions I found:
CREATE FUNCTION [dbo].[CleanPhoneNumber] (#Temp VARCHAR(1000))
RETURNS VARCHAR(1000) AS BEGIN
DECLARE #KeepValues AS VARCHAR(50)
SET #KeepValues = '%[^0-9]%'
WHILE PATINDEX(#KeepValues, #Temp) > 0
SET #Temp = STUFF(#Temp, PATINDEX(#KeepValues, #Temp), 1, '')
RETURN #Temp
END
And this is the query I need the solution for:
$sql = "SELECT pid AS pid FROM patient_data " .
"WHERE pid = '$pID' AND phone_cell = '$phone_number';";
The query should return the data in the pid column for a single patient, so if the phone number is 1234567890 and the pid is 15, 15 should be returned. I have no output at the moment.
The example function definition is Transact-SQL (i.e. for Microsoft SQL Server), it's not valid MySQL syntax.
A function like this doesn't go "into" the PHP code. The function gets created on the MySQL database as a separate step, similar to creating a table. The PHP code can call (reference) the defined function just like it references builtin functions such as DATE_FORMAT or SUBSTR.
The SELECT statement follows the pattern of SQL that is vulnerable to SQL Injection. Any potentially unsafe values that are incorporated into SQL text must be properly escaped. A better pattern is to use prepared statements with bind placeholders.
As an example of a MySQL function:
DELIMITER $$
CREATE FUNCTION clean_phone_number(as_phone_string VARCHAR(1024))
RETURNS VARCHAR(1024)
DETERMINISTIC
BEGIN
DECLARE c CHAR(1) DEFAULT NULL;
DECLARE i INT DEFAULT 0;
DECLARE n INT DEFAULT 0;
DECLARE ls_digits VARCHAR(20) DEFAULT '0,1,2,3,4,5,6,7,8,9';
DECLARE ls_retval VARCHAR(1024) DEFAULT '';
IF ( as_phone_string IS NULL OR as_phone_string = '' ) THEN
RETURN as_phone_string;
END IF;
SET n := CHAR_LENGTH(as_phone_string);
WHILE ( i < n ) DO
SET i := i + 1;
SET c := SUBSTR(as_phone_string,i,1);
IF ( FIND_IN_SET(c,ls_digits) ) THEN
SET ls_retval := CONCAT(ls_retval,c);
END IF;
END WHILE;
RETURN ls_retval;
END$$
DELIMITER ;
We can execute these statements in the mysql command line client, connected as a user with sufficient privilege, to create the function.
This isn't necessarily the best way to write the function, but it does serve as a demonstration.
Once the function is created, we can reference it a SQL statement, for example:
SELECT t.foo
, clean_phone_number(t.foo)
FROM ( SELECT '1' AS foo
UNION ALL SELECT '1-888-TAXICAB'
UNION ALL SELECT '888-555-1212'
UNION ALL SELECT '+=_-()*&^%$##"''<>?/;:"abc...xyz'
UNION ALL SELECT ''
UNION ALL SELECT NULL
) t

Error Code: 1172. Result consisted of more than one row [MySQL]

it is my first time to create a stored procedure in mysql.
What it does is,
first- get count of all records
second- loop through that table 1 by 1
third- compare each entry if it is a duplicate
fourth- insert duplicate in a temporary table
last- display duplicates
It is working properly on 100-200 entries BUT on bigger records up to 500+ (sometimes 25k) it throws a message
Error Code: 1172. Result consisted of more than one row
I have googled this issue but none of them (answers) help me to solve my problem.
Please take a look on my script
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE i_sku VARCHAR(255);
DECLARE i_concatenated_attributes MEDIUMTEXT;
DECLARE f_sku VARCHAR(255);
DECLARE f_offer_type VARCHAR(255);
DECLARE f_name VARCHAR(255);
DECLARE f_product_owner VARCHAR(255);
DECLARE f_listing_city VARCHAR(255);
DECLARE f_listing_area VARCHAR(255);
DECLARE f_price DOUBLE;
DECLARE f_bedrooms INT;
DECLARE f_building_size INT;
DECLARE f_land_size INT;
DECLARE f_concatenated_attributes MEDIUMTEXT;
DECLARE f_duplicate_percentage INT;
SELECT COUNT(*) FROM unit_temp_listing INTO n;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_temp (dup_sku VARCHAR(255), dup_percentage INT, attribs MEDIUMTEXT);
SET i=0;
WHILE i<n DO
-- Get all unit listings (one by one)
SELECT
sku, concat_ws(',',offer_type,name,product_owner,listing_city,listing_area,price,ifnull(bedrooms,0),ifnull(building_size,0),ifnull(land_size,0)) as concatenated_attributes
INTO i_sku, i_concatenated_attributes
FROM unit_temp_listing
limit 1 offset i;
-- Compare one by one (sadla)
SELECT
f.sku, f.offer_type, f.name, f.product_owner, f.listing_city, f.listing_area, f.price, f.bedrooms, f.building_size, f.land_size,
levenshtein_ratio(concat_ws(',',f.offer_type,f.name,f.product_owner,f.listing_city,f.listing_area,f.price,ifnull(f.bedrooms,0),ifnull(f.building_size,0),ifnull(f.land_size,0)),i_concatenated_attributes) as f_duplicate_percentage,
concat_ws(',',f.offer_type,f.name,f.product_owner,f.listing_city,f.listing_area,f.price,ifnull(f.bedrooms,0),ifnull(f.building_size,0),ifnull(f.land_size,0)) as fconcatenated_attributes
INTO f_sku, f_offer_type, f_name, f_product_owner, f_listing_city, f_listing_area, f_price, f_bedrooms, f_building_size, f_land_size, f_duplicate_percentage, f_concatenated_attributes
FROM unit_temp_listing f
WHERE substring(soundex(concat_ws(',',offer_type,name,product_owner,listing_city,listing_area,price,ifnull(bedrooms,0),ifnull(building_size,0),ifnull(land_size,0))),1,10) = substring(soundex(i_concatenated_attributes),1,10)
AND levenshtein_ratio(concat_ws(',',offer_type,name,product_owner,listing_city,listing_area,price,ifnull(bedrooms,0),ifnull(building_size,0),ifnull(land_size,0)),i_concatenated_attributes) > 90
AND f.sku != i_sku;
-- INSERT duplicates
IF(f_sku IS NOT NULL) THEN
INSERT INTO temp_temp (dup_sku, dup_percentage, attribs) VALUES (f_sku, f_duplicate_percentage, f_concatenated_attributes);
SET f_sku = null;
SET f_duplicate_percentage = null;
SET f_concatenated_attributes = null;
END IF;
SET i = i + 1;
END WHILE;
SELECT * FROM temp_temp;
DROP TABLE temp_temp;
End
What is the problem?
Hello to my fellow Developers out there I already solved my issue and I want to share it here.
The issue was, my SECOND SELECT statement inside the WHILE loop was returning multiple rows. I tried using CURSOR but I still got the error message. So I tried to put that second SELECT statement of mine inside the INSERT statement like this
INSERT INTO table_name (columns, ...) SELECT_STATEMENT
and then, problem was solved! But if anyone here have an idea to optimize my query please do help me. I have to process 20k+ records but because of time of execution took too long, I only settle for 500 for 15-20 mins.
Thank you for the 18 views (at the time of writing).
Happy coding!

MySQL Stored Procedure that compares each row's date field and updates based on curdate()

This might be a noob question, but I tried googling about this issue and I haven't found any solution for it (I think). Anyway, I need to compare a date in a row from a mysql table to the current date and update the row depending on the results. I made a stored procedure that will be called by an event every x minutes. Here is my code for the sp:
delimiter //
CREATE PROCEDURE checkPromoValidity()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE id INT;
DECLARE lvn_status INT;
DECLARE lvn_validUntil DATE;
DECLARE cur1 CURSOR FOR SELECT promos.PROMO_NUMBER,promos.VALID_UNTIL,promos.status FROM MDCH_NEW.promos;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open cur1;
check_loop: LOOP
fetch cur1 into id,lvn_validUntil,lvn_status;
IF done THEN
LEAVE check_loop;
END IF;
IF (lvn_validUntil > CURDATE()) THEN
update mdch_new.promos set MDCH_NEW.PROMOS.status = 0 where mdch_new.promos.PROMO_NUMBER = id;
END IF;
end LOOP;
close cur1;
END //
MDCH_NEW = database name
PROMOS= table name
EDIT: I CHANGED my delimiter to // just to see
edit2: added space after the final word, still getting the error below.. :(
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '//' at line 23
delimiter $$
create procedure return7 ()
BEGIN
select 7;
END
$$
call return7();
you are free to re-use this code

Recursive count of employees under a manager using mysql

I need the list of all employees, following the hierarchy of the manager using MYSQL from the following table. In oracle or mssql it is easy job, but could not find any solution in MySQL. Can anyone help me out how to sort it out.
id name manager
1 John 6
2 Gill 7
3 Ben 2
4 Roy 8
5 Lenin 6
6 Nancy 7
7 Sam 0
8 Dolly 3
If you still can limit the maximal number of levels, here is a solution with a recursive procedure. Since recursive functions are not allowed in MySQL, we have here a function (manager_count), which wraps the results from the recursive procedure. Recursion depth is controlled by the max_sp_recursion_depth variable, which takes 255 as its maximum. Use as follows: SELECT *,manager_count(id) FROM my_table. It's not the optimal solution, since it doesn't take into account already counted branches of the hierarchy (a temporary table can actually serve as a cache).
DELIMITER //
DROP FUNCTION IF EXISTS manager_count//
CREATE FUNCTION manager_count(_manager INT) RETURNS INT
BEGIN
DECLARE _count INT DEFAULT 0;
SET max_sp_recursion_depth = 255;
# manager_count_helper does the job
CALL manager_count_helper(_manager, _count);
# subtract 1, because manager_count_helper will count this manager as well
RETURN _count - 1;
END//
DROP PROCEDURE IF EXISTS manager_count_helper//
CREATE PROCEDURE manager_count_helper(IN _manager INT, INOUT _count INT)
BEGIN
IF EXISTS (SELECT 1 FROM my_table WHERE id = _manager) THEN
BEGIN
DECLARE _next_manager INT DEFAULT 0;
DECLARE done BOOLEAN DEFAULT FALSE;
# cursor to loop through the employees
DECLARE _cursor CURSOR FOR SELECT id FROM my_table WHERE manager = _manager;
# if done, the done variable gets TRUE and it's time too leave
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
# count 1, because this guy should be counted as well
SET _count = _count + 1;
OPEN _cursor;
read_loop: LOOP
FETCH _cursor INTO _next_manager;
IF done THEN LEAVE read_loop;
END IF;
CALL manager_count_helper(_next_manager, _count);
END LOOP;
CLOSE _cursor;
END;
END IF;
END

Complicated ORDER BY needed to sort irregular values

I am trying to develop a system to display products from a database on a webpage. Normally this is no problem, except that one manufacturer has several abnormal part numbers I need to sort by properly.
Normally, I could just use an invisible column to sort things out, but then that makes inserting new items in-between two older ones much more difficult.
For example, here are some of the part numbers:
1211
1225
14-302
14-303
2015
23157
3507
35280UP
42-3309
42-3312
4241
Now, the normal order by mfgr produces the above order.
What it SHOULD be is something more like this:
14-302
14-303
42-3309
42-3312
1211
1225
2015
3507
4241
23157
35280UP
What is going to be my best bet on sorting this properly? If they were just being made in a csv file and uploaded afterwards this wouldn't be a problem. But because of automatic database changes, the server will be modifying values in real time. So manually updating it is out of the question. This means that a back end will be required to insert new items, but by what method could I insert an item between another? I would prefer to not resort to something like decimals to give me X in between values (Like I have 1.00 and 2.00 and I want to put another between them so I make it 1.50).
Any help would be appreciated.
EDIT:
The way I would like to sort it is this:
if it has a hyphen, ie 14-302, it is sorted by the 14, and then any 14-xxx is sorted by the numbers after the hyphen.
then, just numbers would be sorted by their actual numner, 802 comes before 45768.
Then any number that has a letter(s) after it will be sorted by the number, the the letter so 123a comes before 123b but after 122. And 123b comes before 124c.
and lastly anything that begins with an M- will be sorted last, and by the numbers after the hyphen.
With your adjusted question the principle is still the same.
My first instinct was to go with extra field but you could use stored function for those.
You still need to divide your partnumber in 3 parts (I take it it's not more than that).
Part1 is an INTEGER and has the first number if existing else fill it with maxint (largest for part1).
Part2 is a CHAR and contains letters if existing.
Part3 is an INTEGER containing the second number if existing.
You can sort by calling a function for these values.
Here is the complete source:
For your convenience a link to a working SQL Fiddle.
It's really sloppy/fast programming but it works. Put together in little time and i'm sure there are points for improvement. You can tweak it yourself. Later you can delete the part1, part2 and part3 from the view. (but leave it in the order by) It's only to show how the sort is done.
DROP PROCEDURE IF EXISTS `uGetParts`//
DROP FUNCTION IF EXISTS `uExtractPart1`//
DROP FUNCTION IF EXISTS `uExtractPart2`//
DROP FUNCTION IF EXISTS `uExtractPart3`//
CREATE PROCEDURE `uGetParts`(
IN ins varchar(50),
OUT num1 int unsigned,
OUT num2 int unsigned,
OUT num3 int unsigned)
NO SQL
BEGIN
SET num1=0;
SET num2=0;
SET num3=0;
WHILE (num1<length(ins)) AND
(SUBSTRING(ins,num1+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num1=num1+1;
END WHILE;
SET num2=num1;
WHILE (num2<length(ins)) AND
(SUBSTRING(ins,num2+1,1) REGEXP('(^[0-9]+$)')=0) DO
SET num2=num2+1;
END WHILE;
SET num3=num2;
WHILE (num3<length(ins)) AND
(SUBSTRING(ins,num3+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num3=num3+1;
END WHILE;
END//
CREATE FUNCTION `uExtractPart1`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num1>0 THEN
RETURN CAST(SUBSTRING(ins,1,num1) AS UNSIGNED);
ELSE
RETURN ~0 >> 32;
END IF;
END//
CREATE FUNCTION `uExtractPart2`(ins varchar(50))
RETURNS varchar(50) NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num2>num1 THEN
RETURN SUBSTRING(ins,num1+1,num2-num1);
ELSE
RETURN '';
END IF;
END//
CREATE FUNCTION `uExtractPart3`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num3>num2 THEN
RETURN CAST(SUBSTRING(ins,num2+1,num3-num2) AS UNSIGNED);
ELSE
RETURN 0;
END IF;
END//
You can call it like this:
SELECT
id,
TYPE,
uExtractPart1(TYPE) as part1,
uExtractPart2(TYPE) as part2,
uExtractPart3(TYPE) as part3
FROM Article
ORDER BY
uExtractPart1(TYPE),
uExtractPart2(TYPE),
uExtractPart3(TYPE)

Categories