Stored function returns NULL Mysql - mysql

i make this simple STORED FUNCTION in MySql that returns emails sparated with comma:
CREATE FUNCTION get_participantes (id INT) RETURNS VARCHAR(50)
BEGIN
DECLARE par VARCHAR(50) DEFAULT "";
(select GROUP_CONCAT(mail SEPARATOR ',') INTO #par from users where id IN (
select user_id from meeting_participants where meeting_id = id));
RETURN #par;
END
But when i call it using SELECT, i got NULL everytime. The SQL sentence works perfectly but itself.
+----------------------+
| get_participantes(5) |
+----------------------+
| NULL |
+----------------------+
Please help

The name of your function parameter is id, but you also have a column id in your users table, and maybe there's an id column in your meeting_participants table as well.
This creates an ambiguity. The expression doesn't know whether you mean the column id or the function parameter id.
You should give your function parameter a distinct name, to resolve the ambiguity.
CREATE FUNCTION get_participantes (_id INT) RETURNS VARCHAR(50)
READS SQL DATA
BEGIN
DECLARE par VARCHAR(50) DEFAULT "";
(select GROUP_CONCAT(mail SEPARATOR ',') INTO par from users where id IN (
select user_id from meeting_participants where meeting_id = _id));
RETURN par;
END
A couple of other issues that you should know about, even though they are not related to your question:
In MySQL routines (unlike SQL Server for example), your par local variable is not the same as the #var session variable. Your code in this specific case will work, but it's possible some other function you write will be confused. See also my answer to "#" symbol in stored procedure?
You should add the clause READS SQL DATA to your function, or else you may get this error (as I did when I tested your function):
ERROR 1418 (HY000): 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)

Related

MySQL Aggregate GROUP_CONCAT and JSON_ARRAYAGG functions returning duplicate values inside stored procedure

