Setting variables in SELECT statement mysql - mysql

Hi I'd like to know if it's OK to assume that the SELECT statement selects the fields from left to right to make sure that it is safe to construct a statement as the following (it wouldn't be nice to try to use a variable that has't been set):
SELECT #variable := IFNULL(var, 0), value + #variable, somevalue, value2*#variable, ..., #variable/whatever...
FROM table1 LEFT JOIN table2 LEFT JOIN table3
The reason I'm asking the question is because it is possible that var equals null in some of the tuples due the use of the LEFT JOINS (let's suppose that var comes from table 3), and I need to use the variable several times to make derivative fields as you can see, so I don't want to use IFNULL(var, 0) every time.

From the docs (with my emphasis):
As a general rule, other than in SET statements, you should never
assign a value to a user variable and read the value within the same
statement. For example, to increment a variable, this is okay:
SET #a = #a + 1;
For other statements, such as SELECT, you might get the results you
expect, but this is not guaranteed. In the following statement, you
might think that MySQL will evaluate #a first and then do an
assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for expressions involving user
variables is undefined.
So, if we go by the book, the answer to your question is NO.

Related

Where to write FROM in IF?

I've already written the function, I'm trying to use FROM within an IF statement. The code below is written within a function.
IF myParameter IN (SELECT id FROM myTable) AND myTable.myType='Type A' THEN
Of course, it gives an error
Unknown table 'myTable'
when trying to execute the function.
EDIT: myParameter is an INT
Is this what you want?
IF myParameter IN (SELECT id FROM myTable WHERE myTable.myType = 'Type A') THEN
That is, move the condition to the subquery.
Typically, I'd use EXISTS for this kind of check:
IF EXISTS (SELECT * FROM myTable WHERE id = myParameter AND myType='Type A') THEN
EXISTS will stop processing at the first match, and despite requiring a SELECT for syntax reasons, doesn't actually create an intermediate result; IN generally processes the entire query within it, formulates a result set, and then checks the left hand argument of the IN against potentially everything in that result (it probably stops at the first match two, but it still had to assemble the entire result).
If myparameter is filtering on the table's primary key, it probably will not make much of a difference in this case; but it something to keep in mind in the more general sense.

Assigning query's results into variable inside MySQL procedure

