Evaluation and assignment order of SET - mysql

The MySQL manual on user variables says that
As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement. For example, to increment a variable, this is okay:
SET #a = #a + 1;
For other statements, such as SELECT, you might get the results you expect, but this is not guaranteed. In the following statement, you might think that MySQL will evaluate #a first and then do an assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for expressions involving user variables is undefined.
This is apparently comparing apples and oranges. SELECT #a:=#a+1; would be the equivalent, and would be just as fine as SET #a=#a+1, since #a+1 has always to be evaluated before assigning its value to #a.
However, if we do the same for SET as for SELECT, ie.
SET #b=#a,#a=#a+1;
is the value of #b guaranteed to contain the value of #a from before executing the statement? Or can it contain the incremented value?
The following questions can be asked:
Will the expressions be evaluated from left to right? Ie. after
SET #a =(#v:='x'), #b = (#v:='y');
is #v guaranteed to be 'y'?
Will the assignments be done from left to right? Ie., after
SET #v='x',#v='y';
is #v guaranteed to be 'y'?
Will all evaluations happen strictly before all assignments? Ie., after
SET #a='x',#v=#a,#a='y';
is #v guaranteed to contain the value of #a from before the statement is executed? ("If any variable assignment in a SET statement fails, the entire statement fails and no variables are changed" from the manual suggests this, though it would also allow SET to save the old values and restore them when an assignment fails.)
Although the MySQL manual on stored program variables says that 'Variables can be set directly with the SET statement. See Section 13.7.4.1, “SET Syntax for Variable Assignment”.', I noticed that when I do
DELIMITER //
CREATE PROCEDURE TEST_SET()
SQL SECURITY INVOKER
BEGIN
SET #v='BEFORE',#a='BEFORE';
SET #a='x',#v=#a,#a='y',#a=(SELECT * FROM doesntexist);
END//
DELIMITER ;
and then
CALL TEST_SET();
SELECT #a,#v;
apparently the SET did evaluations and assignments strictly in alternation from left to right, until it hit the error. This is different behavior from executing the SET outside of the stored procedure, and no "If any variable assignment in a SET statement fails, ... no variables are changed." I am confused.
The MySQL manual on SET does not provide answers to these questions.
Please note that it does not help try the statements with MySQL and see what result is obtained. I want to know what is guaranteed.

Related

Variables in mysql initialized with string datatype

