Alternatives to dynamic sql in stored function - mysql

I'm writing a stored function where I calculate the position of a cell, which value I need to select from a table. To do this, I decided to save this position in a variable, in order to use it as the offset of a LIMIT clause.
According to my research, the way of using values set into local variables with the LIMIT statement is through a prepared statement, but I also got that prepared statements (nor any dynamic SQL) are allowed in stored functions. Are there any alternatives to solve my problem?
A simplified example of my situation:
CREATE FUNCTION foo(a int) RETURNS decimal DETERMINISTIC
BEGIN
SET #var1 := (SELECT COUNT(*) FROM table);
SET #var2 := (ROUND(#var1 * a/5))
PREPARE STMT FROM 'RETURN (SELECT * FROM other_table LIMIT ?, ?)';
EXECUTE STMT USING #var2, #var1;
END
$$ DELIMITER ;
Ideally, this would get me the result I need, where I need it. But, of course, I get Error Code 1336 saying "Dynamic SQL is not allowed in stored function or trigger"

You don't need dynamic SQL for this stored function. You don't need to use dynamic SQL for a LIMIT clause. You just need to make sure the variables are INT type, not strings.
Here's a quick demo:
create function foo(a int) returns int reads sql data
begin
return (select x from test limit 1 offset a);
end
Notice several other things:
Use READS SQL DATA instead of DETERMINISTIC. Your function is not deterministic. You should read the manual page on create function to understand these options better.
Don't use SELECT *. A function can only return a single scalar value, not a set of columns. The table you are querying might in fact have one column, but it's a good habit to make queries be more clear.
Using LIMIT with no ORDER BY may surprise you later, because it doesn't guarantee which order it will use for determining the offset. It's best if you use ORDER BY explicitly.
When using LIMIT, I think it's more clear to use LIMIT <count> OFFSET <offset> instead of LIMIT <offset>, <count>. They do the same thing, but it's easier to remember which argument is which.
Your LIMIT query appears to be selecting many rows. You need the query to select exactly one column and one row, or else it's not valid to return from a stored function.

Related

Select statement defined by parameter SQL

In SQL is there a way to grab the information in a table, but with the table name being specified by a function parameter?
Obviously the following doesn't work, but something along these lines maybe:
CREATE OR REPLACE FUNCTION select_table(table_name TEXT)
RETURNS TABLE (
"ID" TEXT
) AS
$$
SELECT * FROM uploads.<table_name>
$$
LANGUAGE SQL STABLE;
I'm a bit of a rookie when it comes to SQL, so would appreciate any guidance.
Any use of a variable in a query will be as if you had used a string literal, not an identifier.
To use a variable as an identifier, you would have to use dynamic SQL. That is, do string-concatenation of the table_name variable into a string which is your SELECT query, then PREPARE and EXECUTE that query.
But https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html says:
SQL syntax for prepared statements can be used within stored procedures, but not in stored functions or triggers.
This is because if a stored function is running, then by definition, your thread is already running a query. MySQL cannot do that.
You can write your function to do a large CASE statement to do a different fixed query depending on the input variable.
But as P.Salmon comments above, in MySQL you can't return a table from a stored function. Functions can only return a single scalar value, not a result set. So your SELECT would need to query one column and use INTO syntax to save it to a variable. Then return that variable.
Also current versions of MySQL have no "CREATE OR REPLACE FUNCTION" option. You can "CREATE FUNCTION."
DELIMITER //
CREATE FUNCTION select_table(table_name TEXT)
RETURNS TEXT READS SQL DATA
BEGIN
DECLARE result TEXT;
CASE table_name
WHEN 'mytable1' THEN SELECT col1 INTO result FROM mytable1;
WHEN 'mytable2' THEN SELECT col1 INTO result FROM mytable2;
WHEN 'mytable3' THEN SELECT col1 INTO result FROM mytable3;
END CASE;
RETURN result;
END//
DELIMITER ;

How to check if a particular value exists in a variable which stores multiple value in mysql?

I am trying to create an sql trigger statement using phpmyadmin trigger interface.
Trying to do something for table 1 as shown below :
BEGIN
declare #valid_number int ;
select id into #valid_number from table 2 ;
if 10 does not exist in #valid_number then
{do something here}
end if;
END
how to achieve it?
First: a variable in a stored routine can't store multiple values, just a single one. Your statement
select id into #valid_number from table 2 ;
will only work, if the query returns exactly one row. An error will occur, if the query returns multiple rows, a warning, if the query returns no row at all, see the manual page to SELECT ... INTO:
The INTO clause can name a list of one or more variables, which can be
user-defined variables, stored procedure or function parameters, or
stored program local variables. [...]
The selected values are assigned to the variables. The number of
variables must match the number of columns. The query should return a
single row. If the query returns no rows, a warning with error code
1329 occurs (No data), and the variable values remain unchanged. If
the query returns multiple rows, error 1172 occurs (Result consisted
of more than one row).
Solution:
It's not difficult to create a statement that gives you the desired answer in exact one row, i.e.
SELECT COUNT(*) into valid_number FROM example WHERE id = 10;
This query will return 0, if the id 10 does not exists in column id and the count of occurences else. Of course there are several ways to achieve this, this is just one of them. You could rewrite your stored routine to:
BEGIN
-- prefer local variables, don't use user defined, if not needed.
DECLARE valid_number int;
SELECT COUNT(*) into valid_number FROM example WHERE id = 10;
IF valid_number = 0 THEN
-- do something here
END IF;
SELECT result;
END
Note
You could use a cursor to traverse the result of a query, but most times one wants to avoid a cursor. To use a cursor under similar conditions as of this question would not be the SQL way to do it and most times very inefficient.

