MySQL query assignment Error - mysql

When I following query
Select
case
when (#nume=Start_Date) then null
when (#nume!=Start_Date) then #num
End,
case
when (#nume=Start_Date and #nume:=End_Date) then null
when (#nume!=Start_Date) then #nume
End,
case
when (#nume!=Start_Date and #nume:=End_Date and #num:=Start_Date) then #nume
End
from P,(Select #num:=(Select Start_Date from P order by Start_Date desc Limit 1),#nume:=(Select End_Date from P order by Start_Date desc Limit 1 )) as A
;
In the next turn it assigns integer 1 value to #nume, rather than Date as it is supposed to.

"as it is supposed to". Hmmm, the documentation explicitly states:
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.
Your interpretation of what your code is supposed to do is in direct contradiction to the documentation. I would suggest that you ask another question, describing the problem you are trying to solve, and including sample data and desired results.

Related

DENSE_RANK() OVER and IFNULL()

Let's say I have a table like this -
id
number
1
1
2
1
3
1
I want to return the second largest number, and if there isn't, return NULL instead. In this case, since all the numbers in the table are the same, there isn't the second largest number, so it should return NULL.
These codes work -
SELECT IFNULL((
SELECT number
FROM (SELECT *, DENSE_RANK() OVER(ORDER BY number DESC) AS ranking
FROM test) r
WHERE ranking = 2), NULL) AS SecondHighestNumber;
However, after I changed the order of the query, it doesn't work anymore -
SELECT IFNULL(number, NULL) AS SecondHighestNumber
FROM (SELECT *, DENSE_RANK() OVER(ORDER BY number DESC) AS ranking
FROM test) r
WHERE ranking = 2;
It returns blank instead of NULL. Why?
Explanation
This is something of a byproduct of the way you are using subquery in your SELECT clause, and really without a FROM clause.
It is easy to see with a very simple example. We create an empty table. Then we select from it where id = 1 (no results as expected).
CREATE TABLE #foo (id int)
SELECT * FROM #foo WHERE id = 1; -- Empty results
But now if we take a left turn and turn that into a subquery in the select statement - we get a result!
CREATE TABLE #foo (id int)
SELECT (SELECT * FROM #foo WHERE id = 1) AS wtf; -- one record in results with value NULL
I'm not sure what else we could ask our sql engine to do for us - perhaps cough up an error and say I can't do this? Maybe return no results? We are telling it to select an empty result set as a value in the SELECT clause, in a query that doesn't have any FROM clause (personally I would like SQL to cough up and error and say I can't do this ... but it's not my call).
I hope someone else can explain this better, more accurately or technically - or even just give a name to this behavior. But in a nutshell there it is.
tldr;
So your first query has SELECT clause with an IFNULL function in it that uses a subquery ... and otherwise is a SELECT without a FROM. So this is a little weird but does what you want, as shown above. On the other hand, your second query is "normal" sql that selects from a table, filters the results, and lets you know it found nothing -- which might not be what you want but I think actually makes more sense ;)
Footnote: my "sql" here is T-SQL, but I believe this simple example would work the same in MySQL. And for what it's worth, I believe Oracle (back when I learned it years ago) actually would cough up errors here and say you can't have a SELECT clause with no FROM.

Can't Set User-defined Variable From MySQL to Excel With ODBC

I have a query that has a user-defined variable set on top of the main query. Its something like this.
set #from_date = '2019-10-01', #end_date = '2019-12-31';
SELECT * FROM myTable
WHERE create_time between #from_date AND #end_date;
It works just fine when I executed it in MySQL Workbench, but when I put it to Excel via MySQL ODBC it always shows an error like this.
I need that user-defined variable to works in Excel. What am I supposed to change in my query?
The ODBC connector is most likely communicating with MySQL via statements or prepared statements, one statement at a time, and user variables are not supported. A prepared statement would be one way you could bind your date literals. Another option here, given your example, would be to just inline the date literals:
SELECT *
FROM myTable
WHERE create_time >= '2019-10-01' AND create_time < '2020-01-01';
Side note: I expressed the check on the create_time, which seems to want to include the final quarter of 2019, using an inequality. The reason for this is that if create_time be a timestamp/datetime, then using BETWEEN with 31-December on the RHS would only include that day at midnight, at no time after it.
Use subquery for variables values init:
SELECT *
FROM myTable,
( SELECT #from_date := '2019-10-01', #end_date := '2019-12-31' ) init_vars
WHERE create_time between #from_date AND #end_date;
Pay attention:
SELECT is used, not SET;
Assinging operator := is used, = operator will be treated as comparing one in this case giving wrong result;
Alias (init_vars) may be any, but it is compulsory.
Variable is initialized once but may be used a lot of times.
If your query is complex (nested) the variables must be initialized in the most inner subquery. Or in the first CTE if DBMS version knows about CTEs. If the query is extremely complex, and you cannot determine what subquery is "the most inner", then look for execution plan for what table is scanned firstly, and use its subquery for variables init (but remember that execution plan may be altered in any moment).

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);

Setting variables in SELECT statement 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.

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.