Aggregate functions are working for my on command line and in my SQL client, but when I run those very same aggregate functions for use in setting a variable inside a stored procedure, I get duplicate values - the same number of values for multiple rows, but all values are the same.
Say I have a table `table_name`:
|test_field | target_field|
|----------------:|:------------|
|"test_value" |1 |
|"test_value" |2 |
|"not_test_value" |3 |
|"test_value" |4 |
The aggregate function works as expected in regular SQL query:
SET #array_value := "";
SELECT GROUP_CONCAT(target_field) INTO #array_value
FROM `table_name`
WHERE `test_field` = 'test_value';
Where I get the result:
"1,2,4"
However, when I use same syntax inside a stored procedure, I get very different results:
"4,4,4"
Note that this stored procedure is triggered on an update of the same table:
CREATE TRIGGER `cacheAggregate`
AFTER INSERT ON `table_name`
FOR EACH ROW
BEGIN
CALL storedProcedureName (
NEW.target_field
);
END
...which calls the following stored procedure:
CREATE PROCEDURE `storedProcedureName `(
IN `target_field` VARCHAR
)
BEGIN
SET #answer_array := '';
SELECT GROUP_CONCAT(target_field) INTO #answer_array
FROM `table_name`
WHERE `test_field` = "test_value";
INSERT INTO CACHE_TABLE (`answers_array`, `fk_target_field`)
VALUES(#answer_array, target_field);
END
When I insert a value into the table, the trigger fires, calls the stored procedure, but the issue presents itself - #answer_array is wrong, consisting of a an array of the correct length, where each value is always the last value entered into the table.
IE, If I run this query:
INSERT INTO `table_name` (`test_field`,`target_field`) VALUES ("test_value", 5);
I would expect it to be stored as:
"1,2,4,5"
However, what is actually being returned is:
"5,5,5,5"
My guess is that the stored procedure is using some kind of cursor/pointer which breaks the aggregate function, but I had trouble finding anyone who had the same issue.
UPDATE/RESOLVED
While trying to reproduce this with the modified example code, I found my issue.
I'm leaving this here in case it helps anyone out in the future.
The issue was a naming collision. The parameter name for my stored procedure was the same as the target_field of my table, so the SELECT statement was using the stored procedure parameter as opposed to the table field. Since the value passed to the stored procedure the last updated value sent by the trigger, it was replicated the number of the resultant rows.
It would be the same if I used a literal in a select statement that returned multiple rows, EG:
SELECT GROUP_CONCAT("String Literal") INTO #answer_array
FROM `table_name`
WHERE `test_field` = "test_value";
...would produce the result:
"String Literal,String Literal,String Literal,String Literal"
The solution was to either explicitly set the table name in the GROUP_CONCAT argument:
SELECT GROUP_CONCAT(tn.target_field) INTO #answer_array
FROM `table_name` tn
WHERE `test_field` = "test_value";
-or-
...you could just change the name of the passed IN parameter in the stored procedure:
CREATE PROCEDURE `storedProcedureName `(
IN `sp_target_field` VARCHAR
)
...

MySQL variable name conflicts with field name

I am writing a stored procedure for MySQL which needs to have an input parameter that has the same name as a field in a table. I know that I can refer to the field as tableName.fieldName. But how can I refer to the variable in a non-ambiguous way when querying that table within the procedure?
Table files
+----+---------+---------+
| id | ownerId | content |
+----+---------+---------+
CREATE PROCEDURE getFilesOfOwner(IN ownerId INT)
BEGIN
SELECT * FROM files WHERE files.ownerId = ___________ <- ?
...
If the name of a procedure argument is the same as a field name of a table, using that identifier in a query to that table will be interpreted by MySQL as a reference to the variable, i.e. no qualifier is formally needed, however this is a vendor-specific conflict resolution only.
The solution for keeping the argument name the same is to declare an alias variable within the scope of the procedure, but expose it to the caller under the desired name:
CREATE PROCEDURE getFilesOfOwner(IN ownerId INT)
BEGIN
DECLARE _ownerId INT DEFAULT ownerId;
SELECT * FROM files WHERE files.ownerId = _ownerId
...
This requires one additional line of code, but it keeps the procedure signature clean from implementation-specific details.

Creating MySQL Function

I am trying to write a function that looks for a value assigned to its configuration in a parent-child tree, if the value is null or empty it looks one level up for the value.
I am currently getting syntax errors when trying to create the function.
This is what i have so far,
DELIMITER //
CREATE FUNCTION `db`.`Configuration`(
`ColumnName` VARCHAR(128),
`CID` INT
)
RETURNS VARCHAR(256)
NOT DETERMINISTIC
BEGIN
DECLARE Config VARCHAR(256) DEFAULT NULL;
DECLARE Parent INT;
WHILE (#Config IS NULL OR #Config = "") DO
SELECT #ColumnName INTO #Config, `ParentID` INTO #Parent FROM `Table` WHERE `ID`=#CID;
END WHILE;
RETURN CONCAT(#Config, '::', #Parent);
END ;
//
DELIMITER ;
I am getting the following error when I try to add the function:
1327 - Undeclared variable: ParentID
Any help would be greatly appreciated!
You receive the error message in the question because you have multiple into clauses, whereas according to mysql manual on select ... into ... you can only have one. So, to get rid of this specific error message you nee to rewrite your select statement as:
SELECT #ColumnName, `ParentID` INTO #Config, #Parent FROM `Table` WHERE `ID`=#CID;
However, there are some further issues with your code:
varname and #varname do not refer to the same variable. The first one is either a function / stored proc parameter or local variable, while the 2nd one is a user-defined variable. In your code you must remove the # from the variable names.
You cannot use a variable in place of a field name in an sql statement. You must use dynamic sql with prepared statements to achieve this. See the following SO question on how to this: How To have Dynamic SQL in MySQL Stored Procedure
You do not overwrite CID parameter in your while loop. This means that if the first iteration the configuration will remain null, then you have an infinite loop. You should change the value of CID in your loop.
I cannot guarantee that there are no further errors in your code.
There are a few problems with your function:
You are using SELECT...INTO incorrectly. When selecting multiple values you should only use INTO once. For example SELECT a,b into #a,#b FROM...
You are using user-defined variables with similar names to your function parameters, but they are not the same thing. In your code CID and #CID are different. I suggest using standard naming prefixes to clarify this: for example use p_ for function parameters and v_ for local function variables. You shouldn't need to use user-defined variables at all.
Your WHILE loop is bound to lead to infinite loops since the query criteria never changes. If it returns NULL or empty string once, it will keep returning them forever.
Here's a quick rewrite to address the above issues. I'll leave it to you to implement the WHILE loop correctly:
DELIMITER //
CREATE FUNCTION `db`.`Configuration`(
p_column_name VARCHAR(128),
p_id INT
)
RETURNS VARCHAR(256)
READS SQL DATA
BEGIN
DECLARE v_config VARCHAR(256) DEFAULT NULL;
DECLARE v_parent INT;
SELECT p_column_name,`ParentID`
INTO v_config, v_parent
FROM `Table`
WHERE `ID`=p_id;
RETURN CONCAT(v_config, '::', v_parent);
END ;
//
DELIMITER ;

MySql - Creating Stored Function and Calling It

I need help creating this particular stored function and call it by using the single select statement. Below are the questions with my answer. I think I got the first part right but I'm not sure. Any suggestions/advice? For the second question (part b), I'm calling the function incorrectly and can't get it to appear as specified in question/part b. Any advice? I would really appreciate the assistance.
Part A) Create a stored function called get_customer_balance which will return a customer’s balance from the membership table by passing in a membership number.
My Answer:
DELIMITER $$
CREATE FUNCTION get_customer_balance (membershipNo INT)
RETURNS dec
DETERMINISTIC
BEGIN
DECLARE CustBal dec;
SET CustBal = 0;
SELECT balance INTO CustBal
FROM membership
WHERE membership_id = membershipNo;
RETURN CustBal;
END$$
DELIMITER ;
Part B question
Membership Table. This is the original table of the problem (for reference guide)
create table membership
( membership_id int primary key,
balance decimal(10,2) not null
);
insert membership(membership_id,balance) values (1,1),(102,11),(103,109.25);
select membership_id,format(get_customer_balance(membership_id),2) as theBalance
from membership
where membership_id=102;
+---------------+------------+
| membership_id | theBalance |
+---------------+------------+
| 102 | 11.00 |
+---------------+------------+
Mysql Manual page on Create Proc and Functions
Calling your user defined function (UDF) would be like calling any built-in function. Even if it involved joins on tables with aliases (which the above does not show).
A much better example would be one in which there are two tables. A membership table, and a transaction table that needs summed. But that wasn't your schema.
Would you like to see such a thing?

Stored Procedure has OUT parameter with datatype VARCHAR, would like to remove quotes of it to use it in IN clause for INT type

We have a stored procedure and we call it as
call sp_name(1, 2, #VARCHAR_TYPE_VARIABLE);
Next we are trying to use OUT parameter in a WHERE clause like
SELECT COUNT(*) FROM tbl1 WHERE FIELD_OF_TYPE_INT IN(#VARCHAR_TYPE_VARIABLE);
But the problem is #VARCHAR_TYPE_VARIABLE has value like '1,2,3' (Quoted) and but my WHERE clause expect as WHERE IN (1,2,3);
My question is: how can i remove the quotes of returned VARCHAR OUT parameter to make it run for INT TYPE in WHERE IN clause.
This shows the treatment of an IN variable, not an out, as I was a little confused by the OUT part of what you are saying. Meaning, have an IN for an IN. If you need an OUT, give that separately. In my mind.
If you need to use other parameters inside your prepared statement, see the reference SQL Syntax for Prepared Statements halfway down that page around the area EXECUTE stmt2 USING #a, #b;.
So to be clear, parameters like #a above are great for normal parameters one would like plugged into prepared statements (like in PHP or JAVA). The trick with yours is that lists don't work well in that fashion, and require the prepared statement string to be constructed with concat. One can mix and match the two. Meaning you can put one together that requires a concat in the part that is required, as well as execute it USING ... with the variables that don't require concat.
But here is yours.
Schema
create table thingsCoveted
( id int auto_increment primary key,
thing varchar(100) not null
);
insert thingsCoveted(thing) values ('Fritos'),('crackers'),('gold');
Stored Proc
drop procedure if exists sp_name1;
DELIMITER $$
CREATE procedure sp_name1 (
IN theList varchar(100) -- comes in like '1,3', need to strip off single quotes
)
BEGIN
-- declare preparedSql varchar(100);
declare theSize int;
set theSize=length(theList);
set #preparedSql=concat("select * from thingsCoveted where id in (",substring(theList,2,theSize-2),")");
-- select #preparedSql; -- left here for debug view if you un-rem it
PREPARE stmt FROM #preparedSql;
execute stmt;
END $$
DELIMITER ;
Test It
set #var1="'1,3'";
call sp_name1(#var1);
+----+--------+
| id | thing |
+----+--------+
| 1 | Fritos |
| 3 | gold |
+----+--------+