Here is the piece of sql from my procedure:
....
SET #list := (
SELECT
`i`.`id`
FROM
`Item` AS `i`
Order by RAND()
LIMIT 10
);
RETURN CONCAT_WS( '-', #list );
Inside procedure, I need to set query's results (yes, query returns multiple rows as a result) into some variable.
Then as a second variable, I need to concatenate previous results into one string.
But when I do it, I get following error:
Sub-query returns more than 1 row
So the question is, what am I doing wrong?
By the way, I know about group_concat. In second part of procedure, there is a requirement for checking if some id exists on this #list variable: find_in_set(item_id, #list )
And the query returns random results every time when I call it.
That's why, calling sub-query 2 times: 1 time as group concat string, second time just as list of results is not a solution for me. So, I need them as a set stored in variable.
You are approaching this is exactly the wrong way. There is no reason to store the ids in a list.
In the second part of the procedure, you should just be using a subquery:
where exists (select 1
from item i
where i.id = <outer table>.item_id
)
If you really did want to put things into a list, you would use group_concat() as you allude to:
SELECT #list := GROUP_CONCAT(i.id ORDER BY RAND() SEPARATOR '-') as ids
FROM Item i;
I don't see a use for storing the value in a variable, nor for ordering the ids randomly.
So the question is, what am I doing wrong?
What you're doing wrong is trying to store a result set in a user variable.
https://dev.mysql.com/doc/refman/5.7/en/user-variables.html says:
User variables can be assigned a value from a limited set of data types: integer, decimal, floating-point, binary or nonbinary string, or NULL value.
This kind of variable cannot store an array or a result set. Only one scalar value.
This question has code that seems to be related to Checking and preventing similar strings while insertion in MySQL
You should try following solution to overcome the issue of storing large data, it might help others too looking for solution:
# sets the default 1Mb storage limit to 10Mb
SET SESSION group_concat_max_len = (1024*10);
SET #list := (SELECT GROUP_CONCAT(id) FROM `Item` AS `i` Order by RAND() LIMIT 10);

changing the HAVING changes the column values

So I have a query that, when ran, has, as the values in the last column, 1, 4 and 8. But when I change the HAVING condition those values become 1, 3 and 5. This doesn't make any sense to me.
Here's my SQL:
SELECT memberId, #temp:=total AS total, #runningTotal as runningTotal, #runningTotal:=#temp+#runningTotal AS newRunningTotal
FROM (
SELECT 1 AS memberId, 1 AS total UNION
SELECT 2, 2 UNION
SELECT 3, 2
) AS temp
JOIN (SELECT #temp:=0) AS temp2
JOIN (SELECT #runningTotal:=0) AS temp3
HAVING newRunningTotal <= 40;
Here's the SQL fiddle:
http://sqlfiddle.com/#!2/d41d8/27761/0
If I change newRunningTotal to runningTotal I get different numbers in the runningTotal and newRunningTotal. This doesn't make any sense to me.
Here's the SQL fiddle for the changed query:
http://sqlfiddle.com/#!2/d41d8/27762/0
Any ideas?
Thanks!
MySQL documentation is quite explicit against doing what you are doing in the select:
As a general rule, other than in SET statements, you should never
assign a value to a user variable and read the value within the same
statement. For example, to increment a variable, this is okay:
SET #a = #a + 1;
For other statements, such as SELECT, you might get the results you
expect, but this is not guaranteed. In the following statement, you
might think that MySQL will evaluate #a first and then do an
assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for
expressions involving user variables is undefined.
I think you have found a situation where it makes a difference.
use this sqlFiddle to specify less than 40,
or this sqlFiddle also to specify less than 40.
I think what's happening is HAVING is applied after everything is done, but because you have variables in your select, they get calculated again giving you no control.

MySQL Dynamic HAVING Clause

I've written SELECT statement that works perfectly. However, I need to make some changes so that it now gets user submitted information from a form and returns results from the database based on that.
It will essentially be a product search—if a user searches for "red" they'll get back all items that are red. If they search for "red" and "wood" they'll get back only the items that are red and made of wood.
Here is my HAVING clause:
HAVING values LIKE "%Red%" AND values LIKE "%Wood%"
If I had a series of 5 drop down menus, each with a different set of terms, should I try to build the HAVING clause dynamically based on what drop downs the user uses? How would this be done?
I would opt for a static SQL statement, given that there are exactly five "options" the user can select from.
What I would do is use an empty string as the value that represents "no restriction" for a particular option, so my statement would be like this: e.g.
HAVING `values` LIKE CONCAT('%',:b1,'%')
AND `values` LIKE CONCAT('%',:b2,'%')
AND `values` LIKE CONCAT('%',:b3,'%')
AND `values` LIKE CONCAT('%',:b4,'%')
AND `values` LIKE CONCAT('%',:b5,'%')
To apply restrictions only on options 1 and 2, and no restriction on option 3, e.g.
$sth->bind_param(':b1','Red');
$sth->bind_param(':b2','Wood');
$sth->bind_param(':b3','');
$sth->bind_param(':b4','');
$sth->bind_param(':b5','');
To apply restriction only on option 2, e.g.
$sth->bind_param(':b1','');
$sth->bind_param(':b2','Wood');
$sth->bind_param(':b3','');
$sth->bind_param(':b4','');
$sth->bind_param(':b5','');
This allows you to have a static statement, and the only thing that needs to change is the values supplied for the bind parameters.
It's also possible to use a NULL value to represent "no restriction" for an option, but you'd need to modify the SQL statement to do a "no restriction" when a NULL value is supplied. Either check for the NULL value specifically, e.g.
HAVING ( `values` LIKE CONCAT('%',:b1,'%') OR :b1 IS NULL )
AND ( `values` LIKE CONCAT('%',:b2,'%') OR :b2 IS NULL )
AND ( `values` LIKE CONCAT('%',:b3,'%') OR :b3 IS NULL )
-or- just convert the NULL to an empty string for use in the LIKE predicate, e.g.
HAVING `values` LIKE CONCAT('%',IFNULL(:b1,''),'%')
AND `values` LIKE CONCAT('%',IFNULL(:b2,''),'%')
AND `values` LIKE CONCAT('%',IFNULL(:b3,''),'%')
The same technique will work with a WHERE clause as well. You may want to consider whether a WHERE clause is more appropriate in your case. (We can't tell from the information provided.)
But note that the HAVING clause does not restrict which rows are included in the statement; rather, the HAVING clause only restricts which rows from the result set are returned. The HAVING clause is applied nearly last in the execution plan (I think its followed only by the ORDER BY and LIMIT.
The HAVING clause can apply to aggregates, which the WHERE clause cannot do. The HAVING clause can reference columns in the SELECT list, which the WHERE clause cannot do.
(Also note that VALUES is a reserved word, if it's not qualified (preceded by alias., it may need to be enclosed in backticks.)
No, you should not build the HAVING clause.
Yes, you could build the WHERE clause as you suggested.
HAVING is used for imposing conditions involving aggregation functions on groups.
This approach make sense for where more conditions are involved, but you can use them on 5 as well. Collect your menu values into a temp table and then inner join them to your query on LIKE condition
SELECT .... FROM MyTable
INNER JOIN MyTempTable
ON values LIKE '%' + MenuValue '%'

MySQL: Set variable and use in where clause in same query

I'm having some trouble setting a user variable in MySQL and using it in the same query. I found one or two other questions similar to this, but couldn't get any of their solutions to work in my case.
Here's my query, stripped down a bit. I replaced values and names and stuff for simplicity, and removed some irrelevant parts, but left the basic structure for some context. Not really relevant but fyi I'm using CodeIgniter's active record class to build the query.
SELECT * FROM (things, (SELECT #exp_time := IF(5 < 10, X, Y) as var))
JOIN more_things ON ...
WHERE ...
AND #exp_time < UNIX_TIMESTAMP()
AND `#exp_time` > 1319180316
ORDER BY ...
LIMIT 1 ...
The error I'm getting is: "Every derived table must have its own alias."
Would really appreciate any assistance. Thanks!
Your actual error is because (as the error message explains) you haven't specified an alias for your derived table, and this is required in MySQL (even if you never use the alias anywhere else in your query).
SELECT *
FROM
(
things,
(SELECT #exp_time := IF(5 < 10, X, Y) as var) AS your_alias
)
...
However you don't need variables here. Now that your derived table actually has a name you have a way to refer to your value without needing to store it an a variable.