From 5b3066a3857e1e246ba812ac57d326b9dbbb3d27 Mon Sep 17 00:00:00 2001
From: "David G. Johnston" <David.G.Johnston@Gmail.com>
Date: Mon, 10 Mar 2025 20:52:25 -0700
Subject: [PATCH 2/2] v7 Document NULL (delta from v6)

---
 doc/src/sgml/nullvalues.sgml | 1143 ++++++++++++++++++----------------
 1 file changed, 599 insertions(+), 544 deletions(-)

diff --git a/doc/src/sgml/nullvalues.sgml b/doc/src/sgml/nullvalues.sgml
index 07bbc58528..29ece221ff 100644
--- a/doc/src/sgml/nullvalues.sgml
+++ b/doc/src/sgml/nullvalues.sgml
@@ -6,27 +6,45 @@
  </indexterm>
 
  <para>
-  This section first introduces the concept of null values and then goes
-  on to explain how different parts of the system behave when provided
+  This section first introduces the concept of null values and then
+  explains how different parts of the system behave when provided
   one or more null value inputs.  Examples throughout this section
   can be executed so long as the following table and rows are created first.
  </para>
 
  <para>
-  Throughout this section the discussion of null values will be limited to
+  Throughout this section, the discussion of null values will be limited to
   the SQL language unless otherwise noted.  The JSON-related data types, and the
   non-SQL procedural languages, have their own behaviors documented in their
   respective areas.
  </para>
 
- <programlisting>
-  CREATE TABLE null_examples (
-    id bigint PRIMARY KEY,
-    value integer NULL
-  );
-  INSERT INTO null_examples
-  VALUES (1, 1), (2, NULL), (3, 4);
- </programlisting>
+ <para>
+  The following <literal>CREATE TABLE</literal> and <literal>INSERT</literal>
+  SQL commands can be executed in any SQL client to create and populate
+  the persistent table used in the examples below.  The <literal>\pset</literal>
+  commands require the use of <application>psql</application> as the client program;
+  they make the resulting output a bit easier to read and do not impact any behaviors
+  described herein.  Note, the examples below have been manually edited to show
+  <literal>true</literal> and <literal>false</literal> instead of
+  <literal>t</literal> and <literal>f</literal>.  They also omit any transactional
+  command output when transactions are used.  Instead, each transaction gets its own
+  display block.
+ </para>
+
+<programlisting>
+CREATE TABLE null_examples (
+  id bigint PRIMARY KEY,
+  value integer NULL
+);
+INSERT INTO null_examples
+VALUES (1, 1), (2, NULL), (3, 4);
+
+-- This makes null values print as \N in the output instead of the empty string.
+\pset null '\\N'
+-- Removes the row count footer that prints by default.
+\pset footer off
+</programlisting>
 
  <sect2 id="nullvalues-model">
   <title>Meaning</title>
@@ -53,15 +71,14 @@
   <para>
    A null value, like all values, must have a data type, and is valid for all data types.
    It must also be printed as text.  This section discusses null values at the boundaries
-   of the system as well as how they can come into existence due to the design of a query.
+   of the system, as well as how they can come into existence due to the design of a query.
   </para>
   <sect3 id="nullvalues-usage-input">
    <title>Null Value Input</title>
    <para>
     A null value can be used as input to any function, operator, or expression.
     The system will then decide how to behave based on the rules described in the
-    rest of this section.  The system will also decide how to behave when a null value
-    is used as a parameter to a function that does not accept null values.
+    rest of this section.
    </para>
    <para>
     As noted in <xref linkend="sql-syntax-constants-nullvalue"/>,
@@ -70,34 +87,34 @@
     but can be cast to any concrete data type.
    </para>
    <para>
-    <programlisting>
-    SELECT
-     NULL AS "Literal Null Value",
-     pg_typeof(null) AS "Type of Null",
-     pg_typeof(NULL::text) AS "Type of Cast Null",
-     cast(null as text) AS "Cast Null Value";
-    </programlisting>
-    <screen>
-      Literal Null Value | Type of Null | Type of Cast Null | Cast Null Value
-     --------------------+--------------+-------------------+-----------------
-                         | unknown      | text              |
-    </screen>
+<programlisting>
+SELECT
+ NULL AS "Literal Null Value",
+ pg_typeof(null) AS "Type of Null",
+ pg_typeof(NULL::text) AS "Type of Cast Null",
+ cast(null as text) AS "Cast Null Value";
+</programlisting>
+<screen>
+ Literal Null Value | Type of Null | Type of Cast Null | Cast Null Value
+--------------------+--------------+-------------------+-----------------
+ \N                 | unknown      | text              | \N
+</screen>
    </para>
    <para>
-    <programlisting>
-    SELECT text NULL;
-    </programlisting>
-    <screen>
-    ERROR:  column "text" does not exist
-    LINE 1: select text NUll;
-    </screen>
+<programlisting>
+SELECT text NULL;
+</programlisting>
+<screen>
+ERROR:  column "text" does not exist
+LINE 1: select text NUll;
+</screen>
    </para>
    <para>
     The <link linkend="sql-copy"><command>COPY ... FROM</command></link> command,
     including its psql counter-part meta-command
     <link linkend="app-psql-meta-commands-copy"><command>\copy</command></link>,
     must deal with input files containing textual representations of the null value.
-    The lack of consistency in real world data requires having a few options to the
+    The lack of consistency in real-world data requires having a few options to the
     command related to null handling.  See the documentation in
     <xref linkend="sql-copy"/> for more information.
     But, in short, for CSV input it expects text to be quoted and interprets an unquoted
@@ -107,9 +124,13 @@
   <sect3 id="nullvalues-usage-tables">
    <title>Null Values in Tables</title>
    <para>
-    Whether via the copy method above, or by inserting literal null values,
-    most usage concerns for null values results from their presence in a table
-    column that lacks a <link linkend="nullvalues-table-constraints">not-null constraint</link>.
+    From a semantics perspective a table treats a null value like any other value.
+    However, the SQL Language recognizes its uniqueness by defining a column-scoped
+    <link linkend="nullvalues-table-constraints"><literal>NOT NULL</literal> constraint</link>
+    as syntactic sugar.  At present one would expect that a column having a
+    domain data type with a <literal>NOT NULL</literal> constraint would likewise be
+    incapable of having a null value stored.  This is not the case.  See the commentary
+    below in <xref linkend="nullvalues-domains"/> for more information.
    </para>
   </sect3>
   <sect3 id="nullvalues-usage-derived">
@@ -117,26 +138,26 @@
    <para>
     Even if all data stored in tables are known to be non-null, null values can still
     be produced while executing a query.  The most common way this happens is by
-    introducing a (left) outer join to the query and having data missing optional related
-    data on the right side of the join.
-    <programlisting>
-     SELECT
-      countries.country,
-      flagships.flagship
-     FROM (
-      VALUES ('Spain'), ('Switzerland')
-     ) as countries (country)
-     LEFT JOIN (
-      VALUES ('Spain', 'Ship')
-     ) as flagships (country, flagship)
-     ON countries.country = flagships.country;
-    </programlisting>
-    <screen>
-        country   | flagship 
-     -------------+----------
-      Spain       | Ship
-      Switzerland | NULL
-    </screen>
+    introducing a (left) outer join to the query and having left side data without
+    corresponding data on the right side of the join.
+<programlisting>
+SELECT
+ countries.country,
+ flagships.flagship
+FROM (
+ VALUES ('Spain'), ('Switzerland')
+) as countries (country)
+LEFT JOIN (
+ VALUES ('Spain', 'Ship')
+) as flagships (country, flagship)
+ON countries.country = flagships.country;
+</programlisting>
+<screen>
+   country   | flagship
+-------------+----------
+ Spain       | Ship
+ Switzerland | \N
+</screen>
    </para>
   </sect3>
   <sect3 id="nullvalues-usage-output">
