Is MySQL's IF() broken? - mysql

MySQL (5.7.35) chokes with an error "Error Code: 3158. JSON documents may not contain NULL member names." on a query that can be simplified to look like
SELECT IF(false, JSON_OBJECTAGG(NULL, 'whatever'), 0) as x;
So I have to screen the NULL value to make it work:
SELECT IF(false, JSON_OBJECTAGG(IFNULL(NULL, 0), 'whatever'), 0) as x;
Which feels wrong as the expr2 in IF(false, expr2, expr3) is not supposed be even looked at by the engine, unless I'm missing something fundamental.
So my question is, why does MySQL's IF() evaluate/analyse expressions that are not meant to be executed?
Thanks
EDIT: to clarify, my actual code was
SELECT IF(cl.id IS NULL, NULL, JSON_OBJECTAGG(cl.id, cl.somethingelse))

Of cource it is supposed to be looked at and analysed for errors. What if someone else comes by later and sets the condition to true?
If nobody ever will, then you don't need a IF condition in the first place.

Related

Why can't NULL be converted to JSON's null in postgreSQL?

I can perform
SELECT to_json(1)
SELECT to_json(1.4)
SELECT to_json('this is a nice json text')
SELECT to_json('{"become":"json"}')
SELECT to_json('null')
And it all works properly, however when you perform:
SELECT to_json(NULL::TEXT)
You, in fact get the postgres builtin NULL, like if nothing really happened, when I was expecting the same result as to_json('null') for exaple SELECT to_json(someText)::TEXT FROM ... maybe, you'd expect "input", "stuff", "" and null but instead you'd get "input", "stuff", "" and
My question is, why SELECT to_json(NULL::TEXT) doesn't give you a json null, but instead just a NULL pointer? why was it implemented like that in postgres? some specific reasons?
The to_json function is marked as STRICT, which means, return NULL when any parameter is NULL. I'm not sure if this is intended, maybe it's a PostgreSQL bug.
Update: After discussing this on Postgres' mailing list, this is not a bug, but a feature - the situation is not as simple due to the fact that both languages support NULL, but the behavior of NULL is little bit different between these two languages. It's hard to decide whether an SQL NULL should be immediately transformed to a JSON NULL and lose its SQL behavior immediately. If you need different behavior, you can use an SQL function:
CREATE OR REPLACE FUNCTION to_json2(anyelement)
RETURNS json AS $$
SELECT COALESCE(to_json($1), json 'null')
$$ LANGUAGE sql;
Pavel Stehule's answer is great, and has led me to a simpler solution:
SELECT 'null'::json;

Is there a SQL mode for MySQL allowing "WHERE x = NULL" (meaning "WHERE x IS NULL")?

I know that using column = NULL in an SQL statement´s WHERE clause results in an empty result set.
As this is very inefficient in programming environments, I´m searching for a way to let MySQL interpret column = NULL like column IS NULL, maybe by setting an SQL mode?
You can use <=> instead. I wouldn't though. This is not portable.
(Thanks to Martin Smith who pointed me to this which was a solution to my problem)
You can have a case statement in your WHERE clause to use the correct syntax:
http://www.postgresql.org/docs/9.4/static/functions-conditional.html
SELECT * FROM my_items WHERE
CASE WHEN 1=1 THEN mac = '08:00:2b:01:02:03'
WHEN 1=2 THEN mac IS NULL
END;
Obviously, the first case (WHEN 1=1) will always be called in this example. You can put your own logic in that satisfies your condition.
Well, at least PostgreSQL does have a parameter for that
transform_null_equals = true
When is set to true, the query parser will transform
field = NULL
to
field is NULL
beforehand. Something tells me there's an equivalent parameter hidden in MySQL less documented params.
EDIT as of 2020-07-15
My "not really a solution" might be one of my first answers at SO. I'm somewhat ashamed for answering something about another DBRMS engine instead of what I was asked by the OP.
Anyway, the correct answer would be using the <=> operator as others have said in their answers already. You replace = for that and it will behave as other comparisons:
A = B // zero if different, one if equals, this is treated as a boolean result
A != NULL // always null, therefore not false nor true.
A <=> B // zero if different, one if equals, zero is one of the variables are null, 1 if both are.
This is not a setting and therefore it's behavior would never be session-wise or DDBB wise.

Dynamic Values in 'IN' operator

I have a select statement in which the WHERE clause's IN operator. The query works properly as long as some values are passed to question mark (passed from java program). But when there are no values passed, I get a syntax error.
select
this_.categoryAddressMapId as category1_1_0_,
this_.categoryId as categoryId1_0_,
this_.addressId as addressId1_0_
from
icapcheckmyphotos.category_address_map this_ <br>
where
this_.addressId in (
?
)
When there are no parameters passed, I need null set. Please suggest how to modify the where clause. Thanks in advance
Modify your java program. Your choices are to not run the query if there are no addressIds, or ensure that it passes at least one value. If addressId is an integer field, pass -1 or something like that. Personally, I like the first option but it depends on your requirements.
how about doing sometiong like
where (? is null) OR this_.addressId in ( ? )
you may need to add some special treatment to the first part of the OR if your columns does accept NULLS
//pseudo code
...
WHERE 1
if(!null) {
AND this_.addressId in ('stuff')
}

Subtracting the value from the last row using variable assignment in MySQL

