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.
Related
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 ;
I have a stored procedure which function to retrieve the ColumnProperty based on the input parameter matching the ColumnRule.
For example,
I will execute the below to get the ColumnProperty as string(short). But the problem is when using WHERE to filter ColumnRule, the retrieved value is string and get unknown type at the end.
CALL `testing-spGetColumnType`('varchar(11)', #outputProperty);
select #outputProperty;
DELIMITER $$
CREATE PROCEDURE `testing-spGetColumnType`(IN pColumnType varchar(50),OUT pColumnProperty varchar(50))
BEGIN
SELECT ColumnProperty FROM model_column_type where pColumnType like replace(ColumnRule, 'variable', pColumnType) into pColumnProperty;
SELECT IFNULL(pColumnProperty,'unknown type') into pColumnProperty;
END$$
DELIMITER ;
The sample table:
when the condition in the WHERE clause is evaluated against the stringShort row, given 'varchar(11)' as the argument, it's equivalent to
WHERE 'varchar(11)'
LIKE '%varchar% and SUBSTRING_INDEX(SUBSTRING_INDEX(varchar(11),''('',-1),'')'',1) < 50'
and the result of the LIKE comparison will be FALSE.
The value of ColumnRule from that row
%varchar% and SUBSTRING_INDEX(SUBSTRING_INDEX(variable,'(',-1),')',1) < 50
is a value. It doesn't matter that it looks like SQL text. In the context of the SELECT statement, it is just a string of characters. It's a string value. It is not references to identifiers, or SQL functions, or boolean operators.
We would get the same result if the ColumnRule value was
%varchar% one two buckle my shoe
To get a string value to be seen as SQL text, we can use dynamically prepared SQL.
In the context of MySQL PROCEDURE, we can dynamically create SQL text and store it as a string, and then execute the string, something like this:
SET #foo = ' foo, bar' ;
SET #sql = CONCAT('SELECT ',#foo,' FROM mytab',' ORDER BY ',#foo);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
A word of caution: if we dynamically create SQL text, and incorporate potentially unsafe values, we can open a gaping SQL Injection vulnerability, ala Little Bobby Tables https://xkcd.com/327/
MySQL Reference https://dev.mysql.com/doc/refman/8.0/en/sql-syntax-prepared-statements.html
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.
I'm work with store procedures, supose that I've the following procedure that return a value, and this value I use in other query.
CREATE PROCEUDRE filter(IN string varchar(1000), OUT salida varchar(1000))
BEGIN
.....
END
And I want make a insert with a select query for example:
INSERT INTO otherTable
SELECT filter(concat_group(column)) , value1,value2 from mytable
GROUP BY column,value,value2;
which is the correct way to do this?
Generally, you cannot call a stored procedure in the SQL select statement. What you want is like custom scalar functions.
reference
mysql scalar function with for loop
I have a stored procedure that is similar to below
SELECT *
FROM Table1
WHERE Tag IN (ids)
here Tag is an Integer column.
I tired to pass in comma separated values as string into the stored procedure but it does not work. Then I used stored procedure like below
SELECT *
FROM Table1
WHERE FIND_IN_SET(Tag, ids)
This works very well, the only problem is my table is very big - millions of rows and using FIND_IN_SET takes too long compared to IN when running a direct SQL statement.
What would be the best performance optimized option to use?
Is there a split function that can convert the ids into integer and parse it ready for IN clause? I think that would be the best option. Any suggestions or ideas?
You can prepare a statement and then execute it:
set #sql = concat('select * from table1 where tag in (', ids, ')');
PREPARE q FROM #sql;
execute q;
This constructs the string for each execution, so you can use in. The resulting execute should be able to use an index on tag, which should speed things up considerably.