воскресенье, 13 апреля 2014 г.

Многоликий NULL, часть I

Дыра это просто ничто, но вы можете в ней сломать шею.

Остин О'Мэлли

В этой статье я хочу с разных сторон посмотреть на NULL - явление в мире SQL фундаментальное. Все приведенные ниже примеры выполнены в СУБД Oracle 11gR2.

Создам таблицу для экспериментов и вставлю в нее несколько строк:

SQL> CREATE TABLE items (
  2      id NUMBER,
  3      name VARCHAR2(20)
  4  );
 
Table created
SQL> INSERT INTO items VALUES (1, 'скрепка');
 
1 row inserted
SQL> INSERT INTO items VALUES (2, NULL);
 
1 row inserted
SQL> INSERT INTO items VALUES (NULL, NULL);
 
1 row inserted
 
SQL> SELECT rownum, i.* FROM items i;
 
    ROWNUM         ID NAME
---------- ---------- --------------------
         1          1 скрепка
         2          2 
         3            

Указание литерала NULL для столбца name привело к тому, что значение в столбце name для строк с id равным 2 и 3 отсутствует. Таким образом, NULL - это ключевое слово, обозначающее отсутствие значения.

В разных языках программирования переменные, не инициализированные явно, получают самые разные значения. В зависимости от типа, от области видимости их значения могут оказаться либо непредсказуемыми, либо значениями по умолчанию для данного типа данных (0 для целых, пустая строка для строк и т.п.) В SQL нет возможности добавить в таблицу строку с "неинициализированным" столбцом: нужно явно указать значение или признак отсутствия значения, NULL, в команде INSERT, либо заранее явно объявить для столбца значение по умолчанию.

Объявим NULL в качестве значения по умолчанию для столбца name, после чего можно не указывать значение для этого столбца в команде INSERT:

ALTER TABLE items MODIFY (name VARCHAR2(20) DEFAULT NULL);

SQL> INSERT INTO items (id) VALUES (3);
 
1 row inserted
 
SQL> SELECT rownum, i.* FROM items i;
 
    ROWNUM         ID NAME
---------- ---------- --------------------
         1          1 скрепка
         2          2 
         3            
         4          3 

Для проверки столбца на отсутствие (присутствие) в нем значения используется оператор IS NULL (IS NOT NULL), а операторы сравнения с NULL всегда дают NULL, что интерпретируется как отрицательный результат сравнения:

SQL> SELECT rownum, i.* FROM items i WHERE name IS NULL;
 
    ROWNUM         ID NAME
---------- ---------- --------------------
         1          2 
         2            
         3          3 

SQL> SELECT rownum, i.* FROM items i WHERE name = NULL;
 
    ROWNUM         ID NAME
---------- ---------- --------------------

SQL> SELECT rownum, i.* FROM items i WHERE name > NULL;
 
    ROWNUM         ID NAME
---------- ---------- --------------------

Выходит, значение 'скрепка' не равно NULL, и отсутствие значения тоже не равно NULL. Иначе второй SELECT вернул хотя бы что-то. Вот еще одна компактная и убедительная иллюстрация того, как работает NULL:

SQL> SELECT * FROM dual WHERE NULL = NULL;
 
DUMMY
-----

SQL> SELECT * FROM dual WHERE NULL != NULL;
 
DUMMY
-----

SQL> SELECT * FROM dual WHERE NULL IS NULL;
 
DUMMY
-----
X

SQL> SELECT * FROM dual WHERE NULL IS NOT NULL;
 
DUMMY
-----
 

Итак, NULL означает отсутствие значения, и отсутствие значения есть отсутствие значения (NULL IS NULL).

Чтобы не допустить отсутствия значения в столбце таблицы, необходимо объявить ограничение целостности NOT NULL для этого столбца. Сделаем это для столбца id, предварительно удалив строку, где столбец id не содержит значения:

SQL> DELETE FROM items WHERE id IS NULL;
1 row deleted
 
SQL> ALTER TABLE items MODIFY (id NUMBER NOT NULL);
Table altered

SQL> INSERT INTO items (id) VALUES (NULL);
 