@@ -145,7 +166,7 @@
     As evidenced above, the "absence of value" aspect of the null value results
     in its secondary textual representation being an empty string
     (its primary representation is just NULL).
-    This can be problematic if the empty string is expected to also be a valid value.
+    This can be problematic if the empty string is expected to be a valid value.
     Therefore, places that deal with possible null values as input and text as
     output need some means to give the user a way to specify how to print
     the null value.
@@ -166,7 +187,7 @@
    </para>
    <para>
     When the final output of the result is a text file instead of a user additional
-    considerations come into play.  While the option to simply take the user presentation
+    considerations come into play.  While the option to take the user presentation
     and send it to a text file always exists <productname>PostgreSQL</productname> also
     provides a facility to output a structured text file.
     The <link linkend="sql-copy"><command>COPY ... TO</command></link> command,
@@ -186,15 +207,15 @@
     In three-valued logic the concept of unknown, represented using the null value, is
     also an outcome.  This results in falsifying the common-sense notion
     that "p OR NOT p" is always true.
-    <programlisting>
-     SELECT
-      NULL OR NOT NULL AS "N OR !N";
-    </programlisting>
-    <screen>
-      N OR !N 
-     ---------
-      NULL
-    </screen>
+<programlisting>
+SELECT
+ NULL OR NOT NULL AS "N OR !N";
+</programlisting>
+<screen>
+ N OR !N
+---------
+ \N
+</screen>
     (See <xref linkend="nullvalues-operands"/> for more explanation.)
    </para>
    <para>
@@ -216,54 +237,54 @@
     neither equal nor unequal
    </link>
    to any value, including other null values.
-   <programlisting>
-    SELECT
-     NULL = NULL AS "N = N",
-     NULL != NULL AS "N != N",
-     1 = NULL AS "1 = N",
-     1 != NULL AS "1 != N",
-     1 = 1 AS "1 = 1",
-     1 != 1 AS "1 != 1";
-   </programlisting>
-   <screen>
-     N = N | N != N | 1 = N | 1 != N | 1 = 1 | 1 != 1
-    -------+--------+-------+--------+-------+--------
-           |        |       |        | t     | f
-   </screen>
+<programlisting>
+SELECT
+ NULL = NULL AS "N = N",
+ NULL != NULL AS "N != N",
+ 1 = NULL AS "1 = N",
+ 1 != NULL AS "1 != N",
+ 1 = 1 AS "1 = 1",
+ 1 != 1 AS "1 != 1";
+</programlisting>
+<screen>
+ N = N | N != N | 1 = N | 1 != N | 1 = 1 | 1 != 1
+-------+--------+-------+--------+-------+--------
+ \N    | \N     | \N    | \N     | true  | false
+</screen>
    However, as with many rules, there are exceptions, as noted in
    <xref linkend="nullvalues-multielementcomparison"/>.
    Particularly, when the two compared values are part of a larger multi-element value.
-   <programlisting>
-    SELECT
-     array[1,2]=array[1,null] AS "Array Equals";
-   </programlisting>
-   <screen>
-     Array Equals
-    --------------
-     f
-   </screen>
+<programlisting>
+SELECT
+ array[1,2]=array[1,null] AS "Array Equals";
+</programlisting>
+<screen>
+ Array Equals
+--------------
+ false
+</screen>
   </para>
   <para>
-   Because of this SQL standard mandated rule, checking for a null value has an
-   explicit <literal>IS NULL</literal> predicate, and additionally there comparison
+   Because of this SQL standard rule, checking for a null value has an
+   explicit <literal>IS NULL</literal> predicate.  Additionally, there are comparison
    predicates that consider a null value equal to other null values but unequal
    to any other value (e.g., <literal>IS DISTINCT</literal>, and <literal>IS TRUE</literal>.)
    These, and other predicates, are described in
    <xref linkend="functions-comparison-pred-table"/>
-   <programlisting>
-    SELECT id, value,
-     value IS NULL AS "IS N",
-     value IS DISTINCT FROM id AS "IS D",
-     value != id AS "IS !="
-    FROM null_examples;
-   </programlisting>
-   <screen>
-     id | value | IS N | IS D | IS !=
-    ----+-------+------+------+-------
-      1 |     1 | f    | f    | f
-      2 |       | t    | t    |
-      3 |     4 | f    | t    | t
-   </screen>
+<programlisting>
+SELECT id, value,
+ value IS NULL AS "IS NULL",
+ value IS DISTINCT FROM id AS "IS DIST",
+ value != id AS "IS !="
+FROM null_examples;
+</programlisting>
+<screen>
+ id | value | IS NULL | IS DIST | IS !=
+----+-------+---------+---------+-------
+  1 |     1 | false   | false   | false
+  2 |    \N | true    | true    | \N
+  3 |     4 | false   | true    | true
+</screen>
   </para>
   <para>
    On the other hand, the SQL standard is largely alone in taking this approach to comparing
@@ -275,30 +296,30 @@
   <para>
    There is also a cardinal warning: when dealing with
    <link linkend="rowtypes">composite types</link> in
-   expressions; <literal>composite IS NULL</literal>
-   and <literal>composite IS NOT NUll</literal>
+   expressions, <literal>composite IS NULL</literal>
+   and <literal>composite IS NOT NULL</literal>
    are not the opposites of each other in the case where some,
    but not all, of the composite's fields are null values.
    (The case where all fields are null is indistinguishable
    from the composite as a whole being null.)
    Write <literal>NOT(composite IS NULL)</literal> instead.
-   <programlisting>
-    SELECT
-     c,
-     c IS NULL AS "c IS N",
-     NOT(c IS NULL) AS "NOT c IS N",
-     c IS NOT NULL AS "c IS NOT N",
-     ROW(value, value) IS NULL AS "ROW(v,v) IS N",
-     ROW(value, value) IS NOT NULL AS "ROW(v,v) IS NOT N"
-    FROM null_examples AS c;
-   </programlisting>
-   <screen>
-       c   | c IS N | NOT c IS N | c IS NOT N | ROW(v,v) IS N | ROW(v,v) IS NOT N
-    -------+--------+------------+------------+---------------+-------------------
-     (1,1) | f      | t          | t          | f             | t
-     (2,)  | f      | t          | f          | t             | f
-     (3,4) | f      | t          | t          | f             | t
-   </screen>
+<programlisting>
+SELECT
+ c,
+ c IS NULL AS "c IS N",
+ NOT(c IS NULL) AS "NOT c IS N",
+ c IS NOT NULL AS "c IS NOT N",
+ ROW(value, value) IS NULL AS "ROW(v,v) IS N",
+ ROW(value, value) IS NOT NULL AS "ROW(v,v) IS NOT N"
+FROM null_examples AS c;
+</programlisting>
+<screen>
+   c   | c IS N | NOT c IS N | c IS NOT N | ROW(v,v) IS N | ROW(v,v) IS NOT N
+-------+--------+------------+------------+---------------+-------------------
+ (1,1) | false  | true       | true       | false         | true
+ (2,)  | false  | true       | false      | true          | false
+ (3,4) | false  | true       | true       | false         | true
+</screen>
    See <xref linkend="nullvalues-multielement"/> below for an explanation.
   </para>
  </sect2>
