NULL セマンティクス

説明

テーブルは行の集合で構成され、各行は列の集合を含みます。列はデータ型に関連付けられており、エンティティの特定の属性を表します(たとえば、ageperson と呼ばれるエンティティの列です)。行が作成された時点で、行に固有の列の値が不明な場合があります。SQL では、このような値は NULL として表されます。このセクションでは、さまざまな演算子、式、およびその他の SQL 構成要素における NULL 値の処理のセマンティクスについて詳しく説明します。

  1. 比較演算子における NULL 処理
  2. 論理演算子における NULL 処理
  3. 式における NULL 処理
    1. NULL 非許容式における NULL 処理
    2. NULL 値オペランドを処理できる式における NULL 処理
    3. 組み込み集約式における NULL 処理
  4. WHERE、HAVING、および JOIN 条件における NULL 処理
  5. GROUP BY および DISTINCT における NULL 処理
  6. ORDER BY における NULL 処理
  7. UNION、INTERSECT、EXCEPT における NULL 処理
  8. EXISTS および NOT EXISTS サブクエリにおける NULL 処理
  9. IN および NOT IN サブクエリにおける NULL 処理

person という名前のテーブルのスキーマレイアウトとデータを示します。データには age 列に NULL 値が含まれており、このテーブルは以降のセクションのさまざまな例で使用されます。
テーブル: person

ID 名前 年齢
100 Joe 30
200 Marry NULL
300 Mike 18
400 Fred 50
500 Albert NULL
600 Michelle 30
700 Dan 50

比較演算子