ORA-01400: cannot insert NULL into ("AY"."ITEMS"."ID")

Теперь в таблице не может быть строки, в которой столбец id пуст.

Интересно, что в системном словаре Oracle ограничение целостности NOT NULL оказывается частным случаем ограничения CHECK:

SQL> SELECT constraint_name, constraint_type, search_condition
  2  FROM user_constraints
  3  WHERE TABLE_NAME='ITEMS';
 
CONSTRAINT_NAME CONSTRAINT_TYPE SEARCH_CONDITION
--------------- --------------- ----------------------------------------
SYS_C0010385    C               "ID" IS NOT NULL

Другой способ гарантировать присутствие значения в столбце - включить этот столбец в первичный ключ таблицы. То есть, создать ограничение целостности PRIMARY KEY:

-- убираю ранее объявленное ограничение целостности NOT NULL
SQL> ALTER TABLE items MODIFY (id NUMBER NULL);
Table altered

-- объявляю первичный ключ
SQL> ALTER TABLE items ADD PRIMARY KEY (id);
Table altered

SQL> SELECT constraint_name, constraint_type, search_condition
  2  FROM user_constraints
  3  WHERE TABLE_NAME='ITEMS';
 
CONSTRAINT_NAME CONSTRAINT_TYPE SEARCH_CONDITION
--------------- --------------- ----------------------------------------
SYS_C0010388                   P               

Теперь первичный ключ - единственное ограничение целостности на столбце id, и оно гарантирует

  1. уникальность значений в столбце и
  2. наличие значений в столбце,

даже при отсутствии ограничения целостности NOT NULL:

SQL> -- id не может быть пустым
SQL> INSERT INTO items VALUES (NULL, NULL);
 
ORA-01400: cannot insert NULL into ("AY"."ITEMS"."ID")

SQL> INSERT INTO items VALUES (4, 'ножницы');
1 row inserted

SQL> -- id должен быть уникален
SQL> INSERT INTO items VALUES (4, 'карандаш');
 
ORA-00001: unique constraint (AY.SYS_C0010388) violated
 
SQL> SELECT * FROM items;
 
        ID NAME
---------- --------------------
         4 ножницы
         1 скрепка
         2 
         3 

А вот ограничение целостности UNIQUE (уникальный ключ) допускает отсутствие значений в столбце, гарантируя лишь, что все присутствующие значения уникальны:

SQL> ALTER TABLE items ADD CONSTRAINT items_name_uk UNIQUE (name);
Table altered

SQL> INSERT INTO items VALUES (5, NULL);
1 row inserted

SQL> INSERT INTO items VALUES (6, 'степлер');
1 row inserted

SQL> -- значение name должно быть уникальным
SQL> INSERT INTO items VALUES (7, 'степлер');
 
ORA-00001: unique constraint (AY.ITEMS_NAME_UK) violated

SQL> INSERT INTO items VALUES (8, 'дырокол');
 
1 row inserted
 
-- значение name может отсутствовать во многих строках
SQL> SELECT * FROM items ORDER BY id;
 
        ID NAME
---------- --------------------
         1 скрепка
         2 
         3 
         4 ножницы
         5 
         6 степлер
         8 дырокол
 
7 rows selected

Как известно, и первичный, и уникальный ключи используют "за кулисами" уникальный индекс для обеспечения уникальности значений столбца:

SQL> SELECT index_name, uniqueness
  2  FROM user_indexes
  3  WHERE table_name = 'ITEMS';
 
INDEX_NAME                     UNIQUENESS
------------------------------ ----------
SYS_C0010388                   UNIQUE
ITEMS_NAME_UK                  UNIQUE

Отдельного внимания заслуживают взимоотношения NULL и строковых типов Oracle. В Oracle пустая строка VARCHAR2 трактуется как NULL, отсутствие значения:

SQL> INSERT INTO items VALUES (9, '');
1 row inserted

SQL> SELECT * FROM items WHERE name = '';
 
        ID NAME
---------- --------------------
 
SQL> SELECT * FROM items WHERE name IS NULL;
 
        ID NAME
