number equality to null using case when - postgresql

In my Postgres database, I'm checking user answers for correctness by checking if two IDs, "user_answered_id" and "expected_answer_id", are equivalent. If the user doesn't provide a "user_answered_id", then we still mark their answer as incorrect.
In Postgres, the following queries
select case when 1 != null then TRUE else FALSE end as test;
select case when 1 = null then TRUE else FALSE end as test;
both result in FALSE. This is true for any number check (e.g., when 2 != null, when 3 != null, ..., etc.
Why doesn't CASE WHEN show TRUE for 1 != null?
Must I put in the check "or is null"? E.g.,
CASE WHEN
user_answered_id != expected_answer_id
OR user_answered_id IS NULL
THEN TRUE
ELSE FALSE
END as user_incorrect_tally

What you are looking for is: IS DISTINCT FROM
select 2 is distinct from null;
?column?
----------
t
select 2 is distinct from 1;
?column?
----------
t
From the docs:
datatype IS DISTINCT FROM datatype → boolean
Not equal, treating null as a comparable value.
1 IS DISTINCT FROM NULL → t (rather than NULL)
NULL IS DISTINCT FROM NULL → f (rather than NULL)

SQL uses three-valued logic: true, false, and null. Null is not false. Null can be thought of as "no value".
Operations on null almost always yield null. So 1 != null is null. 1 = null is null. null = null is null. 5 < null is null. Etc.
To check for null, use is null and is not null.
Back to your query. is not distinct from and is distinct from are like = and != which treat null as a comparable value. So null is distinct from 1 will be true.
select
user_answered_id is distinct from expected_answer_id as user_incorrect
If you need to convert a null into a different value such as 0 or an empty string, use coalesce.
select
coalesce(user_answered_text, 'No Answer')
Your column is named "tally", but a tally means a count. If you intend to count a user's true and false answers use count with a filter.
select
count(user_answered_id) filter (
where user_answered_id = expected_answer_id
) as user_correct_tally,
-- count ignores null, this will only be the questions they tried to answer
count(user_answered_id) as user_answered_tally,
count(user_answered_id) filter (
where user_answered_is is distinct from expected_answer_id
) as user_incorrect_tally

Yes, You should check NULL value with is null, And last query you wrote is correct.
I suggest you to read below documents:
https://www.postgresql.org/docs/current/functions-comparison.html

Related

Postgres ignoring null values when using filter as not in array

SELECT * FROM Entity e WHERE e.Status <> ANY(ARRAY[1,2,3]);
Here Status is a nullable integer column. Using the above query i am unable to fetch the records whose status value is NULL.
SELECT * FROM Entity e WHERE (e.Status is NULL OR e.Status = 4);
This query does the trick. Could someone explain me why the first query was not working as expected.
NULL kinda means "unknown", so the expressions
NULL = NULL
and
NULL != NULL
are neither true nor false, they're NULL. Because it is not known whether an "unknown" value is equal or unequal to another "unknown" value.
Since <> ANY uses an equality test, if the value searched in the array is NULL, then the result will be NULL.
So your second query is correct.
It is spelled out in the docs Array ANY:
If the array expression yields a null array, the result of ANY will be null. If the left-hand expression yields null, the result of ANY is ordinarily null (though a non-strict comparison operator could possibly yield a different result). Also, if the right-hand array contains any null elements and no true comparison result is obtained, the result of ANY will be null, not false (again, assuming a strict comparison operator). This is in accordance with SQL's normal rules for Boolean combinations of null values.
FYI:
e.Status is NULL OR e.Status = 4
can be shortened to:
e_status IS NOT DISTINCT FROM 4
per Comparison operators.

Py-postgresql: WHERE param = None not working

I am using python3.6 and py-postgresql==1.2.1.
I have the following statement:
db.prepapre("SELECT * FROM seasons WHERE user_id=$1 AND season_id=$2 LIMIT 1), where season_id can be NULL.
I want to be able to be able to get the latest record with a NULL season_id by passing None as the $2 param, but it does not work. Instead, I need to create this second statement:
db.prepapre("SELECT * FROM seasons WHERE user_id=$1 AND season_id IS NULL LIMIT 1)
It must have something to do with season_id = NULL not working and season_id IS NULL is, but is there a way to make this work?
From Comparison Functions and Operators:
Do not write expression = NULL because NULL is not “equal to” NULL. (The null value represents an unknown value, and it is not known whether two unknown values are equal.)
Some applications might expect that expression = NULL returns true if expression evaluates to the null value. It is highly recommended that these applications be modified to comply with the SQL standard. However, if that cannot be done the transform_null_equals configuration variable is available. If it is enabled, PostgreSQL will convert x = NULL clauses to x IS NULL.
and:
19.13.2. Platform and Client Compatibility
transform_null_equals (boolean)
When on, expressions of the form expr = NULL (or NULL = expr) are treated as expr IS NULL, that is, they return true if expr evaluates to the null value, and false otherwise. The correct SQL-spec-compliant behavior of expr = NULL is to always return null (unknown). Therefore this parameter defaults to off.
You could rewrite your query:
SELECT *
FROM seasons
WHERE user_id = $1
AND (season_id = $2 OR ($2 IS NULL AND season_id IS NULL))
-- ORDER BY ... --LIMIT without sorting could be dangerous
-- you should explicitly specify sorting
LIMIT 1;

Dynamic WHERE condition Is Null or Is Not Null

I have a stored procedure which takes a #flag as a parameter. That flag supposes to indicate to select null values or none null values.
for none null values my solution looks like that:
#Flag int
SET #Flag = NULL
WHERE ISNULL(column1,'') = ISNULL(#Flag,'')
Is there a way to accommodate none null values in similar manner ? If no what would be the most compact solution ?
Flag int
SET #Flag = NULL
select * from table
WHERE (#flag is null and column is null) or ((#flag is not null and column is not null) and #flag = column)
I advise to NEVER SET ANSI_NULL OFF, NEVER! That can lead to a lot of unnecessary maintenance pain.
No need for a compact solution like
ISNULL(column1,'') = ISNULL(#Flag,'')
Also that ill return null rows when #flag = '' and also ill return '' rows when flag is null
The only way I can think of is SET ANSI_NULL OFF and then do comparison: column1 = #flag

case when null evaluates to false

Today I was surprised by this case behaviour:
select case when null then true else false end;
case
------
f
I would expect it to return null since a null casted to boolean yelds a null not a false:
select null::boolean is null;
?column?
----------
t
Any comments on the rationale of this behaviour? What Am I missing?
Use something like
case when p.parent_id is null then false else true end as has_parent
In case you (also) need something like
if column is null then 'value'
Use:
COALESCE(column_name, 'replacment for null value') as column_name
In case you still need a case statement then use:
case COALESCE(column_name, 'asdf')
when 'asdf' then true
else false
end as desired_column_name
You're thinking of the CASE expression like it was taking the null as input to a function or operator, where null input generally results in null output:
regress=> SELECT 't'::boolean = NULL::boolean;
bool
------
(1 row)
wheras in fact it behaves like a WHERE clause in terms of null handling:
craig=> SELECT 't' WHERE NULL;
?column?
----------
(0 rows)
In WHERE clauses - and in CASE, a NULL result from a test expression is treated as "not true and therefore false". In some ways it's regrettable that the SQL standard didn't make a NULL result in a WHERE expression an error instead of treating it as false, but that's how it is.
This is yet another painful symptom of NULL's split personality, where the SQL spec can't decide if NULL means "unknown/undefined value" or "the absence of a value", much like the horrible mess with NULLs and aggregates.
You can use 'select' to check DATA column for null:
select
ID,
case (select 1 where DATA is null)
when 1 then 'no data'
else data
end
from ...
As the PostgreSQL documentation states:
If no WHEN condition is true then the value of the case expression is
the result in the ELSE clause. If the ELSE clause is omitted and no condition matches, the result is null.
Postgresql does not cast the output, and since you have an else condition, you're getting false. The next query returns a null value (Since there's no else condition)
select case when null then true end;
The CASE statement you wrote has two branches:
The one, when null then true, will never happen (because null is not equal to true)
And else branch, that will happen when there are no matches in regular when branches.
Example:
CASE WHEN val = 1 THEN 5
WHEN val = 2 THEN 10
ELSE 20 /*all other values, including null*/
END
select case when null is null then null else 1 end
returns null
select case null when then null else 1 end
returns 1

Postgresql and comparing to an empty field

It seems that in PostgreSQL, empty_field != 1 (or some other value) is FALSE. If this is true, can somebody tell me how to compare with empty fields?
I have following query, which translates to "select all posts in users group for which one hasn't voted yet:
SELECT p.id, p.body, p.author_id, p.created_at
FROM posts p
LEFT OUTER JOIN votes v ON v.post_id = p.id
WHERE p.group_id = 1
AND v.user_id != 1
and it outputs nothing, even though votes table is empty. Maybe there is something wrong with my query and not with the logic above?
Edit: it seems that changing v.user_id != 1, to v.user_id IS DISTINCT FROM 1, did the job.
From PostgreSQL docs:
For non-null inputs, IS DISTINCT FROM
is the same as the <> operator.
However, when both inputs are null it
will return false, and when just one
input is null it will return true.
If you want to return rows where v.user_id is NULL then you need to handle that specially. One way you can fix it is to write:
AND COALESCE(v.user_id, 0) != 1
Another option is:
AND (v.user_id != 1 OR v.user_id IS NULL)
Edit: spacemonkey is correct that in PostgreSQL you should use IS DISTINCT FROM here.
NULL is a unknown value so it can never equal something. Look into using the COALESCE function.