@@ -306,35 +327,35 @@
  <sect2 id="nullvalues-operands">
   <title>Null-Valued Operands</title>
   <para>
-   As a general expectation, operator invocation expressions where one of inputs
+   As a general expectation, operator invocation expressions where one of the inputs
    is a null value will result in a null-valued output.
-   <programlisting>
-    SELECT
-     1 + null AS "Add",
-     'text' || null AS "Concatenate";
-   </programlisting>
-   <screen>
-     Add | Concatenate
-    -----+-------------
-         |
-   </screen>
+<programlisting>
+SELECT
+ 1 + null AS "Add",
+ 'text' || null AS "Concatenate";
+</programlisting>
+<screen>
+ Add | Concatenate
+-----+-------------
+  \N | \N
+</screen>
    Operators that behave otherwise should document their deviation from this norm.
   </para>
   <para>
-   A notable example of this is the <literal>IN</literal> operator, which
+   A notable example is the <literal>IN</literal> operator, which
    uses equality, not distinctness, for testing.
-   <programlisting>
-    SELECT
-     1 IN (1, null) AS "In Present",
-     1 IN (2, null) AS "In Missing",
-     null IN (1, 2) AS "N In Non-N",
-     null IN (null, 2) AS "N In N";
-   </programlisting>
-   <screen>
-     In Present | In Missing | N In Non-N | N In N
-    ------------+------------+------------+--------
-     t          |            |            |
-   </screen>
+<programlisting>
+SELECT
+ 1 IN (1, null) AS "In Present",
+ 1 IN (2, null) AS "In Missing",
+ null IN (1, 2) AS "N In Non-N",
+ null IN (null, 2) AS "N In N";
+</programlisting>
+<screen>
+ In Present | In Missing | N In Non-N | N In N
+------------+------------+------------+--------
+ true       | \N         | \N         | \N
+</screen>
    This is just an extension of the multi-element testing behavior described in
    <xref linkend="nullvalues-multielement"/>.
   </para>
@@ -342,77 +363,77 @@
    Experience shows that <literal>CASE</literal> expressions are also prone
    to bugs since their format encourages binary logic thinking while a
    <literal>WHEN</literal> test will not consider a null value to be a match.
-   <programlisting>
-    SELECT id, value,
-     CASE WHEN id = value THEN 'Equal' ELSE 'Not Equal' END AS "Affirm",
-     CASE WHEN id != value THEN 'Not Equal' ELSE 'Equal' END AS "Negate",
-     CASE WHEN value IS NULL THEN 'Null'
-          WHEN id = value THEN 'Equal'
-          ELSE 'Not Equal' END AS "Safe Affirm",
-     CASE WHEN value IS NULL THEN 'Null'
-          WHEN id != value THEN 'Not Equal'
-          ELSE 'Equal' END AS "Safe Negate"
-    FROM null_examples;
-   </programlisting>
-   <screen>
-     id | value |  Affirm   |  Negate   | Safe Affirm | Safe Negate
-    ----+-------+-----------+-----------+-------------+-------------
-      1 |     1 | Equal     | Equal     | Equal       | Equal
-      2 |       | Not Equal | Equal     | Null        | Null
-      3 |     4 | Not Equal | Not Equal | Not Equal   | Not Equal
-   </screen>
+<programlisting>
+SELECT id, value,
+ CASE WHEN id = value THEN 'Equal' ELSE 'Not Equal' END AS "Affirm",
+ CASE WHEN id != value THEN 'Not Equal' ELSE 'Equal' END AS "Negate",
+ CASE WHEN value IS NULL THEN 'Null'
+      WHEN id = value THEN 'Equal'
+      ELSE 'Not Equal' END AS "Safe Affirm",
+ CASE WHEN value IS NULL THEN 'Null'
+      WHEN id != value THEN 'Not Equal'
+      ELSE 'Equal' END AS "Safe Negate"
+FROM null_examples;
+</programlisting>
+<screen>
+ id | value |  Affirm   |  Negate   | Safe Affirm | Safe Negate
+----+-------+-----------+-----------+-------------+-------------
+  1 |     1 | Equal     | Equal     | Equal       | Equal
+  2 |    \N | Not Equal | Equal     | Null        | Null
+  3 |     4 | Not Equal | Not Equal | Not Equal   | Not Equal
+</screen>
   </para>
   <para>
    The boolean operators <literal>AND</literal> and <literal>OR</literal>
    will ignore the null value input if the other input is sufficient to
    determine the outcome.
-   <programlisting>
-    SELECT
-     true OR null AS "T or N",
-     false OR null AS "F or N",
-     true AND null AS "T and N",
-     false AND null AS "F and N";
-   </programlisting>
-   <screen>
-     T or N | F or N | T and N | F and N
-    --------+--------+---------+---------
-     t      |        |         | f
-   </screen>
+<programlisting>
+SELECT
+ true OR null AS "T or N",
+ false OR null AS "F or N",
+ true AND null AS "T and N",
+ false AND null AS "F and N";
+</programlisting>
+<screen>
+ T or N | F or N | T and N | F and N
+--------+--------+---------+---------
+ true   | \N     | \N      | false
+</screen>
   </para>
  </sect2>
 
  <sect2 id="nullvalues-domains">
   <title>Null Values in Domains</title>
   <para>
-   A domain is a user-defined data type that can have a <literal>NOT NULL</literal> constraint.
-   However, some usages of domains will cause the resultant output column (not table column)
-   to have the domain type but the value will be null.
-   The common way this happens is by including the domain column's table on the right side of a left join.
-   <programlisting>
-    BEGIN;
-    CREATE DOMAIN domain_example AS integer NOT NULL;
-    CREATE TABLE domain_examples (de_id bigint PRIMARY KEY, de_value domain_example);
-    INSERT INTO domain_examples VALUES (1, 1), (2, 2);
-    SELECT *, pg_typeof(de_value)
-    FROM null_examples AS ne
-    LEFT JOIN domain_examples AS de ON ne.id = de.de_id;
-    ROLLBACK;
-   </programlisting>
-   <screen>
-    BEGIN
-    CREATE DOMAIN
-    CREATE TABLE
-    INSERT 0 2
-     id | value | de_id | de_value |   pg_typeof
-    ----+-------+-------+----------+----------------
-      1 |     1 |     1 |        1 | domain_example
-      2 |       |     2 |        2 | domain_example
-      3 |     4 |       |          | domain_example
+   A domain is a user-defined data type that can have a <literal>NOT NULL</literal>
+   constraint.  However, some usages of domains can still cause a column to be of the
+   domain type but some value may be null.  The common way this happens is by including
+   the domain column's table on the right side of a left join.
+<programlisting>
+BEGIN;
+CREATE DOMAIN domain_example AS integer NOT NULL;
+CREATE TABLE domain_examples (de_id bigint PRIMARY KEY, de_value domain_example);
+INSERT INTO domain_examples VALUES (1, 1), (2, 2);
+SELECT *, pg_typeof(de_value)
+FROM null_examples AS ne
+LEFT JOIN domain_examples AS de ON ne.id = de.de_id;
+ROLLBACK;
+</programlisting>
+<screen>
+BEGIN
+CREATE DOMAIN
+CREATE TABLE
+INSERT 0 2
+ id | value | de_id | de_value |   pg_typeof
+----+-------+-------+----------+----------------
+  1 |     1 |     1 |        1 | domain_example
+  2 |    \N |     2 |        2 | domain_example
+  3 |     4 |    \N |       \N | domain_example
 