MySQL Stored Procedure with Dynamic Result LIMIT

I've got a stored procedure in my MySQL database, and need to figure out how to limit the return to create some pagination.
Some pseudocode:
CREATE PROCEDURE `my_procedure`(IN member_id INT, IN start INT, IN end INT)
BEGIN
SELECT * FROM member_activity WHERE `member_id` = member_id
<if start is not null>
LIMIT start, end
<endif>
END;
If I pass a null value, how do I simply unlimit the query?
Passing my_procedure(1,null,null) returns an error.
I know I can just wrap the entire query in an IF statement, but I'd rather not, because there's several other variables that would be annoying to keep in sync. Is it possible to accomplish this without writing the entire query twice?
Thanks
As mentioned in the manual:
To retrieve all rows from a certain offset up to the end of the result set, you can use some large number for the second parameter. This statement retrieves all rows from the 96th row to the last:
SELECT * FROM tbl LIMIT 95,18446744073709551615;
Since, as you point out, one cannot use the IFNULL() function within the LIMIT clause, prior to your SELECT command you could do:
SET `start` := IFNULL(`start`, 0);
SET `end` := IFNULL(`end` , 18446744073709551615);

Limiting selected rows count with a stored procedure parameter in MySQL

I have a procedure SelectProc which contains a SELECT statement. I want to add a procedure param LimitRowsCount and use it as following:
CREATE PROCEDURE SelectProc (IN LimitRowsCount INTEGER UNSIGNED)
BEGIN
SELECT (...)
LIMIT LimitRowsCount;
END
but this approach doesn't work.
The SELECT itself contains nested subqueries so I can't create view from it. Is there a way more proper than dynamic SQL (prepared statements)?
CREATE PROCEDURE SelectProc (IN LimitRowsCount INT)
BEGIN
SET #LimitRowsCount1=LimitRowsCount;
PREPARE STMT FROM "SELECT (...) LIMIT ?";
EXECUTE STMT USING #LimitRowsCount1;
END
From the manual:
The LIMIT clause can be used to constrain the number of rows
returned by the SELECT statement. LIMIT takes one or two numeric
arguments, which must both be nonnegative integer constants
(except when using prepared statements).
MySQL Manual - 12.2.8. SELECT Syntax
So that's a no - you cannot.

MySQL UPDATE and SELECT in one pass

I have a MySQL table of tasks to perform, each row having parameters for a single task.
There are many worker apps (possibly on different machines), performing tasks in a loop.
The apps access the database using MySQL's native C APIs.
In order to own a task, an app does something like that:
Generate a globally-unique id (for simplicity, let's say it is a number)
UPDATE tasks
SET guid = %d
WHERE guid = 0 LIMIT 1
SELECT params
FROM tasks
WHERE guid = %d
If the last query returns a row, we own it and have the parameters to run
Is there a way to achieve the same effect (i.e. 'own' a row and get its parameters) in a single call to the server?
try like this
UPDATE `lastid` SET `idnum` = (SELECT `id` FROM `history` ORDER BY `id` DESC LIMIT 1);
above code worked for me
You may create a procedure that does it:
CREATE PROCEDURE prc_get_task (in_guid BINARY(16), OUT out_params VARCHAR(200))
BEGIN
DECLARE task_id INT;
SELECT id, out_params
INTO task_id, out_params
FROM tasks
WHERE guid = 0
LIMIT 1
FOR UPDATE;
UPDATE task
SET guid = in_guid
WHERE id = task_id;
END;
BEGIN TRANSACTION;
CALL prc_get_task(#guid, #params);
COMMIT;
If you are looking for a single query then it can't happen. The UPDATE function specifically returns just the number of items that were updated. Similarly, the SELECT function doesn't alter a table, only return values.
Using a procedure will indeed turn it into a single function and it can be handy if locking is a concern for you. If your biggest concern is network traffic (ie: passing too many queries) then use the procedure. If you concern is server overload (ie: the DB is working too hard) then the extra overhead of a procedure could make things worse.
I have the exact same issue. We ended up using PostreSQL instead, and UPDATE ... RETURNING:
The optional RETURNING clause causes UPDATE to compute and return value(s) based on each row actually updated. Any expression using the table's columns, and/or columns of other tables mentioned in FROM, can be computed. The new (post-update) values of the table's columns are used. The syntax of the RETURNING list is identical to that of the output list of SELECT.
Example: UPDATE 'my_table' SET 'status' = 1 WHERE 'status' = 0 LIMIT 1 RETURNING *;
Or, in your case: UPDATE 'tasks' SET 'guid' = %d WHERE 'guid' = 0 LIMIT 1 RETURNING 'params';
Sorry, I know this doesn't answer the question with MySQL, and it might not be easy to just switch to PostgreSQL, but it's the best way we've found to do it. Even 6 years later, MySQL still doesn't support UPDATE ... RETURNING. It might be added at some point in the future, but for now MariaDB only has it for DELETE statements.
Edit: There is a task (low priority) to add UPDATE ... RETURNING support to MariaDB.
I don't know about the single call part, but what you're describing is a lock. Locks are an essential element of relational databases.
I don't know the specifics of locking a row, reading it, and then updating it in MySQL, but with a bit of reading of the mysql lock documentation you could do all kinds of lock-based manipulations.
The postgres documenation of locks has a great example describing exactly what you want to do: lock the table, read the table, modify the table.
UPDATE tasks
SET guid = %d, params = #params := params
WHERE guid = 0 LIMIT 1;
It will return 1 or 0, depending on whether the values were effectively changed.
SELECT #params AS params;
This one just selects the variable from the connection.
From: here