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.
Related
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?
Our database has a function to generate an order number. It reads a value from a Settings table, increments it, then returns the new value. For example:
CREATE FUNCTION NextOrderNumber() RETURNS INTEGER UNSIGNED NOT DETERMINISTIC
BEGIN
DECLARE number INTEGER UNSIGNED;
UPDATE Settings SET IntegerValue=LAST_INSERT_ID(IntegerValue+1) WHERE KeyName='NextOrderNumber';
SET number=LAST_INSERT_ID();
return number;
END
Note: Don't critique this function I know it has flaws it's just for illustration.
We use this function as follows:
INSERT INTO Orders(OrderNumber, ...)
SELECT NextOrderNumber(), ...
When binary logging is enabled, CREATE FUNCTION gives this error:
This function has none of
DETERMINISTIC, NO SQL, or READS SQL
DATA in its declaration and binary
logging is enabled (you might want
to use the less safe
log_bin_trust_function_creators
variable)
Regardless of what binlog_format is set, is there really a problem with the above function? According to my reading of the relevant MySQL page I can't see any reason why this function would be incompatible with replication, with either ROW or STATEMENT level binary logging.
If the function is safe, setting the global log_bin_trust_function_creators=1 makes me uneasy. I don't want to disable this check for all functions, just this one. Could I instead just flag the function as NO SQL to suppress the warning? I tried it and it worked. Will this cause any problem?
I've googled and here I am.
I've found a way :
SET GLOBAL log_bin_trust_function_creators = 1;
But be careful, it may be unsafe for data recovery or replication...
As per my understating it cause problem when data recovery or replication
Ref: http://dev.mysql.com/doc/refman/5.0/en/stored-programs-logging.html
MySQL 5.0.6: Statements that create stored routines and CALL statements are logged. Stored function invocations are logged when they occur in statements that update data (because those statements are logged).
However, function invocations are not logged when they occur in statements such as SELECT that do not change data, even if a data change occurs within a function itself; this can cause problems.
Under some circumstances, functions and procedures can have different effects if executed at different times or on different (master and slave) machines, and thus can be unsafe for data recovery or replication.
E.g.
CREATE FUNCTION myfunc () RETURNS INT DETERMINISTIC
BEGIN
INSERT INTO t (i) VALUES(1);
RETURN 0;
END;
SELECT myfunc();
If a stored function is invoked within a statement such as SELECT that does not modify data, execution of the function is not written to the binary log, even if the function itself modifies data. This logging behavior has the potential to cause problems. Suppose that a function myfunc() is defined as above.
There are two ways to fix this:
Execute the following in the MySQL console:
SET GLOBAL log_bin_trust_function_creators = 1;
Add the following to the mysql.ini configuration file:
log_bin_trust_function_creators = 1
The setting relaxes the checking for non-deterministic functions. Non-deterministic functions are functions that modify data (i.e. have update, insert or delete statement(s)). For more info, see here.
Please note, if binary logging is NOT enabled, this setting does not apply.
Have a think about what's getting written to the binary log.
You can't ensure that an order created on a master would have the same sequence generated for it when the transaction is played on a slave - or, what would much more likely, by another master in the cluster. e.g.
0) Node 1 and Node 2 are in sync, NextOrderNumber=100
1) Node 1 receives insert statement wrt order from customer A and assigns
order number 100, changes its NextOrderNumber to 101
2) Node 1 writes the settings update to the log
3) Node 1 writes the insert statement to the log
4) Node 2 processes for customer B, asigns order number 100 and increments
5) Node 2 writes the settings update from to the log
6) Node 2 writes the insert statement to the log
7) Nodes 2 reads settings update from the log #2
- Its NextOrderNumber is now 102
8) Node 2 reads insert from log #3, tries to apply it but it fails
due to duplicate key
9) Node 1 reads the update #5 - Its nextOrderNumber is also now 102
10) Node1 reads insert from log #6 -
but this fails due to duplicate key
Now orders 100 on the 2 nodes refer to different data, and there is no order 101.
There is a reason that there has been a lot of functionality added to modify the behaviour of auto_increment variables.
If you wrap the insert in a procedure - which retrieves a value from the sequence generator then embeds it in the insert statement the immediate problem will be resolved, however you need to think about how you avoid assigning the same number twice using different database nodes.
Could I instead just flag the function as NO SQL to suppress the warning? I tried it and it worked. Will this cause any problem?
According to this Mysql doc:
Assessment of the nature of a function is based on the “honesty” of the creator: MySQL does not check that a function declared DETERMINISTIC is free of statements that produce nondeterministic results.
So it's up to you. If you are sure the method won't cause any problem...
Writing the attribute helped me. In this function, you need to write - MODIFIES SQL DATA - because the function uses UPDATE. If only SELECT is used in the function, then we would write READS SQL DATA. You can also write these two attributes if both data read and write operators are used in the function body.
CREATE FUNCTION NextOrderNumber()
RETURNS INTEGER
UNSIGNED
NOT DETERMINISTIC
MODIFIES SQL DATA
BEGIN
DECLARE number INTEGER UNSIGNED;
UPDATE Settings SET IntegerValue=LAST_INSERT_ID(IntegerValue+1)
WHERE KeyName='NextOrderNumber';
SET number=LAST_INSERT_ID();
return number;
END
Execute this just before creating the function:
SET ##global.log_bin_trust_function_creators = 1;
And add MODIFIES SQL DATA to the declaration.
Also... well, you asked not to comment the function itself, but I suggest that you drop the number variable and simply do RETURN LAST_INSERT_ID().
add READS SQL DATA which declare that is a read only function :
CREATE FUNCTION NextOrderNumber() RETURNS INTEGER UNSIGNED NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE number INTEGER UNSIGNED;
UPDATE Settings SET IntegerValue=LAST_INSERT_ID(IntegerValue+1) WHERE KeyName='NextOrderNumber';
SET number=LAST_INSERT_ID();
return number;
END
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').
I'm a SQL noob, and I need a little bit of help understanding the big picture of if it is possible, and if so how to go about filtering a result set based on the return value of a function which is passed one of the fields of the record.
Let's say I have a table called "Numbers" with just one field: "Value".
How could I correctly specify the following "pseudo-sql"?:
SELECT Value FROM numbers WHERE IsPrime(Value)=true
Can I accomplish such a thing, and if so, where/how do I put/store "IsPrime"?
I'm using MySQL.
I agree with extraneon that it's usually better to store this value in the database rather than compute it in the where clause. This way you can compute it once per row and index the data for faster performance.
As long as you are on MySQL 5.x, I would recommend a stored function, as opposed to a UDF. You can add an IS_PRIME column of type TINYINT to your DB, add an index on that column, then use the stored function to calculate the value at insert time. You can even calculate the IS_PRIME value using a before insert trigger if you don't want to change the insert code.
The stored function would look something like this:
DELIMITER $$
DROP FUNCTION IF EXISTS IS_PRIME $$
CREATE FUNCTION IS_PRIME(P_INT BIGINT) RETURNS TINYINT
BEGIN
DECLARE V_FACTOR BIGINT;
DECLARE V_MAX_FACTOR BIGINT;
SET V_FACTOR := 2;
SET V_MAX_FACTOR := round(sqrt(P_INT),0);
WHILE (V_FACTOR <= V_MAX_FACTOR)
DO
IF (P_INT % V_FACTOR) = 0
THEN
RETURN FALSE;
END IF;
SET V_FACTOR := V_FACTOR + 1;
END WHILE;
RETURN TRUE;
END $$
DELIMITER ;
I think you may find some help with the doc about the user-defined functions: http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html
I don't know anything about user defined functions, but I could imagine that for computation-intensive functions it might be best to have that value precomputed and stored in the database somewhere.
Depending on how the data gets in the database you could require the client to compute isPrime (that can be done if the client is a web service or app on your own server), or perhaps a scheduled job which processes every record with isPrime is null or something like that. That is not instantaneous, but for some scenario's it may be good enough.
If isPrime is only used sometimes you could also post-process/filter the data on the client when retrieving data.
In another question I posted someone told me that there is a difference between:
#variable
and:
variable
in MySQL. He also mentioned how MSSQL has batch scope and MySQL has session scope. Can someone elaborate on this for me?
MySQL has a concept of user-defined variables.
They are loosely typed variables that may be initialized somewhere in a session and keep their value until the session ends.
They are prepended with an # sign, like this: #var
You can initialize this variable with a SET statement or inside a query:
SET #var = 1
SELECT #var2 := 2
When you develop a stored procedure in MySQL, you can pass the input parameters and declare the local variables:
DELIMITER //
CREATE PROCEDURE prc_test (var INT)
BEGIN
DECLARE var2 INT;
SET var2 = 1;
SELECT var2;
END;
//
DELIMITER ;
These variables are not prepended with any prefixes.
The difference between a procedure variable and a session-specific user-defined variable is that a procedure variable is reinitialized to NULL each time the procedure is called, while the session-specific variable is not:
CREATE PROCEDURE prc_test ()
BEGIN
DECLARE var2 INT DEFAULT 1;
SET var2 = var2 + 1;
SET #var2 = #var2 + 1;
SELECT var2, #var2;
END;
SET #var2 = 1;
CALL prc_test();
var2 #var2
--- ---
2 2
CALL prc_test();
var2 #var2
--- ---
2 3
CALL prc_test();
var2 #var2
--- ---
2 4
As you can see, var2 (procedure variable) is reinitialized each time the procedure is called, while #var2 (session-specific variable) is not.
(In addition to user-defined variables, MySQL also has some predefined "system variables", which may be "global variables" such as ##global.port or "session variables" such as ##session.sql_mode; these "session variables" are unrelated to session-specific user-defined variables.)
In MySQL, #variable indicates a user-defined variable. You can define your own.
SET #a = 'test';
SELECT #a;
Outside of stored programs, a variable, without #, is a system variable, which you cannot define yourself.
The scope of this variable is the entire session. That means that while your connection with the database exists, the variable can still be used.
This is in contrast with MSSQL, where the variable will only be available in the current batch of queries (stored procedure, script, or otherwise). It will not be available in a different batch in the same session.
MSSQL requires that variables within procedures be DECLAREd and folks use the #Variable syntax (DECLARE #TEXT VARCHAR(25) = 'text'). Also, MS allows for declares within any block in the procedure, unlike MySQL which requires all the DECLAREs at the top.
While good on the command line, I feel using the set = #variable within stored procedures in MySQL is risky. There is no scope and variables live across scope boundaries. This is similar to variables in JavaScript being declared without the var prefix, which are then the global namespace and create unexpected collisions and overwrites.
I am hoping that the good folks at MySQL will allow DECLARE #Variable at various block levels within a stored procedure. Notice the # (at sign). The # sign prefix helps to separate variable names from table column names - as they are often the same. Of course, one can always add an "v" or "l_" prefix, but the # sign is a handy and succinct way to have the variable name match the column you might be extracting the data from without clobbering it.
MySQL is new to stored procedures and they have done a good job for their first version. It will be a pleasure to see where they take it form here and to watch the server side aspects of the language mature.
In principle, I use UserDefinedVariables (prepended with #) within Stored Procedures. This makes life easier, especially when I need these variables in two or more Stored Procedures. Just when I need a variable only within ONE Stored Procedure, than I use a System Variable (without prepended #).
#Xybo:
I don't understand why using #variables in StoredProcedures should be risky. Could you please explain "scope" and "boundaries" a little bit easier (for me as a newbe)?
#variable is very useful if calling stored procedures from an application written in Java , Python etc.
There are ocassions where variable values are created in the first call and needed in functions of subsequent calls.
Side-note on PL/SQL (Oracle)
The advantage can be seen in Oracle PL/SQL where these variables have 3 different scopes:
Function variable for which the scope ends when function exits.
Package body variables defined at the top of package and outside all functions whose scope is the session and visibility is package.
Package variable whose variable is session and visibility is global.
My Experience in PL/SQL
I have developed an architecture in which the complete code is written in PL/SQL. These are called from a middle-ware written in Java. There are two types of middle-ware. One to cater calls from a client which is also written in Java. The other other one to cater for calls from a browser.
The client facility is implemented 100 percent in JavaScript. A command set is used instead of HTML and JavaScript for writing application in PL/SQL.
I have been looking for the same facility to port the codes written in PL/SQL to another database. The nearest one I have found is Postgres. But all the variables have function scope.
Opinion towards # in MySQL
I am happy to see that at least this # facility is there in MySQL. I don't think Oracle will build same facility available in PL/SQL to MySQL stored procedures since it may affect the sales of Oracle database.