After Googling for awhile I didn't see an answer. Anyway I have a situation in a stored procedure where I do a set select like:
SET someVariable = (SELECT ...)
Anyway, due to some redundant records existing somewhere else in the system, this SELECT query used in the SET returns more than one row. I'm guessing this will cause breakage or badness? True, false?
Thanks.
True. When assigning to a variable, the query must return a single row, containing a single column. You can also do it with this syntax:
SELECT someColumn INTO myVariable ... LIMIT 1;
Related
In Mysql, user variables are taking string values when declared inside select statement.
I have a table named 'a' with one column named 'amount' as shown below
amount
100
1000
1000
111115000000062760
111115000000062765
111115000000062770
111115000000062775
111115000000062780
Consider that I want to display null for amount values that are repeating in this table in the inserted order.
I've written the below query to achieve the same :
select
CASE
WHEN (#x != amount) THEN amount
END result,
CASE
WHEN ((#x) != amount) THEN #x:=amount
END dummy
from (select amount, #x:=0 x from a) q;
I'm creating a variable named '#x' in innermost query with default value as 0. When processing each row, I'm taking that row's 'amount' value and setting in the variable. Hence, in next row, I'll use the variable to hold previous row's data.
Using current and previous amount values, I'll only show the data that differ.
problem :
When I execute the query, I got the below output :
This is wrong as only 3rd row must have NULL values. 5th and 6th rows must have values. But, When I execute the query again, I got the below output :
This is the correct result. When subsequently executing the query, it always works fine. Only when it is executed for the first time after connecting to DB, this issue occurs.
Debugging :
I've tried setting #x's value before executing the query and it works fine. But, I want to know why it wouldn't work if I set it inside the select query.
I tried printing #x's values in the query and the only difference between the first query result and subsequent query results is alignment of #x's values. I think #x is considered as a string (as it is left aligned) in the first query. Hence, it breaks when comparing with certain bigint values.
When checking the documentation, it is mentioned that referring to a variable that has not been initialized will be taken as 'string'
If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.
I believe this is what's happening here. After first query, the variable probably got declared with 'integer' type which made it work in subsequent queryies.
Is it not possible to provide the variable's datatype inside select statement? Kindly advise.
No, to specify the type of your user defined variable you MUST declare it outside of the SELECT statement.
As you have already pointed out the MySQL documentation specifically mentions that you MUST declare the variable first:
9.4 User-Defined Variables
If you refer to a variable that has not been initialized, it has a value of NULL and a type of string.
Beginning with MySQL 8.0.22, a reference to a user variable in a prepared statement has its type determined when the statement is first prepared, and retains this type each time the statement is executed thereafter. Similarly, the type of a user variable employed in a statement within a stored procedure is determined the first time the stored procedure is invoked, and retains this type with each subsequent invocation.
This is very clear, either you formally declare the variable first, or it will be a string that is initialized with a value of NULL.
So just declare the variable first, you can declare variables within your inline SQL scripts, so don't try to fight it, either declare your variable first, or modify your query to use the variable as a string, either by casting amount to a string in the inner most query or cast #x to your numeric type in the comparison:
select
CASE
WHEN (CAST(#x as SIGNED) != amount) THEN amount
END result,
CASE
WHEN (CAST(#x as SIGNED) != amount) THEN #x:=amount
END dummy
from (select amount, #x:=0 x from a) q;
In my testing environment, I created the following procedure:-
CREATE DEFINER=`SC1`#`%` PROCEDURE `sc1_procedure_wipe_perms`(IN UserUUID char(36))
BEGIN
DELETE FROM subAccountPermissions WHERE `UserUUID` = UserUUID;
END
Innocent enough, right?
I call it from Java as follows:-
connection = ConnectionMgr.getConnectionToSc1();
rmStatement = connection.prepareStatement("CALL sc1_procedure_wipe_perms(?)");
rmStatement.setString(1, UserUUID);
rmStatement.execute();
It deletes everything. I am unsure what is going on here?
The problem is in the ambiguity between the procedure parameter UserUUID and the table column UserUUID. The MySQL compiler will ignore the parameter and take the table column for both sides of the condition, which means it will resolve to true for all rows.
You need to disambiguate the names, and use a different name for the procedure parameter, for example UserUUIDToDelete.
Please consider the following snippet of MySQL stored procedure code which works fine...
BEGIN
sponsor: loop
-- DO STUFF
select #sq := sq from a_nice_table where id = #a_nice_var;
-- DO MORE STUFF
end loop sponsor;
END
The line of code where I populate the variable 'sq' is the source of my question. This stored procedure code is being written to "DO STUFF" and NOT to be returning results. I need to populate the variable, but returning the #sq in a result set is undesired.
I am using Navicat to code with, and I get a result set returned for each loop iteration.
Is there a way to turn off result sets for stored procs? Is there also a way turn turn them back on mid-process? That would be useful for debugging purposes. Thank you.
This way of populating doesn't output result.
select sq into #sq from a_nice_table where id = #a_nice_var limit 1;
It works for multiple variables, too
select sq, whatever, one_more into #sq, #v_whatever, #v_one_more from a_nice_table where id = #a_nice_var limit 1;
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.
Read more about it here.
P.S.: Note, that I added LIMIT 1 to the queries. Otherwise an error would be thrown that the query returns more than one row. The difference to
select #sq := sq from a_nice_table where id = #a_nice_var;
is, that the #sq := sq is done for every row (if there are multiple), but in the end the variable will hold the value of the last row, not of all rows. So actually there's no difference, resultset-wise...
This question already has answers here:
How can I get the number of rows 'returned' from a result set of a stored procedure
(2 answers)
Closed 7 years ago.
I am trying to write a function to return the number of rows a call to a stored procedure would return. I'm trying to minimise repetition of the code (for reduced code maintenance/debugging- the procedure select is long).
The stored procedure just read-only selects rows matching certain criteria (vague I know but details should not be material to the question).
I could just copy the procedure into a function and change the select to count() but as it is long with multiple joins I was hoping to write a function that could call the procedure and return the row count. The goal is not for optimised running but for efficient code maintenance, boiler plate reduction.
I have tried this as a test:
DELIMITER //
CREATE PROCEDURE IF NOT EXISTS proc_select1()
BEGIN
SELECT 1;
END //
CREATE FUNCTION IF NOT EXISTS select1_count() RETURNS INT UNSIGNED
BEGIN
CALL proc_select1();
RETURN FOUND_ROWS();
END //
DELIMITER ;
However when I SELECT select1_count(); - which I am hoping will return 1 - I get the "cannot return a result set from a function" error.
I tried assigning FOUND_ROWS to a variable, clearing the result set then returning the variable value but can't get it to work.
Does anyone know a work around or do I really need to copy-paste the procedure and convert to a SELECT COUNT and function?
I'm using MySQL 5.5.16 (can upgrade if necessary), Windows 7 (nobody seems to want to upgrade :) with HeidiSQLv7.0.0.4053 (if relevant)
As always, any help much appreciated.
first use distinct to get distinct values then use count on that..like
select count(distinct column_name) from table_name
cannot return a result set from a function
This error happens when a SELECT query is done in a procedure without storing the output values.
Other thing : don't forget to use SQL_CALC_FOUND_ROWS to indicates your DBMS to store the number of found rows.
I tried to make it work without any temporary variable (Maybe there is exists a mysql keyword to not "return" the set of the SELECT query), but no success. Here a piece of code which works using temporary var.
CREATE PROCEDURE proc_select1()
BEGIN
DECLARE temp INT;
SELECT SQL_CALC_FOUND_ROWS 1 INTO temp;
END //
CREATE FUNCTION select1_count() RETURNS INT UNSIGNED
BEGIN
CALL proc_select1();
RETURN FOUND_ROWS();
END //
The result is 1 as expected :-) cf : SQLFiddle
It appears this is not possible. The select statement is required to SELECT INTO something to avoid the cannot return a result set from a function error during the function call. If this is not possible or required in the SELECT statement used in the procedure then the function will not run without error.
Using CLEAR QUERY CACHE or FLUSH QUERY CACHE after the procedure call did not help (and is probably a bad idea / bad coding anyway).
I have a trigger containing this:
SET v1 = CONCAT_WS(',',NEW.ID, NEW.Name, NEW.Type, NEW.Value);
Can this be simplified into something like this to include the entire new row?:
SET v1 = CONCAT_WS(',',NEW.*);
(I've tried variations of the above however they causes syntax errors)
Thanks
No, there's no easy way to do this. You have to reference each column.
The only real workaround is to use the table metadata to help you generate the statement you want, and then include that statement in your procedure.
You wouldn't want to do this dynamically in the TRIGGER, even if it were possible.
SELECT CONCAT('NEW.`',GROUP_CONCAT(c.column_name
ORDER BY ORDINAL_POSITION SEPARATOR '`,NEW.`'),'`')
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'mytable'
That should get you a string that looks like:
NEW.`ID`,NEW.`Name`,NEW.`Type`,NEW.`Value`
And you can paste that into your trigger body. (Of course you could extend the CONCAT to generate the whole line.)
The downside is that when new columns are added to the table, those won't get automatically included; that will require a change in the trigger. If a column gets dropped or renamed, your trigger will start throwing exceptions; again requiring a fix to the trigger.
UPDATE
Q:How can I convert this string into a MySQL query?
#query = "CONCAT_WS(',','CCC','64',NEW.Record,NEW.ID,NEW.Name,NEW.User_ID,NEW.State_Record,NEW.Hash);"
I wouldn't convert that to a query. I would just use that as static line of code (with no double quotes) in the body of your trigger, just like the original statement you had in your TRIGGER.
SET v1 = CONCAT_WS(',','CCC','64',NEW.Record,NEW.ID,NEW.Name,NEW.User_ID,NEW.State_Record,NEW.Hash);
(It wasn't clear what you intended to do with that string.)
If you are trying to create a SELECT statement, you could try removing that semicolon from the end of the string, and prepending a SELECT keyword on it. But I don't think the NEW. references to the column values of the current row will be recognized in that context. That might happen, but you'd need to test.
If I needed to do something like that, I would do it using user variables,
SET #new_id = NEW.ID;
SET #new_name = NEW.Name;
SELECT #new_id, #new_name
It's not at all clear to me what you are going to do with the result set returned by a query like. If you are attempting to audit changes to the table, the normative pattern is to run an INSERT of the column values into an changelog table,
INSERT INTO mytable_changelog (ID, Name) VALUES (NEW.ID, NEW.Name);
It really depends on what you are trying to accomplish.