---------- --------------------
         5 
         2 
         3 
         9 

SQL> INSERT INTO items VALUES ('', '');
 
ORA-01400: cannot insert NULL into ("AY"."ITEMS"."ID")

Продемонстрирую практическую эквивалентность пустой строки VARCHAR2 и NULL при их использовании с некоторыми функциями SQL:

SQL> SELECT
  2      LENGTH('') len_empty,
  3      LENGTH(NULL) len_null,
  4      TO_NUMBER('') empty_to_number,
  5      TO_NUMBER(NULL) null_to_number,
  6      NVL('', 'IS NULL') nvl_empty,
  7      NVL(NULL, 'IS NULL') nvl_null
  8  FROM dual;
 
 LEN_EMPTY   LEN_NULL EMPTY_TO_NUMBER NULL_TO_NUMBER NVL_EMPTY NVL_NULL
---------- ---------- --------------- -------------- --------- --------
                                                     IS NULL   IS NULL

Однако, пустая строка CHAR - другой случай, поскольку строки CHAR дополняются пробелами до объявленной длины:

SQL> set serveroutput on
SQL> DECLARE
  2      c CHAR(1) := '';
  3  BEGIN
  4      IF c IS NULL THEN
  5          DBMS_OUTPUT.PUT_LINE('IS NULL');
  6      END IF;
  7      IF c = '' THEN
  8          DBMS_OUTPUT.PUT_LINE('= ''''');
  9      END IF;
 10      DBMS_OUTPUT.PUT_LINE(LENGTH(c) || ' [' || c || ']');
 11  END;
 12  /
 
1 [ ]
 
PL/SQL procedure successfully completed

Попробуем NULL в качестве операнда в операциях со строками, числами и датами:

SQL> SELECT
  2      '3.145' || NULL str,
  3      3.14 + NULL num1,
  4      3.14 - NULL num2,
  5      3.14 * NULL num3,
  6      3.14 / NULL num4,
  7      DATE '2014-03-08' + NULL dat1,
  8      DATE '2014-03-08' - NULL dat2
  9  FROM dual;
 
STR         NUM1       NUM2       NUM3       NUM4 DAT1        DAT2
----- ---------- ---------- ---------- ---------- ----------- -----------
3.145                                                         

В выражении со строкой NULL работает как пустая строка, присоединение которой к другой строке не меняет последнюю. Иными словами, оператор конкатенации игнорирует NULL'ы. А в операциях с числами и датами операнд NULL приводит к результату NULL. Еще одна демонстрация этого:

SQL> SELECT *
  2  FROM dual
  3  WHERE NULL * 5 IS NULL
  4      AND NULL + SYSDATE IS NULL;
 
DUMMY
-----
X

Логично, что подмешивая в выражение неизвестное значение (еще одна ипостась NULL), получаем неизвестный результат. Что для практических целей равнозначно отсутствию результата.

Выше мы видели, что операции сравнения с NULL также возвращают NULL.

Теперь посмотрим на поведение функций SQL, которым передается NULL в качестве аргумента. Начну со скалярных функций, после чего перейду к агрегатным.

Подавляющее большинство встроенных скалярных функций Oracle, получив NULL в качестве агрумента, возвращают NULL:

SQL> SELECT UPPER(NULL) upper,
  2      INSTR(NULL, 'qwerty') instr1,
  3      INSTR('qwe', NULL) instr2,
  4      SUBSTR(NULL, 3, 5) substr1,
  5      SUBSTR('qwerty', NULL) substr2,
  6      GREATEST(1, 21, NULL) greatest
  7  FROM dual;
 
UPPER     INSTR1     INSTR2 SUBSTR1 SUBSTR2   GREATEST
----- ---------- ---------- ------- ------- ----------

Есть несколько функций, которые не следуют указанному правилу: REPLACE, DECODE, NVL и NVL2. Если второй аргумент REPLACE есть NULL, то возвращается первый аргумент без изменений. Работа функций DECODE, NVL и NVL2 связана с выбором из нескольких вариантов. Функции NVL и NVL2 способны превращать отсутствие значения в значение:

SQL> SELECT
  2      NVL(NULL, '1-й аргумент IS NULL'),
  3      NVL2('', '1-й аргумент IS NOT NULL', '1-й аргумент IS NULL')
  4  FROM dual;
 
NVL(NULL,'1-ЙАРГУМЕНТ          NVL2('','1-ЙАРГУМЕНТI
------------------------------ ------------------------------
1-й аргумент IS NULL           1-й аргумент IS NULL
 
SQL> SELECT name,
  2      NVL(name, 'не-знаю-что') nvl,
  3      NVL2(name, INITCAP(name), 'Не-знаю-что') nvl2
  4  FROM items;
 
NAME                 NVL                  NVL2
-------------------- -------------------- --------------------
ножницы              ножницы              Ножницы
                     не-знаю-что          Не-знаю-что
один                 один                 Один
                     не-знаю-что          Не-знаю-что
степлер              степлер              Степлер
                     не-знаю-что          Не-знаю-что
дырокол              дырокол              Дырокол
                     не-знаю-что          Не-знаю-что
 
8 rows selected

Функция DECODE и оператор CASE по разному (!) справляются с NULL в качестве аргумента (операнда):

SQL> SELECT
  2      DECODE('', NULL, 'IS NULL', 'IS NOT NULL') decode,
  3      CASE '' WHEN NULL THEN 'IS NULL' ELSE 'IS NOT NULL' END case1,
  4      CASE WHEN '' IS NULL THEN 'IS NULL' ELSE 'IS NOT NULL' END case2
  5  FROM dual;
 
DECODE  CASE1       CASE2
------- ----------- -----------
IS NULL IS NOT NULL IS NULL

Обратите внимание, что DECODE приравнивает 1-ый агрумент '' ко второму аргументу NULL, а CASE в строке 3 не приравнивает пустую строку '' к NULL. Следовательно, если анализируемое значение может оказаться пустым, то нужно использовать форму оператора CASE, как в строке 4 приведенной команды SELECT.

Перейду к агрегатным функциям. Если вставить в таблицу несколько строк, таких, что во всех столбцах отсутствуют значения, то получим такой любопытный случай:

SQL> CREATE TABLE nullrows (id NUMBER, name VARCHAR2(20));
Table created
 
SQL> INSERT INTO nullrows SELECT NULL, NULL FROM all_users;
 
14 rows inserted

SQL> SELECT COUNT(*), COUNT(id), COUNT(name) FROM nullrows;
 
  COUNT(*)  COUNT(ID) COUNT(NAME)
---------- ---------- -----------
        14          0           0

Как видим, COUNT(*) считает строки, во всех столбцах которых отсутствуют значения, а COUNT(id) и COUNT(name) не считают отсутствующие значения! Подобным же образом отсутствие значений игнорируется и другими агрегатными функциями.

Следующий запрос генерирует 4 строки с NULL и 1 в столбце x:

SQL> SELECT ROWNUM, DECODE(MOD(ROWNUM, 2), 0, NULL, 1) x
  2  FROM dual CONNECT BY level < 5;
 
    ROWNUM X
---------- -
         1 1
         2 
         3 1
         4 

Убедимся, что агрегатные функции просто игнорируют отсутствие значений в столбце:

SQL> SELECT COUNT(x), SUM(x), AVG(x), MAX(x)
  2  FROM (
  3   SELECT DECODE(MOD(ROWNUM, 2), 0, NULL, 1) x
  4   FROM dual CONNECT BY level < 5
  5  );
 
  COUNT(X)     SUM(X)     AVG(X) MAX(X)
---------- ---------- ---------- ------
         2          2          1 1

В статье Пользовательские агрегатные функции в Oracle показано, что Oracle передает в пользовательские агрегатные функции только известные значения и не передает NULL.

В следующий раз я рассмотрю взаимоотношения NULL cо значениями типа BOOLEAN в PL/SQL, некоторые подзапросы и иные вещи, связанные с многоликим NULL в СУБД Oracle.

Комментариев нет:

Отправить комментарий