MySQL stored procedure with OUT parameter - mysql

I keep getting a null value when using an out parameter.
CREATE PROCEDURE `getTotalPoints`(IN `uEmail` VARCHAR(255), OUT `iTotal` DECIMAL(5,2) UNSIGNED)
begin
select
coalesce(
coalesce(sum(points_earned),0) -
coalesce(sum(points_lost),0) +
coalesce(sum(points_taken),0) -
coalesce(sum(points_traded),0),0)
from user_activities
where user_email=#uEmail into #iTotal;
end
I know the select statement works fine as without the out parameter, I get 0.00 as a result.
I'm sure it's something simple, but everything I've researched and tried has resulted in the same NULL return value.

Remove the "#" symbols on the variable references.
"#uEmail" and "#iTotal" are references to user-defined variables, not the procedure arguments.
You want to reference the procedure arguments as "uEmail" and "iTotal", without the "#".
Or, you could probably get your statement to work if you did the necessary assignments to and from the user-defined variables, something like this in the body of the procedure:
-- set user-defined variable to value from procedure argument
SET #uEmail = uEmail;
-- statement references user-defined variables
SELECT ...#uEmail ... INTO #iTotal;
-- set procedure OUT argument from user-defined variable
SET iTotal = #iTotal;

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 Stored Procedure with multiple optional parameters