Apache Spark は、「>」、「>=」、「=」、「<」、「<=` などの標準比較演算子をサポートしています. これらの演算子の結果は、オペランドのいずれかまたは両方が不明または NULL の場合、不明または NULL になります。NULL 値を等価比較するために、Spark は NULL セーフ等価演算子(「<=>」)を提供します.これは、オペランドのいずれかが NULL の場合は False を返し、両方のオペランドが NULL の場合は `True` を返します。次の表は、一方または両方のオペランドが NULL の場合の比較演算子の動作を示しています.

左オペランド 右オペランド > >= = < <= <=>
NULL 任意の値 NULL NULL NULL NULL NULL False
任意の値 NULL NULL NULL NULL NULL NULL False
NULL NULL NULL NULL NULL NULL NULL True

-- Normal comparison operators return `NULL` when one of the operand is `NULL`.
SELECT 5 > null AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

-- Normal comparison operators return `NULL` when both the operands are `NULL`.
SELECT null = null AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

-- Null-safe equal operator return `False` when one of the operand is `NULL`
SELECT 5 <=> null AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|            false|
+-----------------+

-- Null-safe equal operator return `True` when one of the operand is `NULL`
SELECT NULL <=> NULL;
+-----------------+
|expression_output|
+-----------------+
|             true|
+-----------------+

論理演算子

Spark は、ANDORNOT などの標準論理演算子をサポートしています。これらの演算子は、引数として ブール式を取り、ブール値を返します.

次の表は、一方または両方のオペランドが NULL の場合の論理演算子の動作を示しています.

左オペランド 右オペランド OR AND
True NULL True NULL
False NULL NULL False
NULL True True NULL
NULL False NULL False
NULL NULL NULL NULL
オペランド NOT
NULL NULL

-- Normal comparison operators return `NULL` when one of the operands is `NULL`.
SELECT (true OR null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             true|
+-----------------+

-- Normal comparison operators return `NULL` when both the operands are `NULL`.
SELECT (null OR false) AS expression_output
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

-- Null-safe equal operator returns `False` when one of the operands is `NULL`
SELECT NOT(null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

比較演算子と論理演算子は、Spark では式として扱われます。これら 2 種類の式以外に、Spark は関数式、キャスト式など、他の形式の式もサポートしています。Spark の式は、大きく次のように分類できます.

NULL 非許容式

NULL 非許容式は、式の 1 つ以上の引数が NULL の場合に NULL を返し、ほとんどの式がこのカテゴリに分類されます.

SELECT concat('John', null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

SELECT positive(null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

SELECT to_date(null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

NULL 値オペランドを処理できる式

このクラスの式は、NULL 値を処理するように設計されています。式の結果は、式自体によって異なります。たとえば、関数式 isnull は NULL 入力に対して true を返し、非 NULL 入力に対して false を返しますが、関数 coalesce はオペランドリストの最初の非 NULL 値を返します。ただし、coalesce は、すべてのオペランドが NULL の場合に NULL を返します。以下は、このカテゴリの式の不完全なリストです.

SELECT isnull(null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             true|
+-----------------+

-- Returns the first occurrence of non `NULL` value.
SELECT coalesce(null, null, 3, null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|                3|
+-----------------+

-- Returns `NULL` as all its operands are `NULL`. 
SELECT coalesce(null, null, null, null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|             null|
+-----------------+

SELECT isnan(null) AS expression_output;
+-----------------+
|expression_output|
+-----------------+
|            false|
+-----------------+

組み込み集約式

集約関数は、一連の入力行を処理することによって単一の結果を計算します。以下は、集約関数による NULL 値の処理方法のルールです.

-- `count(*)` does not skip `NULL` values.
SELECT count(*) FROM person;
+--------+
|count(1)|
+--------+
|       7|
+--------+

-- `NULL` values in column `age` are skipped from processing.
SELECT count(age) FROM person;
+----------+
|count(age)|
+----------+
|         5|
+----------+

-- `count(*)` on an empty input set returns 0. This is unlike the other
-- aggregate functions, such as `max`, which return `NULL`.
SELECT count(*) FROM person where 1 = 0;
+--------+
|count(1)|
+--------+
|       0|
+--------+

-- `NULL` values are excluded from computation of maximum value.
SELECT max(age) FROM person;
+--------+
|max(age)|
+--------+
|      50|
+--------+

-- `max` returns `NULL` on an empty input set.
SELECT max(age) FROM person where 1 = 0;
+--------+
|max(age)|
+--------+
|    null|
+--------+

WHERE、HAVING、および JOIN 句の条件式

WHEREHAVING 演算子は、ユーザー指定の条件に基づいて行をフィルタリングします。JOIN 演算子は、結合条件に基づいて 2 つのテーブルの行を結合するために使用されます。3 つの演算子すべてについて、条件式はブール式であり、`True、False、または不明(NULL)` を返すことができます。条件の結果が True の場合、「満たされます」。

-- Persons whose age is unknown (`NULL`) are filtered out from the result set.
SELECT * FROM person WHERE age > 0;
+--------+---+
|    name|age|
+--------+---+
|Michelle| 30|
|    Fred| 50|
|    Mike| 18|
|     Dan| 50|
|     Joe| 30|
+--------+---+

-- `IS NULL` expression is used in disjunction to select the persons
-- with unknown (`NULL`) records.
SELECT * FROM person WHERE age > 0 OR age IS NULL;
+--------+----+
|    name| age|
+--------+----+
|  Albert|null|
|Michelle|  30|
|    Fred|  50|
|    Mike|  18|
|     Dan|  50|
|   Marry|null|
|     Joe|  30|
+--------+----+

-- Person with unknown(`NULL`) ages are skipped from processing.
SELECT age, count(*) FROM person GROUP BY age HAVING max(age) > 18;
+---+--------+
|age|count(1)|
+---+--------+
| 50|       2|
| 30|       2|
+---+--------+

-- A self join case with a join condition `p1.age = p2.age AND p1.name = p2.name`.
-- The persons with unknown age (`NULL`) are filtered out by the join operator.
SELECT * FROM person p1, person p2
    WHERE p1.age = p2.age
    AND p1.name = p2.name;
+--------+---+--------+---+
|    name|age|    name|age|
+--------+---+--------+---+
|Michelle| 30|Michelle| 30|
|    Fred| 50|    Fred| 50|
|    Mike| 18|    Mike| 18|
|     Dan| 50|     Dan| 50|
|     Joe| 30|     Joe| 30|
+--------+---+--------+---+

-- The age column from both legs of join are compared using null-safe equal which
-- is why the persons with unknown age (`NULL`) are qualified by the join.
SELECT * FROM person p1, person p2
    WHERE p1.age <=> p2.age
    AND p1.name = p2.name;
+--------+----+--------+----+
|    name| age|    name| age|
+--------+----+--------+----+
|  Albert|null|  Albert|null|
|Michelle|  30|Michelle|  30|
|    Fred|  50|    Fred|  50|
|    Mike|  18|    Mike|  18|
|     Dan|  50|     Dan|  50|
|   Marry|null|   Marry|null|
|     Joe|  30|     Joe|  30|
+--------+----+--------+----+

集約演算子(GROUP BY、DISTINCT)

前のセクション 比較演算子 で説明したように、2 つの NULL 値は等しくありません。ただし、グループ化と個別処理のために、NULL データを持つ 2 つ以上の値は同じバケットにグループ化されます。この動作は、SQL 標準および他のエンタープライズデータベース管理システムに準拠しています.

-- `NULL` values are put in one bucket in `GROUP BY` processing.
SELECT age, count(*) FROM person GROUP BY age;
+----+--------+
| age|count(1)|
+----+--------+
|null|       2|
|  50|       2|
|  30|       2|
|  18|       1|
+----+--------+

-- All `NULL` ages are considered one distinct value in `DISTINCT` processing.
SELECT DISTINCT age FROM person;
+----+
| age|
+----+
|null|
|  50|
|  30|
|  18|
+----+

ソート演算子(ORDER BY 句)

Spark SQL は、ORDER BY 句での NULL 順序指定をサポートしています。Spark は、NULL 順序指定に応じて、すべての NULL 値を最初または最後に配置することにより、ORDER BY 句を処理します。デフォルトでは、すべての NULL 値が最初に配置されます.

-- `NULL` values are shown at first and other values
-- are sorted in ascending way.
SELECT age, name FROM person ORDER BY age;
+----+--------+
| age|    name|
+----+--------+
|null|   Marry|
|null|  Albert|
|  18|    Mike|
|  30|Michelle|
|  30|     Joe|
|  50|    Fred|
|  50|     Dan|
+----+--------+

-- Column values other than `NULL` are sorted in ascending
-- way and `NULL` values are shown at the last.
SELECT age, name FROM person ORDER BY age NULLS LAST;
+----+--------+
| age|    name|
+----+--------+
|  18|    Mike|
|  30|Michelle|
|  30|     Joe|
|  50|     Dan|
|  50|    Fred|
|null|   Marry|
|null|  Albert|
+----+--------+

-- Columns other than `NULL` values are sorted in descending
-- and `NULL` values are shown at the last.
SELECT age, name FROM person ORDER BY age DESC NULLS LAST;
+----+--------+
| age|    name|
+----+--------+
|  50|    Fred|
|  50|     Dan|
|  30|Michelle|
|  30|     Joe|
|  18|    Mike|
|null|   Marry|
|null|  Albert|
+----+--------+

集合演算子(UNION、INTERSECT、EXCEPT)

集合演算子のコンテキストでは、NULL 値は NULL セーフな方法で等価比較されます。つまり、行を比較する場合、通常の EqualTo=)演算子とは異なり、2 つの NULL 値は等しいと見なされます.

CREATE VIEW unknown_age SELECT * FROM person WHERE age IS NULL;

-- Only common rows between two legs of `INTERSECT` are in the 
-- result set. The comparison between columns of the row are done
-- in a null-safe manner.
SELECT name, age FROM person
    INTERSECT
    SELECT name, age from unknown_age;
+------+----+
|  name| age|
+------+----+
|Albert|null|
| Marry|null|
+------+----+

-- `NULL` values from two legs of the `EXCEPT` are not in output. 
-- This basically shows that the comparison happens in a null-safe manner.
SELECT age, name FROM person
    EXCEPT
    SELECT age FROM unknown_age;
+---+--------+
|age|    name|
+---+--------+
| 30|     Joe|
| 50|    Fred|
| 30|Michelle|
| 18|    Mike|
| 50|     Dan|
+---+--------+

-- Performs `UNION` operation between two sets of data. 
-- The comparison between columns of the row ae done in
-- null-safe manner.
SELECT name, age FROM person
    UNION 
    SELECT name, age FROM unknown_age;
+--------+----+
|    name| age|
+--------+----+
|  Albert|null|
|     Joe|  30|
|Michelle|  30|
|   Marry|null|
|    Fred|  50|
|    Mike|  18|
|     Dan|  50|
+--------+----+

EXISTS/NOT EXISTS サブクエリ

Spark では、WHERE 句内で EXISTS および NOT EXISTS 式を使用できます。これらは、TRUE または FALSE のいずれかを返すブール式です。言い換えれば、EXISTS はメンバーシップ条件であり、参照するサブクエリが 1 つ以上の行を返す場合に TRUE を返します。同様に、NOT EXISTS は非メンバーシップ条件であり、サブクエリから行が返されないか、ゼロ行が返される場合に TRUE を返します.

これら 2 つの式は、サブクエリの結果に NULL が存在しても影響を受けません。通常は高速です。NULL を認識するための特別な規定なしで、セミジョイン/アンチセミジョインに変換できるためです.

-- Even if subquery produces rows with `NULL` values, the `EXISTS` expression
-- evaluates to `TRUE` as the subquery produces 1 row.
SELECT * FROM person WHERE EXISTS (SELECT null);
+--------+----+
|    name| age|
+--------+----+
|  Albert|null|
|Michelle|  30|
|    Fred|  50|
|    Mike|  18|
|     Dan|  50|
|   Marry|null|
|     Joe|  30|
+--------+----+

-- `NOT EXISTS` expression returns `FALSE`. It returns `TRUE` only when
-- subquery produces no rows. In this case, it returns 1 row.
SELECT * FROM person WHERE NOT EXISTS (SELECT null);
+----+---+
|name|age|
+----+---+
+----+---+

-- `NOT EXISTS` expression returns `TRUE`.
SELECT * FROM person WHERE NOT EXISTS (SELECT 1 WHERE 1 = 0);
+--------+----+
|    name| age|
+--------+----+
|  Albert|null|
|Michelle|  30|
|    Fred|  50|
|    Mike|  18|
|     Dan|  50|
|   Marry|null|
|     Joe|  30|
+--------+----+

IN/NOT IN サブクエリ

Sparkでは、クエリ`WHERE`句内で`IN`式と`NOT IN`式を使用できます。`EXISTS`式とは異なり、`IN`式は`TRUE`、`FALSE`、または`UNKNOWN (NULL)`値を返すことができます。概念的には、`IN`式は、論理和演算子(`OR`)で区切られた一連の等価条件と意味的に等価です。たとえば、`c1 IN (1, 2, 3)`は、` (C1 = 1 OR c1 = 2 OR c1 = 3)`と意味的に等価です。

`NULL`値の処理に関しては、比較演算子(`=`)および論理演算子(`OR`)における`NULL`値の処理からセマンティクスを推測できます。要約すると、`IN`式の結果を計算するためのルールは次のとおりです。

`NOT IN`は、入力値に関係なく、リストに`NULL`が含まれている場合は常に`UNKNOWN`を返します。これは、値が`NULL`を含むリストにない場合、`IN`が`UNKNOWN`を返し、`NOT UNKNOWN`が再び`UNKNOWN`になるためです。

-- The subquery has only `NULL` value in its result set. Therefore,
-- the result of `IN` predicate is UNKNOWN.
SELECT * FROM person WHERE age IN (SELECT null);
+----+---+
|name|age|
+----+---+
+----+---+

-- The subquery has `NULL` value in the result set as well as a valid 
-- value `50`. Rows with age = 50 are returned. 
SELECT * FROM person
    WHERE age IN (SELECT age FROM VALUES (50), (null) sub(age));
+----+---+
|name|age|
+----+---+
|Fred| 50|
| Dan| 50|
+----+---+

-- Since subquery has `NULL` value in the result set, the `NOT IN`
-- predicate would return UNKNOWN. Hence, no rows are
-- qualified for this query.
SELECT * FROM person
    WHERE age NOT IN (SELECT age FROM VALUES (50), (null) sub(age));
+----+---+
|name|age|
+----+---+
+----+---+