# IS NOT DISTINCT FROM vs row-wise equality with =

Looking at the below query,

``````SELECT null IS NOT DISTINCT FROM null AS indf,
null = null AS eq;
indf | eq
------+----
t    |
``````

From this we can see the result of the `IS NOT DISTINCT FROM` is `true`, and the result of the `eq` is `false`. The following then logically follows from that,

``````-- 1 rows
SELECT *
FROM ( VALUES (null) ) AS t(a)
JOIN ( VALUES (null) ) AS g(a)
ON t.a IS NOT DISTINCT FROM g.a;

-- 0 rows
SELECT *
FROM ( VALUES (null) ) AS t(a)
JOIN ( VALUES (null) ) AS g(a)
ON t.a = g.a;
``````

Because if the condition returns `null` the `join` fails. But, this throws me off, with row-wise comparison

``````-- returns 1 row.
SELECT *
FROM ( VALUES (null) ) AS t(a)
JOIN ( VALUES (null) ) AS g(a)
ON (t) IS NOT DISTINCT FROM (g);

-- also returns one row.
SELECT * FROM ( VALUES (null) ) AS t(a)
JOIN ( VALUES (null) ) AS g(a)
ON (t) = (g);
``````

Why does row-wise comparison treat `null`s different than scalar comparison? And is there a point of `IS NOT DISTINCT FROM` in row-wise comparison?

This behavior is documented under Composite Type Comparison,

The SQL specification requires row-wise comparison to return NULL if the result depends on comparing two `NULL` values or a `NULL` and a non-`NULL`. PostgreSQL does this only when comparing the results of two row constructors (as in Section 9.23.5) or comparing a row constructor to the output of a subquery (as in Section 9.22). In other contexts where two composite-type values are compared, two `NULL` field values are considered equal, and a `NULL` is considered larger than a non-`NULL`. This is necessary in order to have consistent sorting and indexing behavior for composite types.

This essentially means that the standard behavior can only be seen in the following syntax (`ROW()` is the row-constructor):

``````SELECT * FROM ( VALUES (null) ) AS t(a)
JOIN ( VALUES (null) ) AS g(a)
ON ROW(t.*) = ROW(g.*);
``````

versus,

``````SELECT * FROM ( VALUES (null) ) AS t(a)
JOIN ( VALUES (null) ) AS g(a)
ON ROW(t.*) IS NOT DISTINCT FROM ROW(g.*);
``````

Just using the ROW constructor with the type will not work either `ON ROW(t) = ROW(g)`