According to the MySQL documentation:
As a general rule, you should never assign a value to a user variable and read the value within the same statement. You might get
the results you expect, but this is not guaranteed.
http://dev.mysql.com/doc/refman/5.6/en/user-variables.html
However, in the book High Perfomance MySQL there are a couple of examples of using this tactic to improve query performance anyway.
Is the following an anti-pattern and if so is there a better way to write the query while maintaining good performance?
set #last = null;
select tick, count-#last as delta, #last:=count from measurement;
For clarification, my goal is to find the difference between this row and the last. My table has a primary key on tick which is a datetime column.
Update:
After trying Shlomi's suggestion, I have reverted back to my original query. It turns out that using a case statement with aggregate functions produces unexpected behavior. See for example:
case when (#delta := (max(measurement.count) - #lastCount)) AND 0 then null
when (#lastCount := measurement.count) AND 0 then null
else #delta end
It appears that mysql evaluates the expressions that don't contain aggregate functions on a first pass through the results, and then evaluates the aggregate expressions on a second (grouping) pass. It appears to evaluate the case expression during or after that second pass and use the precalculated values from the first pass in that evaluation. The result is that the third line #delta is always the initial value of #delta (because assignment didn't happen until the grouping pass). I attempted to incorporate a group function into the line with #delta but couldn't get it to behave as expected. So I ultimately when back to my original query which didn't have this problem.
I would still love to hear any more suggestions about how to better handle a query like this.
Update 2:
Sorry for the lack of response on this question, I didn't have a chance to investigate further until now.
Using Shlomi's solution it looks like I had a problem because I was using a group by function when I read my #last variable but not when I set it. My code looked something like this:
CASE
WHEN (#delta := count - #last) IS NULL THEN NULL
WHEN (#last:= count ) IS NULL THEN NULL
ELSE (CASE WHEN cumulative THEN #delta ELSE avg(count) END)
END AS delta
MySQL appears to process expressions that don't contain aggregate functions in a first pass and ones that do in a second pass. The strange thing in the code above is that even when cumulative evaluates to true MySQL must see the AVG aggregate function in the ELSE clause and decides to evaluate the whole inner CASE expression in the second pass. Since #delta is set in an expression without an aggregate function it seems to be getting set on the first pass and by the time the second pass happens MySQL is done evaluating the lines that set #delta and #last.
Ultimately I seem to have found a fix by including aggregate functions in the first expressions as well. Something like this:
CASE
WHEN (#delta := max(count) - #last) IS NULL THEN NULL
WHEN (#last:= max(count) ) IS NULL THEN NULL
ELSE (CASE WHEN cumulative THEN #delta ELSE avg(count) END)
END AS delta
My understanding of what MySQL is doing is purely based on testing and conjecture since I didn't read the source code, but hopefully this will help others who might run into similar problems.
I am going to accept Shlomi's answer because it really is a good solution. Just be careful how you use aggregate functions.
I've researched this issue in depth, and wrote a few improvements on the above.
I offer a solution in this post, which uses functions whose order can be expected. Also consider my talk last year.
Constructs such as CASE and functions such as COALESCE have known underlying behavior (at least until this is changed, right?).
For example, a CASE clause inspects the WHEN conditions one by one, by order of definition.
Consider a rewrite of the original query:
select
tick,
CASE
WHEN (#delta := count-#last) IS NULL THEN NULL
WHEN (#last:=count ) IS NULL THEN NULL
ELSE #delta
END AS delta
from
measurement,
(select #last := 0) s_init
;
The CASE clause has three WHEN conditions. It executes them by order until it meets the first that succeeds. I've written them such that the first two will always fail. It therefore executes the first, then turns to execute the second, then finally returns the third. Always.
I thus overcome the problem of expecting order of evaluation, which is a real and true problem, mostly evident when you start adding more complex clauses such as GROUP BY, DISTINCT, ORDER BY and such.
As a final note, my solution differs from yours in the first row on the result set -- with yours' it returns NULL, with mine it returns the delta between 0 and count. Had I used NULL I would have needed to change the WHEN conditions in some other way -- making sure they would fail on NULL values.

NVL2 function does not exist? mysql query

I'm trying to do some queries but I keep getting errors, now I'm thinking that there is something wrong with the mysql installation. Can anybody tell me if there is an error in this query?
SELECT settings.ID,
settings.name,
settings.description,
NVL2(userSettings.value, userSettings.value, settings.default)
FROM settings
LEFT OUTER JOIN userSettings ON (settings.ID = userSettings.settingID)
The error I get says the function databaseX.NVL2 does not exist
I recommend staying away from vendor specific functions when a ANSI standard equivalent alternative is available. NVL and IFNULL for example can (often) be replaced with COALESCE.
You can also use CASE WHEN, which means a lot more typing on the downside, but the upside is that people with background in SQL Server for example won't have to deal with Oracles DECODE() or NVL or NVL2, because the logic is right there in the code.
That's probably because NVL2 is an Oracle function, not a MySQL function. I believe the function you are looking for in MySQL would be COALESCE()
As #Eric Petroelje mentioned NVL2() is Oracle function, not MySQL. However MySQL has its own equivalent that can be used in this case: IFNULL():
SELECT ... IFNULL(userSettings.value, settings.default) ...
After several try&error probes, I found this method to emulate Oracle's NVL2 function. It's not very elegant, but it works
SELECT IF(LENGTH(ISNULL(FieldName, '')) > 0, 'Not NULL Value', 'Null Value') FROM TableName
I think this can help you
IF(expr1,expr2,expr3)
If expr1 is TRUE (expr1 <> 0 and expr1 <> NULL) then IF() returns expr2; otherwise it returns expr3. IF() returns a numeric or string value, depending on the context in which it is used.
Alternatively you can substitute NVL2 to:
IF (userSettings.value IS NULL, userSettings.value, settings.default)