In Mysql, user variables are taking string values when declared inside select statement.
I have a table named 'a' with one column named 'amount' as shown below
amount
100
1000
1000
111115000000062760
111115000000062765
111115000000062770
111115000000062775
111115000000062780
Consider that I want to display null for amount values that are repeating in this table in the inserted order.
I've written the below query to achieve the same :
select
CASE
WHEN (#x != amount) THEN amount
END result,
CASE
WHEN ((#x) != amount) THEN #x:=amount
END dummy
from (select amount, #x:=0 x from a) q;
I'm creating a variable named '#x' in innermost query with default value as 0. When processing each row, I'm taking that row's 'amount' value and setting in the variable. Hence, in next row, I'll use the variable to hold previous row's data.
Using current and previous amount values, I'll only show the data that differ.
problem :
When I execute the query, I got the below output :
This is wrong as only 3rd row must have NULL values. 5th and 6th rows must have values. But, When I execute the query again, I got the below output :
This is the correct result. When subsequently executing the query, it always works fine. Only when it is executed for the first time after connecting to DB, this issue occurs.
Debugging :
I've tried setting #x's value before executing the query and it works fine. But, I want to know why it wouldn't work if I set it inside the select query.
I tried printing #x's values in the query and the only difference between the first query result and subsequent query results is alignment of #x's values. I think #x is considered as a string (as it is left aligned) in the first query. Hence, it breaks when comparing with certain bigint values.
When checking the documentation, it is mentioned that referring to a variable that has not been initialized will be taken as 'string'
If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.
I believe this is what's happening here. After first query, the variable probably got declared with 'integer' type which made it work in subsequent queryies.
Is it not possible to provide the variable's datatype inside select statement? Kindly advise.
No, to specify the type of your user defined variable you MUST declare it outside of the SELECT statement.
As you have already pointed out the MySQL documentation specifically mentions that you MUST declare the variable first:
9.4 User-Defined Variables
If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.
Beginning with MySQL 8.0.22, a reference to a user variable in a prepared statement has its type determined when the statement is first prepared, and retains this type each time the statement is executed thereafter. Similarly, the type of a user variable employed in a statement within a stored procedure is determined the first time the stored procedure is invoked, and retains this type with each subsequent invocation.
This is very clear, either you formally declare the variable first, or it will be a string that is initialized with a value of NULL.
So just declare the variable first, you can declare variables within your inline SQL scripts, so don't try to fight it, either declare your variable first, or modify your query to use the variable as a string, either by casting amount to a string in the inner most query or cast #x to your numeric type in the comparison:
select
CASE
WHEN (CAST(#x as SIGNED) != amount) THEN amount
END result,
CASE
WHEN (CAST(#x as SIGNED) != amount) THEN #x:=amount
END dummy
from (select amount, #x:=0 x from a) q;

use of ':=' fails in select in stored procedure due to variable definition

Why does the use of the assignment operator := fail to parse in this stored procedure (fragment)? In the update statement, in the set median = [select expression], in the expression, the MySQL 5.6 parser reports the error, "[Check]...for the right syntax to use near ':= row + 1 as row, $vol as $vol from univ_members' ".
delimiter //
CREATE PROCEDURE m()
BEGIN
DECLARE row int;
SELECT row := row + 1 AS row;
END //
delimiter ;
Running the select statement the mysql shell also fails, but says, 'row' is not a system variable or 'row' is not a column, depending on whether I try to define it with 'set'.
Do you know of a limitation in a stored procedure that prohibits this, or of such a bug in MySQL 5.6? If so, is there a workaround? Can you suggest an alternative approach?
So, after struggling like a man blinded by darkness, I defined the variable #row in the shell using 'set' (the shell's parser does not allow 'row') and it worked. The parser however does not allow a variable defined in a stored procedure with 'declare' to begin with a '#', but, if defined with 'set', it works, it does allow it to be used as the left-hand value in the :=.
So, it's an issue with variable definition. Evidently, only 'user variables', which must begin with a '#' and must be defined with 'set', can be assigned values with ':='. (See User-Defined Variables)
I find such a nuance that all variables don't share a common behavior when it comes to assignment non-intuitive and incredibly irritating. Am I still missing something?

How to get depth in mysql store procedure recursion?

I have a recursive mysql stored procedures for which I have set the max_sp_recursion_depth=10.
Now, without setting a local variable, i would like to know what recursion's level is during single execution.
I think that surely there is a session variable that stores the depth (how else would you know when you reach the maximum level) but I could not find it. I would avoid using a variable to do this incrementally. How could i know this (if any) system variable?
I know you specifically asked how to do this without a user-created variable - but for others coming across this thought it would be worth posting how to do it with one as it's fairly simple:
CREATE PROCEDURE sp_recursive
BEGIN
// ... DECLAREs here
-- Set maximum recursion depth (max is 255)
SET ##SESSION.max_sp_recursion_depth = 10;
-- Increment current recursion depth
SET #recursion_depth = IFNULL(#recursion_depth + 1, 1);
-- ... More stored procedure code
-- Decrement current recursion depth. Note: Care must be taken to ensure this line
-- is *always* executed at the end of the stored procedure.
SET #recursion_depth = #recursion_depth - 1;
END
Explanation
The #recursion_depth session-scoped variable is incremented by the above SET statement each time the stored procedure is entered. The first time it is entered, the variable is uninitialized and so has a value of NULL - this is checked for by the IFNULL(), which reassigns it to one in this case. At the end of the stored procedure just before exiting, the depth needs to be decremented.
Further notes
Worth noting that SQL Server does provide an in-built ##NESTLEVEL variable for doing the above - but unfortunately MySQL doesn't seem to have an equivalent.

MySql stored procedure SET command -- when it errors

After Googling for awhile I didn't see an answer. Anyway I have a situation in a stored procedure where I do a set select like:
SET someVariable = (SELECT ...)
Anyway, due to some redundant records existing somewhere else in the system, this SELECT query used in the SET returns more than one row. I'm guessing this will cause breakage or badness? True, false?
Thanks.
True. When assigning to a variable, the query must return a single row, containing a single column. You can also do it with this syntax:
SELECT someColumn INTO myVariable ... LIMIT 1;

Does SQL Server Management Studio (or SQL Server) evaluate *all* expressions?

Here's my configuration:
I have a re-runnable batch script that I use to update my database.
Inside of that batch script, I have code that says the following:
If Table 'A' doesn't exist, then create Table 'A' and insert rows into it.
Later on in that batch script, I create an schemabound indexed view on that table.
And if you didn't already know, indexed views require specific client settings.
Sometimes, when I re-run the script, that is after the table has been created, SQL Server Management Studio evaluates the "insert rows" code, which is protected by the 'If this table doesn't exist' code, and yields the following error:
Msg 1934, Level 16, State 1, Line 15
INSERT failed because the following SET options have incorrect settings: 'CONCAT_NULL_YIELDS_NULL, ANSI_WARNINGS, ANSI_PADDING, ARITHABORT'. Verify that SET options are correct for use with indexed views and/or indexes on computed columns and/or filtered indexes and/or query notifications and/or XML data type methods and/or spatial index operations.
Please note: If someone were to try this INSERT statement in a vacuum, I would fully expect SSMS to generate this error.
But not when it's protected by a conditional block.
My Question:
Does the SSMS compiler evaluate all expressions, regardless of whether they will actually be executed?
Yes, it evaluates all of them,take a look at this
declare #i int
select #i =1
if #i = 1
begin
declare #i2 int
set #i2 = 5
end
else
begin
declare #i2 int
set #i2 = 5
end
Msg 134, Level 15, State 1, Line 12
The variable name '#i2' has already been declared. Variable names must be unique within a query batch or stored procedure.
Another example with temp tables is here: What is deferred name resolution and why do you need to care?
your only way out would be to wrap it inside dynamic SQL
Note that most of the settings you mention are connection-level, i.e. in case you set/change them they stay in effect unless you close the connection or explicitly change their value.
Returning to your question. The error you mention looks like runtime error, i.e. the INSERT is actually being executed. It would be better if you could show your script (omitting details, but keeping batches).
Edit: it is not SSMS compiler that evaluates SQL you try to execute - it is SQL Server. What do you meant by 'evaluate'? Is it 'execute'? When you run a batch (which is what actually is being executed by a server), SQL Server first does syntactic analysis and throws error in case it finds any syntactic error, nothing is being executed at this point of time. In case syntax is ok, the server starts executing you batch.
Again, the error you show seems to be runtime - so I guess you'd carefully watch for the conditions and track what happens (or provide us more details about 'sometimes').