-    ROLLBACK
-   </screen>
+ROLLBACK
+</screen>
    Please see the details in <xref linkend="sql-createdomain-notes"/>
-   for another example, as well as commentary why this non-standard behavior exists.
+   for another example, as well as commentary on why this non-standard behavior exists.
   </para>
  </sect2>
 
@@ -421,18 +442,18 @@
   <para>
    Arrays and composite types are multi-element types.  Here we also consider non-empty
    <link linkend="functions-subquery">subquery results</link>
-   and the list of values specified in the
+   and the list of values (i.e., the multiset) specified in the
    <link linkend="functions-comparisons-in-scalar">IN test</link>.
   </para>
   <para>
    When a test is performed on one of these multi-element values
-   the system will iterate over each element, or pair of elements if the test is
-   <link linkend="row-wise-comparison">comparing two row constructors</link> to each other,
+   the system will iterate over each element, (or pair of elements if the test is
+   <link linkend="row-wise-comparison">comparing two row constructors</link> to each other),
    left-to-right, combining the results using the boolean operations described in
    <xref linkend="nullvalues-operands"/>. For tests that
    require an exhaustive search, (e.g., <literal>ALL</literal>, <literal>NOT IN</literal>)
    the search effectively ends when a false result is found (<literal>AND</literal> combiners).
