Weird behaviour from using CASE/WHEN in MySQL - mysql

I'm trying to learn how to use CASE/WHEN in SQL more specifically, mySQL.
I have two tables: adhoc_item and adhoc_vulnerability. ahdoc_item has one adhoc_vulnerability related to it. now adhoc_vulnerability may or may not have a vulnerability (original_vulnerability_id) attached to it.
In case it doesn't, then I want to return the name on itself.
The tables: (or relevant parts thereof)
ADHOC_ITEM
ADHOC_VULNERABILITY
This is my query:
SELECT adi.name as item, CASE ahv.original_vulnerability_id
WHEN ISNULL(ahv.original_vulnerability_id) THEN ahv.name
ELSE 'foo'
END NAME
FROM adhoc_item adi
JOIN adhoc_vulnerability ahv ON ahv.id = adi.adhoc_vulnerability_id
and these are the results!
Now I know this is a little bit complex but i've been thinking about it for about 2 hours and found no explanation on why that last item (which has an adhoc_vulnerability with NULL as original_vulnerability_id) shows the value that is supposed to appear precisely when that attribute is not null!
Thanks in advance.

The problem is that when ahv.original_vulnerability_id is NULL, the ISNULL() function returns TRUE (which is MySQL is the same as 1 but this is irrelevant). So, instead of comparing the column with NULL, you are comparing it with TRUE.
Rewrite the CASE expresion as:
CASE
WHEN ISNULL(ahv.original_vulnerability_id) THEN ahv.name
ELSE 'foo'
END NAME
or as:
CASE
WHEN ahv.original_vulnerability_id IS NULL THEN ahv.name
ELSE 'foo'
END NAME

Related

MySQL Changing the Name of GROUP BY Column in a View Doesn't Return Results Properly

I have an issue with a View in my MySQL Database. When I select the view, and run a WHERE clause on it, I can only use the direct name of the column, and cannot change it to something else using AS - otherwise the content does not get displayed right:
CREATE OR REPLACE
ALGORITHM = UNDEFINED VIEW `View_Donations` AS
select
concat('$', sum(`donators_animal_linking`.`donation_amount`)) AS
`Donation Total`,
`donators_animal_linking`.`animal_type` AS `animal_type`
from
`donators_animal_linking`
group by
`donators_animal_linking`.`animal_type`
SELECT * FROM View_Donations
WHERE 'Animal Type' IS NOT NULL;
Even when the WHERE clause is set to IS NOT NULL, the null data still gets shown. And when it's set to something like 'Animal Type' === 'dolphin', then nothing shows up. Am I able to change the column name, or should I just leave it?
You have the wrong kind of quotes in your 'Animal Type'. The engine considers it a string, not a name of a column.
Which is why IS NOT NULL is always true (Animal Type is a well-defined string, so it's not null).
And 'Animal Type' = 'dolphin' will be always false, because - obviously - the two strings are different. Use backticks, like you do above.

Convert NULL into meaningful value with MAX function - mySQL

Can anybody please help me out with an incredibly frustrating line of code in mySQL.
This is a continuation of this case: "Pivoting" a key|value table using case returns blank columns... mySQL
The code provided in that sample works, but now I'm moving to production, there's one line I cannot get to work correctly. I suspect this is due to NULL values within the max clause, but I'm not sure of how to workaround.
I would like the troublesome line to be:
MAX(case when `meta_key`='key1' and (`meta_value`='Validated' or `meta_value` is null) then 'Validated' else 'Not Validated' end) as `key1`
Ie, NULL = validated; Validated = Validated; Anything else = Not Validated.
Updated
It seems the issue is that the null is not caused by a corresponding key|value with the value being null, but by there not being a corresponding key|value pair for that user. This is creating odd results.
See http://sqlfiddle.com/#!9/90f2d9/1 for example.
The following seems to work as you would like:
IFNULL(MAX(CASE WHEN `meta_key`='key1' AND `meta_value`<>'Validated' THEN'Not Validated' END),
'Validated') as `key1`
I've reversed your logic here, so anything that has a value for key1 that is not validated will return Not Validated, anything else will return NULL. I then use IFNULL() to replace all NULLs with Validated
Example on SQL Fiddle

cannot access field on a value with type string_google big query

I faced an error in google big query that I haven't been successful to find a solution for that.
I have two tables, Airnow_dataset.adjustedTime_met_la_wind_letter and Airnow_dataset.station_neighbour, the value of wind_direction column in the former table defines the column name of the latter one.
I used "case" clause to refer to the appropriate columns of another table, but an error comes up whenever I run query
cannot access field pm_station on a value with type string at [3:83]
I would like to mention that the number of rows returned in station_neighbour is one, so it shouldn't be a problem. Also, when I hardcoded s.pm_station value, it works fine. I guess the problem is due to aliasing in the outer query, but I don't know how I can fix it. For your information, I have attached the screenshot of my tables as well as my code.
Any help is greatly appreciated.
This is my query:
SELECT s.pm_station, s.RH, (CASE
WHEN s.wind_direction="W" then (SELECT ss.W FROM
`Airnow_dataset.station_neighbour` ss
WHERE ss.pm_station=s.pm_station )
ELSE "na"
END) as neighbour_wind_direction
FROM `Airnow_dataset.adjustedTime_met_la_wind_letter` s
This is image of schemas:
The problem is that your station_neighbour table has a column named S, so the outer alias for adjustedTime_met_la_wind_letter is shadowed within the CASE WHEN expression. To work around the error, use a different alias, e.g.:
SELECT
wind_letter.pm_station,
wind_letter.RH,
(CASE WHEN wind_letter.wind_direction="W" THEN (
SELECT ss.W FROM
`Airnow_dataset.station_neighbour` ss
WHERE ss.pm_station=wind_letter.pm_station )
ELSE "na" END) as neighbour_wind_direction
FROM `Airnow_dataset.adjustedTime_met_la_wind_letter` wind_letter

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.

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.