i am trying to make a stored procedure that has multiple optionals parameters, i have tried with IF statement but i can't make it work and this is the last code i have tried.
I want to filter by "Usuario" if it is not null
I'm giving "Usuario" as an example, but it will have others filters like it.
CREATE DEFINER=`conciliacion`#`%` PROCEDURE `BuscarRRNormal`(IN `FechaDesde` DATE, IN `FechaHasta` DATE, IN `Usuario` varchar(255))
BEGIN
SELECT IDS, Fecha_Recarga, Usuario, Monto, Operador
FROM transaccionesrr
WHERE (Fecha_Recarga BETWEEN FechaDesde AND FechaHasta)
AND (#Usuario IS NULL OR Usuario = #Usuario);
END
It's seems like mySQL doesn't support optional parameters but there could be a similar option?. Any help will be appreciated.
In MySQL, the variable #Usario is not the same object as the procedure IN parameter Usario. They are different variables. You cannot reference #Usario to get the value of the IN parameter Usario.
You should name your parameter something distinct from the column name you want to compare it to, then just use it in the query without a # character. For example, one could use a naming convention to prefix the parameter names with "p".
CREATE DEFINER=`conciliacion`#`%` PROCEDURE `BuscarRRNormal`(IN `pFechaDesde` DATE, IN `pFechaHasta` DATE, IN `pUsuario` varchar(255))
BEGIN
SELECT IDS, Fecha_Recarga, Usuario, Monto, Operador
FROM transaccionesrr
WHERE (Fecha_Recarga BETWEEN pFechaDesde AND pFechaHasta)
AND (pUsuario IS NULL OR Usuario = pUsuario);
END

MySQL Call procedure inside function for count

Is it possible to use a procedure inside a function? For example, I would like to gather all my rows related to an id but I would also like to count the rows and use it in a select statement. This is not working:
drop procedure if exists relatives;
create procedure relatives(in parent int(11),out counted int(11))
begin
set counted=(select count(*) from category where related=parent);
end;
drop function if exists relatives_count;
create function relatives_count(parent parent(11)) returns int(11)
begin
declare count int(11);
call relatives(parent,counted);
return counted;
end;
So that I can use the count
select relatives_count(id) from category
This is just for curiosity purposes. It may look senseless since I can just call a single select query and get the same results but I want to know how I can use my procedure out variable in a function.
Yes, a MySQL FUNCTION can call a MySQL PROCEDURE.
But... the operations the procedure performs will be limited to the operations allowed by a function. (We can't use a procedure to workaround the limitations placed on a function.)
"is not working" is so nebulously vague as to be practically useless in debugging the issue. What exact behavior is being observed?
My suspicion is that the SQL statements shown are failing, because there is no override for the default statement delimiter.
Also, parent(11) is not a valid datatype.
Be aware that when an identifier for a column in a SQL statement in a MySQL stored program matches an identifier used for an argument or local variable, MySQL follows a rule about which (the column name or the variable) that is being referenced.
Best practice is to adopt a naming convention for arguments and local variables that do not match column names, and to qualify all column references with a table name or table alias.
Personally, I use a prefix for arguments and local variables (a for argument, l for local, followed by a datatype i for integer, d for date/datetime, n for decimal, ...
DELIMITER $$
DROP PROCEDURE IF EXISTS relatives$$
CREATE PROCEDURE relatives(IN ai_parent INT(11),OUT ai_counted INT(11))
BEGIN
SELECT COUNT(*)
INTO ai_counted
FROM category c
WHERE c.related = ai_parent
;
END$$
DROP FUNCTION IF EXISTS relatives_count$$
CREATE FUNCTION relatives_count(ai_parent INT(11))
RETURNS INT(11)
BEGIN
DECLARE li_counted INT(11);
CALL relatives(ai_parent,li_counted);
RETURN li_counted;
END$$
DELIMITER ;
Please identify the exact behavior you observe. Error message when creating the procedure? Error message when executing the function? Unexpected behavior. That's much more precise and informative than telling us something "is not working".

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 ;

Simple sql function with SELECT

I try to understand how functions work. I can make the equivalent in procedure but I can't create a simple function with select.
element is UNIQUE and
thing is PRIMARY
CREATE DEFINER=`root`#`localhost`
FUNCTION `get_element_by_thing`(`thing` VARCHAR(255))
RETURNS VARCHAR(255)
CHARSET utf8
NOT DETERMINISTIC
READS SQL DATA
SQL SECURITY DEFINER
DECLARE #return_element VARCHAR(255);
SET #return_element = (
SELECT
`element`
FROM
`table1`
WHERE
`thing` = thing
);
RETURN #return_element;
I use the phpmyadmin interface.
1) Don't declare user-defined variables.
The name of a local variable in MySQL stored program does not start with an at sign #. As an example:
DECLARE stored_program_local_variable VARCHAR(255);
SET stored_program_local_variable = 'somevalue';
The name of a user-defined variables start with an at sign #. (The at sign character is what distinguishes user-defined variables from other identifiers.) It's not valid to declare a user-defined variable in a stored program. To create a user-defined variable, just assign a value to it. For example:
SET #user_defined_variable = 'somevalue';
2) If we don't need to persist variables beyond the scope of a stored program, we typically use local variables, which exist only for the duration of the stored program execution. (Which is different behavior than user-defined variables which are at the session level.)
3) Use the SELECT ... INTO syntax to retrieve scalar values into user-defined or local variables. https://dev.mysql.com/doc/refman/5.7/en/select-into.html
Try:
DELIMITER $$
CREATE DEFINER=`root`#`localhost`
FUNCTION `get_element_by_thing`(`thing` VARCHAR(255))
RETURNS VARCHAR(255)
...
BEGIN
DECLARE return_element VARCHAR(255) ;
SELECT t1.element
INTO return_element
FROM table1 t1
WHERE t1.thing = thing
LIMIT 1 ;
RETURN return_element ;
END $$
DELIMITER ;
Note: with ambiguous identifiers (i.e. routine parameter and column with the same name in a SQL statement, the routine parameter takes precedence over the column name. Qualify the column reference with the table name or table alias so it's not ambiguous. I prefer to assign routine parameters (and local variables) names that do not match column names.
If for some reason you need to assign a value to a user-defined variable in a SQL statement, you can use the := assignment operator. This is also valid outside the context of a stored program.
SELECT #user_defined_variable := t.somecolumn
FROM mytable t
WHERE somecondition
ORDER BY someexpression
LIMIT 1