-   For tests that simply require a true result, (e.g., <literal>ANY</literal>,
+   For tests that require a true result, (e.g., <literal>ANY</literal>,
    <literal>IN</literal>) the search effectively ends when a true result is found
    (<literal>OR</literal> combiners). Therefore:
    <simplelist>
@@ -462,7 +483,7 @@
    when the composite has a mix of null and non-null values.
   </para>
   <para>
-   In the next section the rules above are discussed.
+   In the next section, the rules above are discussed.
    <xref linkend="nullvalues-multielementpredicates"/>
    discusses situations where a predicate or a scalar value
    are being compared to a multi-element value.
@@ -478,103 +499,104 @@
   <sect3 id="nullvalues-multielementpredicates-composites">
    <title>Composite Fields</title>
    <para>
-    When a composite typed valued is created a null value can be assigned to any
+    When a composite typed value is created, a null value can be assigned to any
     of its fields (see <xref linkend="rowtypes-constructing"/> for how to do this).
     So long as at least one field is non-null the composite value
-    as whole exists and an <literal>IS NULL</literal> predicate will return false.
+    as a whole exists and an <literal>IS NULL</literal> predicate will return false.
    </para>
    <para>
     Applying the <literal>IS NOT NULL</literal> predicate to a composite value performs
-    checks whether all fields of the composite have non-null values.  This is not the same
+    checks on whether all fields of the composite have non-null values.  This is not the same
     as a non-null composite value.  Specifically, if the composite value has
     a null-valued field then both the <literal>IS NOT NULL</literal> predicate and the
     <literal>IS NULL</literal> predicate will return false.
-    <programlisting>
-     SELECT
-      ROW(1,2) IS NULL AS "Row Is Null",
-      ROW(1,2) IS NOT NULL AS "Row Is Not Null",
-      ROW(1,NULL) IS NULL AS "Row Is Null",
-      ROW(1,NULL) IS NOT NULL AS "Row Is Not Null";
-    </programlisting>
-    <screen>
-      Row Is Null | Row Is Not Null | Row Is Null | Row Is Not Null
-     -------------+-----------------+-------------+-----------------
-      f           | t               | f           | f
-    </screen>
+<programlisting>
+SELECT
+ ROW(1,2) IS NULL AS "Row Is Null",
+ ROW(1,2) IS NOT NULL AS "Row Is Not Null",
+ ROW(1,NULL) IS NULL AS "Row Is Null",
+ ROW(1,NULL) IS NOT NULL AS "Row Is Not Null";
+</programlisting>
+<screen>
+ Row Is Null | Row Is Not Null | Row Is Null | Row Is Not Null
+-------------+-----------------+-------------+-----------------
+ false       | true            | false       | false
+</screen>
    </para>
    <para>
     Please read <xref linkend="composite-type-comparison"/> for a complete treatment
     on how <productname>PostgreSQL</productname> handles row-wise comparison.  The
-    next two multi-element related items in this subsection discuss those comparisons in the
+    next two multi-element related items in this section discuss those comparisons in the
     presence of null-valued fields, and also in terms of the SQL standard.
    </para>
   </sect3>
   <sect3 id="nullvalues-multielementpredicates-arrays">
-   <title>Array Elements and IN Bag Members</title>
+   <title>Array Elements and IN Multiset Members</title>
    <para>
     Examples of applying the behavior discussed in <xref linkend="nullvalues-multielement"/>
-    to arrays, and <literal>IN</literal> and <literal>NOT IN</literal> bags, using the
+    to arrays, and <literal>IN</literal> and <literal>NOT IN</literal> multisets, using the
     operators defined in <xref linkend="functions-comparisons"/>.  The following examples produce
     the same results when swapping <literal>IN</literal>/<literal>ANY</literal>
-    and also <literal>NOT IN</literal>/<literal>ALL</literal>, plus transforming the bag/array format.
+    and also <literal>NOT IN</literal>/<literal>ALL</literal>, plus transforming the multiset/array format.
     I.e., the exhaustive and non-exhaustive pairs noted in <xref linkend="nullvalues-multielement"/>.
    </para>
    <para>
-    <programlisting>
-     SELECT
-      1 = ANY(array[1, 1, NULL]) AS "Any-Null-Match",
-      1 = ANY(array[1, 1]) AS "Any-NoNull-Match",
-      1 = ALL(array[1, 1, NULL]) AS "ALL-Null-Match",
-      1 = ALL(array[1, 1]) AS "All-NoNull-Match";
-     SELECT
-      2 IN (1, 1, NULL) AS "IN-Null-Negative",
-      2 IN (1, 1) AS "IN-NoNull-Negative",
-      2 NOT IN (2, 2, NULL) AS "NotIN-Null-Negative",
-      2 NOT IN (2, 2) AS "NotIN-NoNull-Negative";
-    </programlisting>
-    <screen>
-      Any-Null-Match | Any-NoNull-Match | ALL-Null-Match | All-NoNull-Match
-     ----------------+------------------+----------------+------------------
-      t              | t                |                | t
+<programlisting>
+SELECT
+ 1 = ANY(array[1, 1, NULL]) AS "Any-Null-Match",
+ 1 = ANY(array[1, 1]) AS "Any-NoNull-Match",
+ 1 = ALL(array[1, 1, NULL]) AS "ALL-Null-Match",
+ 1 = ALL(array[1, 1]) AS "All-NoNull-Match";
+SELECT
+ 2 IN (1, 1, NULL) AS "IN-Null-Negative",
+ 2 IN (1, 1) AS "IN-NoNull-Negative",
+ 2 NOT IN (2, 2, NULL) AS "NotIN-Null-Negative",
+ 2 NOT IN (2, 2) AS "NotIN-NoNull-Negative";
+</programlisting>
+<screen>
+ Any-Null-Match | Any-NoNull-Match | ALL-Null-Match | All-NoNull-Match
+----------------+------------------+----------------+------------------
+ true           | true             | \N             | true
 
-      IN-Null-Negative | IN-NoNull-Negative | NotIN-Null-Negative | NotIN-NoNull-Negative
-     ------------------+--------------------+---------------------+-----------------------
-                       | f                  | f                   | f
-    </screen>
+ IN-Null-Negative | IN-NoNull-Negative | NotIN-Null-Negative | NotIN-NoNull-Negative
+------------------+--------------------+---------------------+-----------------------
+ \N               | false              | false               | false
+</screen>
    </para>
   </sect3>
   <sect3 id="nullvalues-multielementpredicates-subqueries">
    <title>Single-Column Subquery Rows</title>
    <para>
-    Examples of applying the behavior discussed in <xref linkend="nullvalues-multielement"/>
-    to subqueries using the operators defined in <xref linkend="functions-subquery"/>.  Here we
-    covers the case were the multiple elements being checked are rows, each having one column.
+    The following examples demonstrate the behavior discussed in
+    <xref linkend="nullvalues-multielement"/>
+    applied to subqueries using the operators defined in <xref linkend="functions-subquery"/>.
+    Here we cover the case where the multiple elements being checked are rows, each having one column.
     If the column itself is multi-element then the thing being searched for must be a compatible
     multi-element value, and the corresponding comparison behavior described in
     <xref linkend="nullvalues-multielementcomparison"/> will also be applied.
    </para>
    <para>
-    <programlisting>
-     SELECT
-      1 = ANY(SELECT unnest(array[1, 1, NULL])) AS "Any-Null-Match",
-      1 = ANY(SELECT unnest(array[1, 1])) AS "Any-NoNull-Match",
-      1 = ALL(SELECT unnest(array[1, 1, NULL])) AS "ALL-Null-Match",
-      1 = ALL(SELECT unnest(array[1, 1])) AS "All-NoNull-Match";
-     SELECT
-      2 = ANY(SELECT unnest(array[1, 1, NULL])) AS "Any-Null-NoMatch",
-      2 = ANY(SELECT unnest(array[1, 1])) AS "Any-NoNull-NoMatch",
-      2 = ALL(SELECT unnest(array[1, 1, NULL])) AS "ALL-Null-NoMatch",
-      2 = ALL(SELECT unnest(array[1, 1])) AS "All-NoNull-NoMatch";
-    </programlisting>
-    <screen>
-      Any-Null-Match | Any-NoNull-Match | ALL-Null-Match | All-NoNull-Match
-     ----------------+------------------+----------------+------------------
-      t              | t                |                | t
+<programlisting>
+SELECT
+ 1 = ANY(SELECT unnest(array[1, 1, NULL])) AS "Any-Null-Match",
+ 1 = ANY(SELECT unnest(array[1, 1])) AS "Any-NoNull-Match",
+ 1 = ALL(SELECT unnest(array[1, 1, NULL])) AS "ALL-Null-Match",
+ 1 = ALL(SELECT unnest(array[1, 1])) AS "All-NoNull-Match";
+SELECT
+ 2 = ANY(SELECT unnest(array[1, 1, NULL])) AS "Any-Null-NoMatch",
+ 2 = ANY(SELECT unnest(array[1, 1])) AS "Any-NoNull-NoMatch",
+ 2 = ALL(SELECT unnest(array[1, 1, NULL])) AS "ALL-Null-NoMatch",
+ 2 = ALL(SELECT unnest(array[1, 1])) AS "All-NoNull-NoMatch";
+</programlisting>
+<screen>
+ Any-Null-Match | Any-NoNull-Match | ALL-Null-Match | All-NoNull-Match
+----------------+------------------+----------------+------------------
+ true           | true             | \N             | true
 
-      Any-Null-NoMatch | Any-NoNull-NoMatch | ALL-Null-NoMatch | All-NoNull-NoMatch
-     ------------------+--------------------+------------------+--------------------
-                       | f                  | f                | f
-    </screen>
+ Any-Null-NoMatch | Any-NoNull-NoMatch | ALL-Null-NoMatch | All-NoNull-NoMatch
+------------------+--------------------+------------------+--------------------
+ \N               | false              | false            | false
+</screen>
    </para>
   </sect3>
  </sect2>
@@ -582,9 +604,9 @@
  <sect2 id="nullvalues-multielementcomparison">
   <title>Multi-Element Comparisons</title>
   <para>
-   The previous subsection, <xref linkend="nullvalues-multielementpredicates"/>, discussed applying
+   The previous section, <xref linkend="nullvalues-multielementpredicates"/>, discussed applying
    a predicate or a scalar value check element-wise across a multi-element value.
-   This subsection moves the discussion over to comparing two multi-element values to each other.
+   This section moves the discussion over to comparing two multi-element values to each other.
    As both array and composite typed values
    can be stored within an index, and comparing two values in that context must not produce
    a null-valued result, considerations are made to adhere to the SQL standard where
@@ -599,67 +621,67 @@
    the fact that row constructors are query literals, while composite typed values can be stored,
    brings about important differences in how they are treated.  Please read
    <xref linkend="composite-type-comparison"/> for a fuller treatment of this topic.  Here
-   we briefly recap the five different situations in the presence of null values.
+   we briefly recap the five situations in the presence of null values.
   </para>
   <sect3 id="nullvalues-multielementcomparison-array">
    <title>Element-wise Comparisons</title>
    <para>
-    First situation, null values within an array compare as equal to each other and greater than all
-    non-null values, regardless of whether the comparison involves
-    <link linkend="sql-syntax-array-constructors">array constructors</link> or array typed values.
-    <programlisting>
-     SELECT
-      array[1,2]=array[1,null] AS "Constructors",
-      s, t,
-      s = t AS "Stored Equality",
-      t &gt; s AS "Stored Ordering"
-     FROM
-     (values (array[1,2])) AS sv (s),
-     (values (array[1,null::integer])) AS st (t);
-    </programlisting>
-    <screen>
-      Constructors |   s   |    t     | Stored Equality | Stored Ordering
-     --------------+-------+----------+-----------------+-----------------
-      f            | {1,2} | {1,NULL} | f               | t
-    </screen>
+    In this first situation, null values within an array compare as equal to each other and greater
+    than all non-null values, regardless of whether the comparison involves
+    <link linkend="sql-syntax-array-constructors">array constructors</link> or array-typed values.
+<programlisting>
+SELECT
+ array[1,2]=array[1,null] AS "Constructors",
+ s, t,
+ s = t AS "Stored Equality",
+ t &gt; s AS "Stored Ordering"
+FROM
+(values (array[1,2])) AS sv (s),
+(values (array[1,null::integer])) AS st (t);
+</programlisting>
+<screen>
+ Constructors |   s   |    t     | Stored Equality | Stored Ordering
+--------------+-------+----------+-----------------+-----------------
+ false        | {1,2} | {1,NULL} | false           | true
+</screen>
    </para>
   </sect3>
   <sect3 id="nullvalues-multielementcomparison-rowconstructor">
    <title>Row-wise Mutual Row Constructor Comparisons</title>
    <para>
-    In this situation null values produce unknown when compared to all values.
-    <programlisting>
-     SELECT
-      (1,2)=(1,null) AS "NonNull=Null",
-      (1,null::integer)=(1,null) AS "Null=Null";
-    </programlisting>
-    <screen>
-      NonNull=Null | Null=Null
-     --------------+-----------
-                   |
-    </screen>
+    In this situation, null values produce unknown when compared to all values.
+<programlisting>
+SELECT
+ (1,2)=(1,null) AS "NonNull=Null",
+ (1,null::integer)=(1,null) AS "Null=Null";
+</programlisting>
+<screen>
+ NonNull=Null | Null=Null
+--------------+-----------
+ \N           | \N
+</screen>
    </para>
   </sect3>
   <sect3 id="nullvalues-multielementcomparison-composite">
    <title>Row-wise Composite Involved Comparisons</title>
    <para>
-    In these three situations null values are considered equal to each other and greater than
-    all non-null value.
+    In these three situations, null values are considered equal to each other and greater than
+    all non-null valueS.
    </para>
-   <programlisting>
-    SELECT s, t,
-     s = t AS "Stored Equals Stored",
-     t &lt; (1,2) AS "Stored LT Constructor",
-     t = (1,null::integer) AS "Stored Equals Constructor"
-    FROM
-     (values (1,2)) AS s,
-     (values (1,null::integer)) AS t;
-   </programlisting>
-   <screen>
-       s   |  t   | Stored Equals Stored | Stored LT Constructor | Stored Equals Constructor
-    -------+------+----------------------+-----------------------+---------------------------
-     (1,2) | (1,) | f                    | f                     | t
-   </screen>
+<programlisting>
+SELECT s, t,
+ s = t AS "Stored Equals Stored",
+ t &lt; (1,2) AS "Stored LT Constructor",
+ t = (1,null::integer) AS "Stored Equals Constructor"
+FROM
+ (values (1,2)) AS s,
+ (values (1,null::integer)) AS t;
+</programlisting>
+<screen>
+   s   |  t   | Stored Equals Stored | Stored LT Constructor | Stored Equals Constructor
+-------+------+----------------------+-----------------------+---------------------------
+ (1,2) | (1,) | false                | false                 | true
+</screen>
   </sect3>
   <sect3 id="nullvalues-multielementcomparison-sqlconformance">
    <title>SQL Standard Conformance</title>
@@ -687,24 +709,24 @@
    Most functions, especially single argument functions, are defined with strict because without
    non-null values to act upon they cannot produce a meaningful result.  However, for multi-argument
    functions, especially <link linkend="xfunc-sql-variadic-functions">variadic functions</link>
-   like concatenate, null values often are simply ignored.
+   like concatenate, null values often are ignored.
    This can be different than the choice made by a binary operator performing the same function,
    like for concatenating text, but not always, like concatenating an element onto an array.
-   <programlisting>
-    SELECT
-     lower(null::text) AS "Lower",
-     left('text', null) AS "Left",
-     'one' || null AS "|| Text Op",
-     concat('one', null) AS "concat Text Func",
-     array_append(array[1], null) AS "append([], null)",
-     array[1]::integer[] || null::integer AS "[] || null",
-     array[1]::integer[] || null::integer[] AS "[] || null[]";
-   </programlisting>
-   <screen>
-     Lower | Left | || Text Op | concat Text Func | append([], null) | [] || null | [] || null[]
-    -------+------+------------+------------------+------------------+------------+--------------
-           |      |            | one              | {1,NULL}         | {1,NULL}   | {1}
-   </screen>
+<programlisting>
+SELECT
+ lower(null::text) AS "Lower",
+ left('text', null) AS "Left",
+ 'one' || null AS "|| Text Op",
+ concat('one', null) AS "concat Text Func",
+ array_append(array[1], null) AS "append([], null)",
+ array[1]::integer[] || null::integer AS "[] || null",
+ array[1]::integer[] || null::integer[] AS "[] || null[]";
+</programlisting>
+<screen>
+ Lower | Left | || Text Op | concat Text Func | append([], null) | [] || null | [] || null[]
+-------+------+------------+------------------+------------------+------------+--------------
+ \N    | \N   | \N         | one              | {1,NULL}         | {1,NULL}   | {1}
+</screen>
    In short, please read the documentation for the functions you use if they may receive null inputs
    to understand how they will behave.  Send a documentation comment pointing out any functions
    that do not behave strictly but whose actual behavior in the presence of null-valued input
@@ -719,22 +741,22 @@
    (which may be initialized to a non-null value, e.g., 0 for the count function)
    will remain unchanged even if the underlying processing
    function returns a null value, whether from being defined strict
-   or it simply returns a null value upon execution.  The aggregation
+   or it returns a null value upon execution.  The aggregation
    routine will usually ignore the null value and continue processing,
    as demonstrated in <literal>count(value)</literal> below.
-   <programlisting>
-    SELECT
-     count(*) AS "Count",
-     count(value) AS "Count Value",
-     count(null_examples) AS "Count Composite",
-     count(row(value, value)) AS "Count Row"
-    FROM null_examples;
-   </programlisting>
-   <screen>
-     Count | Count Value | Count Composite | Count Row
-    -------+-------------+-----------------+-----------
-         3 |           2 |               3 |         3
-   </screen>
+<programlisting>
+SELECT
+ count(*) AS "Count",
+ count(value) AS "Count Value",
+ count(null_examples) AS "Count Composite",
+ count(row(value, value)) AS "Count Row"
+FROM null_examples;
+</programlisting>
+<screen>
+ Count | Count Value | Count Composite | Count Row
+-------+-------------+-----------------+-----------
+     3 |           2 |               3 |         3
+</screen>
    Notice the "Count Row" outcome, though.  While we noted in the cardinal warning
    that a composite whose fields are all null values is indistinguishable from
    a null value of composite type, the count aggregate does indeed distinguish them,
@@ -748,24 +770,35 @@
   <title>Null Values When Filtering</title>
   <para>
    A <literal>WHERE</literal> clause that evaluates to a null value for a given row will exclude that row.
-   <programlisting>
-    SELECT id, value AS "Equals 1"
-    FROM null_examples
-    WHERE value = 1;
+   Note below that, due to tri-valued logic described in <xref linkend="nullvalues-cardinalrule"/>,
+   the row with an id of 2 is not included in either of the first two results.  The third result, using
+   <literal>IS NULL</literal>, finds that row.
+<programlisting>
+SELECT id, value AS "Equals 1"
+FROM null_examples
+WHERE value = 1;
+
+SELECT id, value AS "Not Equal to 1"
+FROM null_examples
+WHERE value != 1;
+
+SELECT id, value AS "IS NULL"
+FROM null_examples
+WHERE value IS NULL;
+</programlisting>
+<screen>
+ id | Equals 1
+----+----------
+  1 |        1
 
-    SELECT id, value AS "Not Equal to 1"
-    FROM null_examples
-    WHERE value != 1;
-   </programlisting>
-   <screen>
-     id | Equals 1
-    ----+----------
-      1 |        1
+ id | Not Equal to 1
+----+----------------
+  3 |              4
 
-     id | Not Equal to 1
-    ----+----------------
-      3 |              4
-   </screen>
+ id | IS NULL
+----+---------
+  2 |      \N
+</screen>
   </para>
  </sect2>
 
@@ -778,31 +811,28 @@
    While this seems like it would behave the same as a where clause, the choice here,
    when an expression evaluates to a null value, is to allow the row to be inserted
    - the same as a true result.
-   <programlisting>
-    BEGIN;
-    ALTER TABLE null_examples ADD CONSTRAINT value_not_1 CHECK (value != 1);
-    ROLLBACK;
-   </programlisting>
-   <screen>
-    BEGIN
-    ERROR:  check constraint "value_not_1" of relation "null_examples" is violated by some row
-    ROLLBACK
-   </screen>
-   <programlisting>
-    BEGIN;
-    ALTER TABLE null_examples ADD CONSTRAINT value_not_10 CHECK (value != 10);
-    ROLLBACK;
-   </programlisting>
-   <screen>
-    BEGIN
-    ALTER TABLE
-    ROLLBACK
-   </screen>
-   We are using a transaction (begin and rollback) and the alter table command to add two
-   constraints to our null_examples table.  The first constraint prohibits rows with a value
-   of 1, which our row with an id of 1 violates.  Prohibiting the value 10 definitely allows
-   rows with ids 1 and 3 to exist, and since we are not told that some row violates our
-   constraint the null value in the row with id 2 is being accepted as well.
+<programlisting>
+BEGIN;
+ALTER TABLE null_examples ADD CONSTRAINT value_not_1 CHECK (value != 1);
+ROLLBACK;
+</programlisting>
+<screen>
+ERROR:  check constraint "value_not_1" of relation "null_examples" is violated by some row
+</screen>
+<programlisting>
+BEGIN;
+ALTER TABLE null_examples ADD CONSTRAINT value_not_10 CHECK (value != 10);
+ROLLBACK;
+</programlisting>
+<screen>
+ALTER TABLE
+</screen>
+   We are using a transaction (<command>BEGIN</command> and <command>ROLLBACK</command>) and
+   the <command>ALTER TABLE</command> command to add two constraints to our null_examples table.
+   The first constraint prohibits rows with a value of 1, which our row with an id of 1 violates.
+   Prohibiting the value 10 definitely allows rows with ids 1 and 3 to exist, and since we are
+   not told that some row violates our constraint the null value in the row with id 2 is being
+   accepted as well.
   </para>
   <para>
    The <link linkend="ddl-constraints-not-null"><literal>NOT NULL</literal> column constraint</link>
@@ -819,35 +849,35 @@
    other values.  These features use <link linkend="nullvalues-cardinalrule">distinctness</link>
    instead of simple equality in order to handle a null value like a definite value equal to
    another null value and unequal to all other values.
-   <programlisting>
-    WITH vals (value) AS (VALUES (1), (NULL), (1), (2), (NULL))
-    SELECT
-     value,
-     count(*) AS "Count"
-    FROM vals
-    GROUP BY value
-    ORDER BY value;
-   </programlisting>
-   <screen>
-     value | Count
-    -------+-------
-         1 |     2
-         2 |     1
-           |     2
-   </screen>
-   <programlisting>
-    WITH vals (value) AS (VALUES (1), (NULL), (1), (2), (NULL))
-    SELECT DISTINCT value
-    FROM vals
-    ORDER BY value NULLS FIRST;
-   </programlisting>
-   <screen>
-     value
-    -------
-
-         1
-         2
-   </screen>
+<programlisting>
+WITH vals (value) AS (VALUES (1), (NULL), (1), (2), (NULL))
+SELECT
+ value,
+ count(*) AS "Count"
+FROM vals
+GROUP BY value
+ORDER BY value;
+</programlisting>
+<screen>
+ value | Count
+-------+-------
+     1 |     2
+     2 |     1
+    \N |     2
+</screen>
+<programlisting>
+WITH vals (value) AS (VALUES (1), (NULL), (1), (2), (NULL))
+SELECT DISTINCT value
+FROM vals
+ORDER BY value NULLS FIRST;
+</programlisting>
+<screen>
+ value
+-------
+    \N
+     1
+     2
+</screen>
   </para>
  </sect2>
 
@@ -859,25 +889,25 @@
    present null values before or after all non-null values.  To handle
    this, the <literal>ORDER BY</literal> clause will let you specify either
    <literal>NULLS FIRST</literal> or <literal>NULLS LAST</literal>.
-   <programlisting>
-    WITH vals (value) AS (VALUES (1), (NULL), (1), (2), (NULL))
-    SELECT value FROM vals
-    ORDER BY value DESC NULLS FIRST;
-   </programlisting>
-   <screen>
-     value
-    -------
-
-
-         2
-         1
-         1
-   </screen>
+<programlisting>
+WITH vals (value) AS (VALUES (1), (NULL), (1), (2), (NULL))
+SELECT value FROM vals
+ORDER BY value DESC NULLS FIRST;
+</programlisting>
+<screen>
+ value
+-------
+    \N
+    \N
+     2
+     1
+     1
+</screen>
   </para>
   <para>
    Note that when dealing with multi-element values the comparison behavior described in
-   <xref linkend="nullvalues-multielementcomparison"/> applies,
-   if the comparison determination rests upon comparing a null value to a non-null value
+   <xref linkend="nullvalues-multielementcomparison"/> applies:
+   if the comparison determination rests upon comparing a null value to a non-null value,
    the multi-element value with the null-valued component will sort greater than the one
    with a non-null component.
   </para>
@@ -894,33 +924,29 @@
    values are equal to each other.  This setting applies to all columns in the index.
   </para>
   <para>
-   <programlisting>
-    BEGIN;
-    CREATE UNIQUE INDEX value_nulls_distinct_implicit ON null_examples (value);
-    CREATE UNIQUE INDEX value_nulls_distinct_explicit ON null_examples (value) NULLS DISTINCT;
-    INSERT INTO null_examples VALUES (4, NULL);
-    ROLLBACK;
-   </programlisting>
-   <screen>
-    BEGIN
-    CREATE INDEX
-    CREATE INDEX
-    INSERT 0 1
-    ROLLBACK
-   </screen>
-   <programlisting>
-    BEGIN;
-    CREATE UNIQUE INDEX value_nulls_not_distinct_explicit ON null_examples (value) NULLS NOT DISTINCT;
-    INSERT INTO null_examples VALUES (4, NULL);
-    ROLLBACK;
-   </programlisting>
-   <screen>
-    BEGIN
-    CREATE INDEX
-    ERROR:  duplicate key value violates unique constraint "value_nulls_not_distinct_explicit"
-    DETAIL:  Key (value)=(null) already exists.
-    ROLLBACK
-   </screen>
+<programlisting>
+BEGIN;
+CREATE UNIQUE INDEX value_nulls_distinct_implicit ON null_examples (value);
+CREATE UNIQUE INDEX value_nulls_distinct_explicit ON null_examples (value) NULLS DISTINCT;
+INSERT INTO null_examples VALUES (4, NULL);
+ROLLBACK;
+</programlisting>
+<screen>
+CREATE INDEX
+CREATE INDEX
+INSERT 0 1
+</screen>
+<programlisting>
+BEGIN;
+CREATE UNIQUE INDEX value_nulls_not_distinct_explicit ON null_examples (value) NULLS NOT DISTINCT;
+INSERT INTO null_examples VALUES (4, NULL);
+ROLLBACK;
+</programlisting>
+<screen>
+CREATE INDEX
+ERROR:  duplicate key value violates unique constraint "value_nulls_not_distinct_explicit"
+DETAIL:  Key (value)=(null) already exists.
+</screen>
   </para>
   <para>
    For ordering, each column in the index gets its own specification of
@@ -939,13 +965,13 @@
  <sect2 id="nullvalues-partitionkeys">
   <title>Null Values in Partition Keys</title>
   <para>
-   Presently, PostgreSQL requires that all the columns of a partition key be included
-   in the primary key.  Furthermore, all columns used in a primary key have a not-null
-   column constraint applied to them.  Therefore, any partitioned table with a primary key
-   will only have non-null values in the partition key columns.
+   Presently, <productname>PostgreSQL</productname> requires that all the columns of a
+   partition key be included in the primary key.  Furthermore, all columns used in a primary
+   key must have a not-null column constraint applied to them.  Therefore, any partitioned table
+   with a primary key will only have non-null values in the partition key columns.
   </para>
   <para>
-   However, should you setup a situation where a partition key column can both: have a null value
+   However, should you set up a situation where a partition key column can both: have a null value
    and, null values in that key go to a specific partition, list-based routing will work as expected.
    There is presently no way to direct rows having null values in partition keys away from the
    default partition for range and hash partitioning.
@@ -955,44 +981,62 @@
  <sect2 id="nullvalues-settings">
   <title>Null-Valued Settings</title>
   <para>
-   There are none.  During initialization all settings are assigned a non-null value.
+   The value of a setting known to the system will never be null.  There is a bit of confusion
+   because the <function>current_setting</function> function has an operating mode where instead
+   of provoking an error when retrieving the value of a setting not known to the system it will
+   instead return a null value.  This null value should not be considered the value of the setting
+   but an error indicator.
+<programlisting>
+SELECT current_setting('example.string', false);
+SELECT current_setting('example.string', true);
+</programlisting>
+<screen>
+unrecognized configuration parameter "example.string"
+ current_setting
+-----------------
+ \N
+</screen>
+   The next paragraph discusses the corner case behavior when this
+   suggestion is not heeded.
   </para>
   <para>
-   This is mostly meaningful for <link linkend="runtime-config-custom">custom settings</link>,
-   thus this subsection focuses on <link linkend="config-setting-sql">SQL interaction</link>.
+   The corner case mentioned above is only meaningful for
+   <link linkend="runtime-config-custom">custom settings</link>,
+   thus this section focuses on <link linkend="config-setting-sql">SQL interaction</link>.
    Unlike settings created by extensions, custom settings can only be textual and the default
    value for text here is the empty string.
-   <programlisting>
-    SHOW example.string;
-    BEGIN;
-    SELECT set_config('example.string', NULL, true);
-    SELECT current_setting('example.string') IS NULL AS "Setting Is Null";
-    ROLLBACK;
-    SHOW example.string;
-    RESET example.string;
-    SHOW example.string;
-   </programlisting>
-   <screen>
-    ERROR:  unrecognized configuration parameter "example.string"
-    BEGIN
-     set_config
-    ------------
+<programlisting>
+-- The transaction markers are left here to emphasize the rollback behavior.
+SHOW example.string;
+BEGIN;
+SELECT set_config('example.string', NULL, true);
+SELECT current_setting('example.string') IS NULL AS "Setting Is Null";
+ROLLBACK;
+SHOW example.string;
+RESET example.string;
+SHOW example.string;
+</programlisting>
+<screen>
+ERROR:  unrecognized configuration parameter "example.string"
+BEGIN
+ set_config
+------------
 
 
-     Setting Is Null
-    -----------------
-     f
+ Setting Is Null
+-----------------
+ false
 
-    ROLLBACK
-     example.string
-    ----------------
+ROLLBACK
+ example.string
+----------------
 
 
-    RESET
-     example.string
-    ----------------
+RESET
+ example.string
+----------------
 
-   </screen>
+</screen>
    Notice two important behaviors: first, even though we passed in a null value to
    the <literal>set_config</literal> function, the <literal>current_setting</literal>
    function returned a non-null value, specifically the empty string.  Second, after ROLLBACK the
@@ -1001,8 +1045,8 @@
    (i.e., RESET does not restore the non-existence state.)
   </para>
   <para>
-    The other ways to specify settings do not have a means to specify null values,
-    a specific non-null value is required as part of the specification of the setting.
+    The other ways to specify settings do allow for null values;
+    a specific non-null value is required as part of the setting specification.
    </para>
  </sect2>
 
@@ -1010,60 +1054,71 @@
   <title>Null Values in JSON</title>
   <para>
    As noted in <xref linkend="json-type-mapping-table"/>, the JSON specification's
-   null value is assigned its own type unlike in SQL.  This introduces an inconsistency
-   since the JSON null type's value is itself non-null.  It is also comparable to any
-   other JSON type, returning false for equality, and itself, returning true for equality.
-   But an SQL value of json or jsonb type having a JSON null value is considered non-null in SQL.
-   <programlisting>
-    SELECT 'null'::json IS NULL AS "JSON null is NULL";
-   </programlisting>
-   <screen>
-     JSON null is NULL
-    -------------------
-     f
-   </screen>
+   null value is assigned its own type having a single constant value which can be
+   compared to all other JSON types with the expected non-null boolean result.
+   A consequence of this definition is that an SQL json or jsonb type containing
+   a JSON null value is seen as non-null in SQL.
+   (Note, while in SQL the capitalization of NULL is unimportant -
+   all-caps is just convention - JSON requires lowercase.)
+<programlisting>
+SELECT 'null'::json IS NULL AS "JSON null is NULL";
+</programlisting>
+<screen>
+ JSON null is NULL
+-------------------
+ false
+</screen>
    Additionally, the SQL operators and functions involving JSON key or array element selection,
    or construction from literals, require that a valid number or text value be supplied as an operand
    and so an SQL null value cannot be targeted by those operators and functions.
-   <programlisting>
-    SELECT to_json(null::text);
-   </programlisting>
-   <screen>
-     to_json
-    ---------
-
-   </screen>
+<programlisting>
+ SELECT to_json(null::text);
+</programlisting>
+<screen>
+ to_json
+---------
+ \N
+</screen>
    That all said, the system will convert an SQL null value to a JSON null value when in a
    composite type context.
-   <programlisting>
-    SELECT json_build_object('value', value)
-    FROM null_examples;
-   </programlisting>
-   <screen>
-     json_build_object
-    -------------------
-     {"value" : 1}
-     {"value" : null}
-     {"value" : 4}
-   </screen>
+<programlisting>
+SELECT json_build_object('value', value)
+FROM null_examples;
+</programlisting>
+<screen>
+ json_build_object
+-------------------
+ {"value" : 1}
+ {"value" : null}
+ {"value" : 4}
+</screen>
    And vice versa.
-   <programlisting>
-    SELECT *
-    FROM jsonb_to_recordset('[{"value":1},{"value":null},{"value":4}]'::jsonb) AS jtr (value integer);
-   </programlisting>
-   <screen>
-     value
-    -------
-         1
-
-         4
-   </screen>
+<programlisting>
+SELECT *
+FROM jsonb_to_recordset('[{"value":1},{"value":null},{"value":4}]'::jsonb) AS jtr (value integer);
+</programlisting>
+<screen>
+ value
+-------
+     1
+    \N
+     4
+</screen>
+   Or when a simple scalar JSON null is cast to an SQL type.
+<programlisting>
+SELECT 'null'::jsonb::numeric IS NULL AS "Cast jsonb NULL to SQL NULL";
+</programlisting>
+<screen>
+ Cast jsonb NULL to SQL NULL
+-----------------------------
+ true
+</screen>
   </para>
   <para>
    Aspects of null value handling within the internals of the JSON-related types are discussed
    in <xref linkend="datatype-json"/>,
    particularly in <xref linkend="datatype-jsonpath"/>.
-   This subsection is focused on how SQL null values are related to JSON null values.
+   This section is focused on how SQL null values are related to JSON null values.
   </para>
  </sect2>
 </sect1>
-- 
2.34.1

