SQL syntax error using prepared statements and variables - mysql

I have three stored procedures for three different ERPs making almost exactly the same operations, so I decided to make just one, and use parameters. Some of the field names change depending on the parameters, so I set them in variables. Then I am using prepared statements to make it work.
A working update that does not use prepared statements is as follows:
update lt_erp_barcodes
set barcode_list = concat(',', curr_barcode, barcode_list)
where cod_navision = curr_erp_code;
It updates the current barcode_list adding curr_barcode at the beggining of the list, and adding also a comma. (,curr_barcode,the rest of the list,)
My approach to make it work with prepared statement is:
set #erp_code_field_name = "cod_navision";
set #curr_erp_code = '12345';
set #curr_barcode = '123123123';
set #q1 = concat('update lt_erp_barcodes
set barcode_list = (\',\'', #curr_barcode, ' barcode_list)
where ', #erp_code_field_name, ' = ', #curr_erp_code);
prepare update_barcode_list from #q1;
execute update_barcode_list;
But when I call to prepare the following error is raised:
0 228 10:30:17 prepare update_barcode_list from #q1 Error Code: 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 '123123123 barcode_list
where cod_navision = 12345' at line 2 0.047 sec
How could I make the second block of code work the same as the first one?
Thanks

Your code is generating non legal SQL in the #Q1 variable:
DELIMITER $
DROP PROCEDURE IF EXISTS procTest$
CREATE PROCEDURE procTest()
BEGIN
set #erp_code_field_name := "cod_navision";
set #curr_erp_code := '12345';
set #curr_barcode := '123123123';
set #q1 := concat('update lt_erp_barcodes
set barcode_list = (\',\'', #curr_barcode, ' barcode_list)
where ', #erp_code_field_name, ' = ', #curr_erp_code);
SELECT #q1;
prepare update_barcode_list from #q1;
execute update_barcode_list;
END$
DELIMITER ;
CALL procTest();
See the result here:
update lt_erp_barcodes
set barcode_list = (','123123123 barcode_list)
where cod_navision = 12345
What is the actual result you are trying to achieve? Let me know if you still can't solve this,
James

You may need to take off the ' barcode_list' from your #q1
set #q1 = concat('update lt_erp_barcodes
set barcode_list = (\',\'', #curr_barcode, ') where ',
#erp_code_field_name, ' = ', #curr_erp_code);
Anyway, you should change it as #Phylogenesis told you in its comment.

Thank you for all your answers. I finally achieved it as follows:
set #q1 = concat('update lt_main_barcodes_v2
set barcode_list = concat(\',', #curr_barcode, '\', barcode_list)
where ', #erp_code_field_name, ' = ', #curr_erp_code);
I corrected syntax errors (thanks James) and added another concat(). Now, MySQL understands barcode_list is a field. Selecting #q1 gives brings the following output:
update lt_erp_barcodes
set barcode_list = concat(',123123123', barcode_list)
where cod_navision = 12345

Related

MySQL GROUP_CONCAT 'Invalid use of Group function' error that DOESN'T involve an aggregate function

I have a MySQL stored procedure (shown below) that's supposed to build a list of IDs from a table of hierarchically related records. I'm having to re-engineer an older stored procedure to switch from using a simple CONCAT function to GROUP_CONCAT because the former couldn't handle the sizes of the lists being generated (i.e., the lists are going well over the 1024 character limit of the CONCAT function).
DELIMITER $$
CREATE PROCEDURE SPTest (top_id INT)
BEGIN
DECLARE ids_all TEXT;
SET SESSION group_concat_max_len = 1000000;
SET ids_all = top_id;
SET #str = GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR \', \') ',
'FROM tbl WHERE nha_id = ', top_id, ' INTO #ids_tmp' SEPARATOR '');
PREPARE stmt FROM #str;
EXECUTE stmt;
WHILE #ids_tmp != "" DO
SET ids_all = GROUP_CONCAT(ids_all, #ids_tmp SEPARATOR ', ');
SET #str = GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR \', \') ',
'FROM tbl WHERE nha_id IN (', #ids_tmp, ') INTO #ids_tmp' SEPARATOR '');
PREPARE stmt FROM #str;
EXECUTE stmt;
END WHILE;
SELECT ids_all AS ids;
END $$
DELIMITER ;
The problem is this routine is generating the following error when I try to call it (and sometimes it returns this error when I try to create the Stored Procedure):
ERROR 1111 (HY000): Invalid use of group function
When I manually create the same kinds of queries this code would build and run them at the command-line, they work perfectly fine -- no errors of any kind, and I get the results I expect. I've seen posts (both here on Stack Exchange and elsewhere) which say that MySQL doesn't support nested aggregate functions, but the concatenation here is just being done to strings. So I thought that maybe somehow it was seeing the GROUP_CONCAT in the string and burping because of that, so I tried putting "XXXX" in place of "GROUP_CONCAT" in the string and then using the REPLACE function to switch it back, but that made no difference. I also tried it with WHERE and HAVING for the criteria clause, but neither one worked. I've also done an extensive web search and was unable to find anything that was in any way helpful.
I don't know what else to try, mainly because I can't see what's wrong with the syntax here. Any help would be appreciated.
Update 1:
I have since tried a modified version of the script where the GROUP_CONCAT merges data from a subquery like this:
SET #qrystr = GROUP_CONCAT('SELECT GROUP_CONCAT(c SEPARATOR ", ") ',
'FROM (SELECT id AS c FROM tbl WHERE nha_id IN (', #ids_tmp,
') AS d INTO #ids_tmp' SEPARATOR '');
but that made no difference either.
You can't use GROUP_CONCAT() as a scalar function; it must be used in the context of a set of rows. Similarly, you can't use any other aggregate function without a table reference:
SET #x = MAX(<expr>); -- makes no sense
Ideally you should upgrade to MySQL 8.0 if you haven't already, and use a recursive CTE query instead:
WITH RECURSIVE hierarchy AS (
SELECT top_id AS id
UNION
SELECT tbl.id FROM tbl JOIN hierarchy ON tbl.nha_id = hierarchy.id
)
SELECT GROUP_CONCAT(id SEPARATOR ', ') AS ids_all FROM hierarchy;
That would eliminate the need for using loops or prepare/execute or temp variables.
Thanks to Bill Karwin's answer regarding the need to use SELECT with GROUP_CONCAT, I was able to see how my code needed to be changed. Rather than using a SET statement to assign the values to the variables, I need to use a SELECT ... INTO ... construct, as shown here:
DELIMITER $$
CREATE PROCEDURE ASSEMBLY_LIST_TEST (top_id INT)
BEGIN
DECLARE ids_all TEXT DEFAULT '';
SET SESSION group_concat_max_len = 1000000;
SET ids_all = top_id;
# Find the 1st-level children of 'top_id' to start building the list
SELECT GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR ", ") FROM tbl ',
'WHERE nha_id = ', top_id, ' INTO #ids_tmp' SEPARATOR '') INTO #qrystr;
PREPARE stmt FROM #qrystr;
EXECUTE stmt;
# Recursively find the children of each level of children of the previous loop & add to the list
WHILE #ids_tmp != '' DO
SELECT GROUP_CONCAT(ids_all, ', ', #ids_tmp SEPARATOR '') INTO ids_all;
SELECT GROUP_CONCAT('SELECT GROUP_CONCAT(id SEPARATOR ", ") FROM tbl ',
'WHERE nha_id IN (', #ids_tmp, ') INTO #ids_tmp' SEPARATOR '') INTO #qrystr;
PREPARE stmt FROM #qrystr;
EXECUTE stmt;
END WHILE;
SELECT ids_all AS ids;
END $$
DELIMITER ;
This now produces exactly the result I was looking for. Of course, the recursive query approach is a much better solution for what I want this to do, but maybe my solution will help someone else.

Every time I try to execute the code below, it displays the error "Incorrect syntax near 'B'."

I am working on a project and I need to declare the query below. But every time I try to execute it, it displays the error "Incorrect syntax near 'B'."
DECLARE #strLineCode VARCHAR (20)
SET #strLineCode = 'CDP'
DECLARE #strSQL VARCHAR(5000)
SET #strSQL = 'SELECT B.strCurrentLineCode,
strLineMachineNo, strSAPAssetNum, B.strResourceSubType
FROM tblMachMaster A
INNER JOIN vwResourceMaster B
ON A.strResourceNumber = B.strResourceNumber'
SET #strSQL = #strSQL + 'WHERE B.strCurrentLineCode = ''' + #strLineCode + ''' '
EXEC(#strSQL)
I tried executing it without declaring the query and it works just fine. Send help :(
SET #strLineCode = 'CDP';
PREPARE stmt FROM 'SELECT B.strCurrentLineCode,
strLineMachineNo, strSAPAssetNum, B.strResourceSubType
FROM tblMachMaster A
INNER JOIN vwResourceMaster B
ON A.strResourceNumber = B.strResourceNumber WHERE B.strCurrentLineCode = ?' ;
EXECUTE stmt
USING #strLineCode;
You can try above code.
Here I had used PREPARE and EXECUTE Statement.
Using which you can simply execute your dynamic query.
One more thing you missed ';' after every line of code. In MySQL statement is always end with ';' only.

Creating a MySQL stored procedure to update records

I'm converting all of my existing MSSQL databases and stored procedures am stuck on a new stored procedure where I need to update an existing record. The procedure gets called from a web form once a record has been inserted into the database and en email sent successfully (or at least passed off to the SMTP server)
I've had a working procedure in MSSQL for a long time but am trying to convert it to MySQL. I'm passing in 3 variables - a bit indicating the email got sent, a string indicating which SMTP server has been used to sent the email and a unique record id so I'll know what record to update. I'm also adding the date and time to another field to know when the procedure ran.
I've got the following but keep getting an error "#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 7 - yet I don't see anything off at line 7 - at least to my eye.
The code I'm trying to use is:
CREATE PROCEDURE `sp_Test`(
`emailSent_In` BIGINT UNSIGNED,
`emailTransport_In` VARCHAR(100),
`formSecret_In` VARCHAR(32)
)
BEGIN
SET #`query` := CONCAT('UPDATE ',`tbl_JustSayThanks`,'
SET `emailSent` = `emailSent_In`,
`emailTransport` = ',`emailTransport_In`,',
`emailSentDate` = NOW()
WHERE `formSecret` = ', `formSecret_In`, '');
PREPARE `stmt` FROM #`query`;
EXECUTE `stmt`;
#`query` := NULL;
DEALLOCATE PREPARE `stmt`;
END//
DELIMITER ;
Just FYI, I'm using the CONCAT based on a previous answer I received from wchiquito and will be passing in the table name eventually. But, I wanted to get it to work on a simplified level before going there.
The following is wrong:
SET #`query` := CONCAT('UPDATE ',`tbl_JustSayThanks`,'
because you seem to be concatenating your SQL text with the value of tbl_JustSayThanks, but I think you mean to use the identifier itself. This should therefore be:
SET #`query` := CONCAT('UPDATE `tbl_JustSayThanks`',
The following is wrong:
`emailTransport` = ',`emailTransport_In`,',
because the variable is a VARCHAR but you don't quote it as a string literal in your SQL statement. It's easy to get mixed up with the multiple levels of quoting. It should be:
`emailTransport` = ''', `emailTransport_In`, ''',
The following is wrong for the same reason:
WHERE `formSecret` = ', `formSecret_In`, '');
it should be:
WHERE `formSecret` = ''', `formSecret_In`, '''');
This still suffers from SQL injection problems, unless you can guarantee that the input parameters are safe (which is not a good assumption). If you need to concatenate values into your SQL expressions, you should use the QUOTE() function to do escaping:
SET #query = CONCAT('
UPDATE tbl_JustSayThanks
SET emailSent = ', QUOTE(emailSent_In), '
emailTransport = ', QUOTE(emailTransport_In), '
emailSentDate = NOW()
WHERE formSecret = ', QUOTE(formSecret_In));
More comments:
You don't need to delimit every identifier with back-ticks, only those that conflict with SQL reserved words, or contain whitespace or punctuation or international characters. None of your identifiers you show require delimiting.
When you use prepared statements, you should use query parameters with the ? placeholders, intead of concatenating variables into the SQL string. You don't quote parameter placeholders in your SQL query. That way you won't run into hard-to-debug syntax errors like the ones you found.
Here's an example showing the fixes:
CREATE PROCEDURE sp_Test(
emailSent_In BIGINT UNSIGNED,
emailTransport_In VARCHAR(100),
formSecret_In VARCHAR(32)
)
BEGIN
SET #query = '
UPDATE tbl_JustSayThanks
SET emailSent = ?,
emailTransport = ?,
emailSentDate = NOW()
WHERE formSecret = ?';
SET #es = emailSent_In;
SET #et = emailTransport_In;
SET #fs = formSecret_In;
PREPARE stmt FROM #query;
EXECUTE stmt USING #es, #et, #fs;
DEALLOCATE PREPARE stmt;
END//
DELIMITER ;
Final comment:
Your example query has no dynamic syntax elements, only dynamic values. So you don't need to use a prepared statement at all.
This is how I'd really write the procedure:
CREATE PROCEDURE sp_Test(
emailSent_In BIGINT UNSIGNED,
emailTransport_In VARCHAR(100),
formSecret_In VARCHAR(32)
)
BEGIN
UPDATE tbl_JustSayThanks
SET emailSent = emailSent_In,
emailTransport = emailTransport_In,
emailSentDate = NOW()
WHERE formSecret = formSecret_In;
END//
DELIMITER ;
You should also be aware that MySQL stored procedures are greatly inferior to Microsoft SQL Server. MySQL doesn't keep compiled stored procedures, it doesn't support packages, it doesn't have a debugger... I recommend you do not use MySQL stored procedures. Use application code instead.

MySQL prepared statement nvarchar

I have a prepared statement which should update an field.
CREATE PROCEDURE `update_table` (in id INT, in col nvarchar(11), in val nvarchar(10))
BEGIN
SET #sql = concat('UPDATE table SET ', col, ' = ', val , ' WHERE id = ', id);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
If I call the procedure with a string containing a hyphen (e.g. A-B)
CALL update_table(1, 'reg', 'A-B');
I get
Error 1054: Unknown column 'A' in 'field list'
Can you please assist in solving the issue?
Edit: I just figuered out the hyphen is not the cause of error. If I try to update with 'AB', the same error comes up.
The field to be updated is nvarchar as well with the same field length.
You're vulnerable to sql injection attacks, basically. Your sproc generated this sql:
UPDATE ... WHERE reg = A-B
Note the lack of quotes around A-B. You're not storing the string A-B in the reg field. You're doing mathematical subtraction: reg = A minus B, and neither A nor B are fields that exist in your table.
At BARE minimum you'd need:
SET #sql = concat('UPDATE table SET ', col, ' = "', val , '" WHERE id = ', id);
^----------^
so you're generating
UPDATE ... reg = "A-B"

Dynamic MySQL Update Statement

I'm trying to write a prodecure that updates a value in a given column-name where the users id equals given user ID.
_strong_1 is a variable that contains the column name, i.e: 'category_1', for example.
SELECT COLUMN_NAME FROM information_schema.`COLUMNS` C
WHERE table_name = 'subscribers_preferences' AND COLUMN_NAME LIKE _strong_1 INTO #columns;
SET #table = 'subscribers_preferences';
SET #s = CONCAT('UPDATE ',#table,' SET ', #columns = 1);
PREPARE stmt FROM #s;
EXECUTE stmt;
There's an error within the 'SET #s =' statement. I can get it to work with a simple SELECT statement, but UPDATE is being tricky.
Thanks in advance.
You need to put = 1 in quotes.
SET #s = CONCAT('UPDATE ',#table,' SET ', #columns, ' = 1');
Otherwise, you're comparing #columns with 1, and concatenating either 1 or 0 (probably always 0, since I doubt you have a column named 1) to the SQL, which is creating invalid SQL.
Note that the above code will only update one column. If #columns is supposed to hold 3 columns, you need to use GROUP_CONCAT in your query that sets it.
SELECT GROUP_CONCAT(CONCAT(column_name, ' = 1')) AS #columns
FROM information_schema.columns
WHERE table_name = 'subscribers_preferences' and column_name LIKE _strong_1;
SET #table = 'subscribers_preferences';
SET #s = CONCAT('UPDATE ',#table,' SET ', #columns);
I suspect you also need to add a WHERE clause to this SQL so it just updates the row for the given ID. As currently written, it will update all rows.
The fact that you need to write the query like this suggests improper normallization of your data. Instead of having each preference option in a different column, they should be different rows of the table, with the key being something like (user_id, setting_name).