MYSQL - Prepared Statement NULL handling - mysql

I have good number of prepared statements in my MYSQL stored procedure . There is a chance the statement can become NULL ( the dynamically generated SQL statement ) , so to avoid run time exception I am using the below approach always . But since I am using prepared statements a lot , giving NULL check every where looks noisy .
SET #s10=CONCAT('UPDATE tlist SET total_count=total_count-1 WHERE group_id =',in_location_id,' AND sub_type=',COMPANY_COUNT,' AND sub_id in (',var_companies,')' );
IF(#s10 is NOT NULL) THEN
PREPARE stmt FROM #s10;
EXECUTE stmt;
END IF;
So is there any other approach that I can follow to mimize the above code into one or two lines ?
( Making statement and executing it together and Null handling together in one step ?)
I know I can run small subroutine and pass the prepared statement to it and do the above check , But want to know if there is any inbuilt MYSQL functionality for it .
Thank You

try
SET #s10=CONCAT('UPDATE tlist SET total_count=total_count-1 WHERE group_id =',in_location_id,'
AND sub_type=',COMPANY_COUNT,' AND sub_id in (',var_companies,')' );
IF(!ISNULL(#s10) && LENGTH(trim(#s10)) > 0) THEN
PREPARE stmt FROM #s10;
EXECUTE stmt;
END IF;

Related

Error while creating a view with procedure MySQL

I have a problem when I try to create a view using a procedure. I have to do that because I need to make a pivot in MySQL, converting rows of a table in columns of another.
The query works great, but when I put it in the "CREATE VIEW" statement it gives me error.
Here is the query with CREATE view
CREATE VIEW `Untitled` AS
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(formazioni_persone.id_formazione = ',
formazioni.id,
', true, false)) AS "',
formazioni.titolo,'"'
)
) INTO #sql
FROM formazioni;
SET #sql = CONCAT('SELECT persone.*, ', #sql, ' FROM persone INNER JOIN formazioni_persone ON persone.id = formazioni_persone.id_persona GROUP BY persone.id');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
The query without CREATE VIEW Untitled AS works great
The query without CREATE VIEW Untitled AS works great. I already tried to create a TEMP TABLE inside the CREATE VIEW, but nothing. Also tried to use delimiters like that, but nothing
DELIMITER $$
CREATE VIEW `Untitled` AS
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(formazioni_persone.id_formazione = ',
formazioni.id,
', true, false)) AS "',
formazioni.titolo,'"'
)
) INTO #sql
FROM formazioni;
SET #sql = CONCAT('SELECT persone.*, ', #sql, ' FROM persone INNER JOIN formazioni_persone ON persone.id = formazioni_persone.id_persona GROUP BY persone.id');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END $$
DELIMITER ;
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 'SET #sql = NULL' at line 2, Time: 0.082000s
A VIEW is not a procedure. A VIEW is only a single SELECT statement, which must have fixed columns at the time you define the VIEW. You can't make a VIEW that is also a procedure.
Sorry, if you need a pivot-table, you need to specify the values for each column in the query. You can't make a SELECT query or a VIEW that dynamically adds more columns as it finds potential future values.
And you can't define a VIEW that runs an arbitrary block of procedure code anyway. That would require a procedure.
You should just use the solutions that are already shown in questions like MySQL - Rows to Columns
There are no other shortcuts or workarounds.
By the way, all SQL databases have this restriction, not just MySQL.
Re your question:
I'm looking for a solution that doesn't require manual update of the query
A pivot-table query must have as many columns in the select-list as the number of columns you want it to return. There is no way to make an SQL query that expands the number of columns dynamically as a result of the data it reads at execute time.
The only way you can make a single query that returns all the data is to NOT do a pivot-table query, and instead return all the data in rows, not columns.
SELECT p.*, f.titolo, pf.id_persona IS NOT NULL AS ha_formazioni
FROM persone AS p
CROSS JOIN formazioni AS f
LEFT OUTER JOIN formazioni_persone AS fp ON fp.id_formazioni AND fp.id_persona = p.id
This will return one row for each formazioni per persone. Then you must write code in your application to loop over all the rows of the reesult, and format the data in columns in the manner you want.

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.

Prepared statement execution with variable number of parameters to be bound

Some stored procedures I work with need to interpolate WHERE criteria based on if procedure input parameters have been supplied. To avoid potential injection points, I'd like to utilize parameter binding for the values that are to be part of the interpolated criteria.
Since the criteria added to the prepared statement and thus the number of parameters to be bound may differ depending on the user input, I devised the method below to determine which variables will be passed to the EXECUTE statement. This works, but it seems inelegant.
CREATE PROCEDURE foo (IN mandatory INT, IN optional INT, IN optional2 VARCHAR(20))
BEGIN
SELECT
0, '', '', mandatory, optional, optional2
INTO
#params, #sql, #where, #m, #o1, #o2;
IF (#o1 > '' AND #o1 IS NOT NULL) THEN
SET #where = CONCAT(#where, ' AND field = ?');
SET #params = #params + 1;
END IF;
IF (#o2 > '' AND #o2 IS NOT NULL) THEN
SET #where = CONCAT(#where, ' AND field2 = ?');
SET #params = #params + 3;
END IF;
SET #sql = CONCAT('
SELECT id, bar FROM table
WHERE
baz = ?
', #where
);
PREPARE STMT FROM #sql;
CASE #params
WHEN 0 THEN EXECUTE STMT USING #m;
WHEN 1 THEN EXECUTE STMT USING #m, #o1;
WHEN 3 THEN EXECUTE STMT USING #m, #o2;
WHEN 4 THEN EXECUTE STMT USING #m, #o1, #o2;
END CASE;
DEALLOCATE PREPARE STMT;
END$$
I'm aware of alternatives:
The binaries that would call these stored procedures have a function that attempts to identify potential SQL injection by passing the user supplied strings through a regular expression.
A user-defined function could be used to dynamically construct the EXECUTE statement given a dynamic number of inputs.
However, I was wondering if anyone else has ran into this desire to handle dynamic construction of an EXECUTE statement purely with SQL.
However, I was wondering if anyone else has ran into this desire to handle dynamic construction of an EXECUTE statement purely with SQL.
Yes, me too.
Here's a PHP solution to generate the list of question marks for a prepared statement based on an array of unknown length:
/* My target query is this:
SELECT fun FROM fun_stuff WHERE fun_key IN ( ...unknown number of values... )
*/
/* For this example let's set our array to this: */
$val_arr = array(1,2,3,4,5,6,7,8,9);
$val_arr_cnt = count($val_arr); /* and count it */
/* Now make prepared statement q-mark string from values array */
$sql_prep = str_pad('?', ($val_arr_cnt * 2) - 1, ',?', STR_PAD_RIGHT);
/* add it to query */
$sql = "SELECT fun FROM fun_stuff WHERE fun_key IN ($sql_prep)";
/* And the result:
SELECT fun FROM fun_stuff WHERE fun_key IN (?,?,?,?,?,?,?,?,?)
*/
I've no idea how efficient this is. But I too every now and then want to implement the security and efficiency of MySQL prepared statements but have variable length input arrays
Not sure if it's possible to dynamically build the parameter list (changing the number of parameters on the fly, etc...). But since you can dynamically build your where clause, one very simple workaround is do something like this. Assuming your validations permit it, the else clause basically has the same effect as ignoring the parameter you may or may not be filtering on.
if p_cust_id is not null && p_cust_id > 0 then
set v_where_clause = concat(v_where_clause, ' c.cust_id = ? ');
set #v_cust_id := p_cust_id;
else
set v_where_clause = concat(v_where_clause, ' c.cust_id > ? ');
set #v_cust_id := 0;
end if;
then plug all the user variables above into your execute statement
execute str1 using #v_cust_id, #v_etc....;*

How can you disable result output for the mysql EXECUTE command in workbench

I'm trying to use a prepared statement in mysql workbench in a cursor. The cursor works on a very big data set so it is executed many times. Every time a new result is shown for the EXECUTE step. This results eventually in mysql workbench crashing because of too many open result windows.
In the cursor I do something like this:
PREPARE stmt2 FROM #eveningQuery;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
Normally I use stuff like
set aVar = (EXECUTE stmt2);
to silence the query but EXECUTE doesn't work like that.
Does anybody know how you can disable the output for the EXECUTE command in mysql?
Note: I understand how i can retrieve the data in a variable, however what I want to prevent is that it is displayed in the results overview like this
This will make mysql-workbench crash when looped too much.
edit because it was asked an example of the #eveningQuery.
SET #eveningQuery = CONCAT('select #resultNm := exists (select idSplitBill from tb_SplitDay where idSplitBill =', idSplitBillVar, ' and ', #columnNameEv ,' = 1 and softdelete = 0)');
idSplitBillVar = the id coming from the cursor.
#columnNameEv = a column that i am filling in variably.
I added this info because it was asked, however it doesn't really matter in my opinion because the question still stands even with the most simple query. When you execute a prepared statement, you will get a output result. I just want to disable this behaviour.
The query you use creates new result-set, and GUI client show it (...many times) -
SELECT #resultNm:=EXISTS(
SELECT idSplitBill FROM tb_SplitDay
WHERE idSplitBill =', idSplitBillVar, ' AND ', #columnNameEv ,' = 1 AND softdelete = 0
)
You can rewrite this query, and result-set won't be created -
SELECT EXISTS(
SELECT idSplitBill FROM tb_SplitDay
WHERE idSplitBill =', idSplitBillVar, ' AND ', #columnNameEv ,' = 1 AND softdelete = 0
)
INTO #resultNm

how to configure the table at runtime in mysql query

people know that we can use if statement to configure a query in the select statement like this
select if(var=1,amount,amount/2) from mytable;
But what if I want to achieve something like this:
select amount from if(var=1,mytable1,mytable2);
Is there any way to configure the table at run time?
SELECT amount FROM mytable1 WHERE #var = 1
UNION
SELECT amount FROM mytable2 WHERE #var = 0
UPD: Here's what MySQL EXPLAIN looks like for the part of the query which has a condition evaluating to FALSE:
Note the Impossible WHERE part. MySQL recognizes that the expression in WHERE is constantly evaluating to FALSE, so it doesn't even try executing the query. Hence, no performance overhead when using this approach.
(Upgrading to an answer)
Where did var come from?
If it's a variable in another language, you could test it in that other language and then construct different SQL as appropriate:
$sql = "SELECT amount FROM " . ($var = 1 ? "mytable1" : "mytable2");
If it's a user variable in SQL, you could similarly use an IF statement around the two alternative SELECT statements:
DELIMITER ;;
IF #var = 1 THEN
SELECT amount FROM mytable1;
ELSE
SELECT amount FROM mytable2;
END IF;;
DELIMITER ;
If it's anything else (like a field from your tables), then your question doesn't make a great deal of sense.
Based on the mysql manual pages, it appears you cannot do this with the traditional syntax.
"User variables are intended to provide data values. They cannot be used directly in an SQL statement as an identifier or as part of an identifier, such as in contexts where a table or database name is expected, or as a reserved word such as SELECT."
- [http://dev.mysql.com/doc/refman/5.6/en/user-variables.html][1]
The exception to this is that you can assemble a prepared statement, but is probably not a better solution for most programming tasks. It would be better to leave the sql string generation to the language invoking mysql.
But, if you are doing this as part of a "sql only" task, like an import, this seems to be the approach you must take.
SET #s = CONCAT("SELECT * FROM ", if(true, "table1", "table2"), " LIMIT 1");
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #s = CONCAT("SELECT * FROM ", if(false, "table1", "table2"), " LIMIT 1");
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;