W poprzednim poście zaczęliśmy opowiadanie o tym, czym jest NULL i jak się zachowuje. Dzisiejszym postem kontynuujemy ten temat.

W SQL wyrażenia, które zawierają w sobie NULL zawsze zwracają nieznany wynik. Przykładem mogą być takie operacje:

mysql> SELECT NULL, 5+NULL, CONCAT('tekst',NULL), FROM_UNIXTIME(NULL);
+------+--------+----------------------+---------------------+
| NULL | 5+NULL | CONCAT('tekst',NULL) | FROM_UNIXTIME(NULL) |
+------+--------+----------------------+---------------------+
| NULL |   NULL | NULL                 | NULL                |
+------+--------+----------------------+---------------------+
1 row in set (0.00 sec)

To samo będzie w przypadku użycia wyrażenia z NULL w warunku WHERE:

mysql> SELECT * FROM tab_null;
+------+------+
| a    | b    |
+------+------+
|    0 |      |
|    0 | NULL |
| NULL | NULL |
+------+------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM tab_null WHERE b=NULL;
Empty set (0.00 sec)

Aby sprawdzić, które rekordy zawierają NULL w kolumnie ‘b’ konieczne jest zastosowanie operatora IS NULL. Analogicznie, aby sprawdzić które nie zawierają NULL wykorzystujemy IS NOT NULL:

mysql> SELECT * FROM tab_null WHERE b IS NULL;
+------+------+
| a    | b    |
+------+------+
|    0 | NULL |
| NULL | NULL |
+------+------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM tab_null WHERE b IS NOT NULL;
+------+------+
| a    | b    |
+------+------+
|    0 |      |
+------+------+
1 row in set (0.00 sec)

Jeśli chcemy wyciągnąć informację o rekordach z pustą kolumną ‘b’ wystarczy oczywiście standardowe zapytanie:

mysql> SELECT * FROM tab_null WHERE b='';
+------+------+
| a    | b    |
+------+------+
|    0 |      |
+------+------+
1 row in set (0.00 sec)

Jeśli stosujemy funkcje agregujące kolumny typu COUNT(), MAX(), SUM(), MIN() itp. ignorują one rekordy z wartością NULL. Odstępstwem jest COUNT(*) – tak wywołana zlicza wszystkie rekordy a nie wartości występujące w kolumnie. Przykładowo:

mysql> SELECT * FROM tab_null;
+------+------+
| a    | b    |
+------+------+
|    0 |      |
|    0 | NULL |
| NULL | NULL |
+------+------+
3 rows in set (0.00 sec)

mysql> SELECT COUNT(a), COUNT(b), COUNT(*) FROM tab_null;
+----------+----------+----------+
| COUNT(a) | COUNT(b) | COUNT(*) |
+----------+----------+----------+
|        2 |        1 |        3 |
+----------+----------+----------+
1 row in set (0.00 sec)

W przypadku zastosowania sortowania NULL traktowana jest jako minus nieskończoność – będzie na początku gdy sortujemy rosnąco i na końcu, gdy sortujemy malejąco.

mysql> SELECT * FROM tab_null ORDER BY b ASC;
+------+------+
| a    | b    |
+------+------+
|    0 | NULL |
| NULL | NULL |
|    0 |      |
+------+------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM tab_null ORDER BY b DESC;
+------+------+
| a    | b    |
+------+------+
|    0 |      |
|    0 | NULL |
| NULL | NULL |
+------+------+
3 rows in set (0.00 sec)

Innymi ciekawymi cechami NULL jest np. to, że jeśli do kolumny o typie TIMESTAMP wrzucimy wartość NULL, to w efekcie dodaną do tabeli wartością tej kolumny będzie aktualna data i czas.

Edit.

Powyższe jest prawdziwe tylko dla tabel tworzonych w sposób następujący:

CREATE TABLE tab (a INT, b TIMESTAMP);

W takiej sytuacji MySQL domyślnie dodaje DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP do definicji kolumny b:

mysql> SHOW CREATE TABLE tab\G
*************************** 1. row ***************************
Table: tab
Create Table: CREATE TABLE `tab` (
`a` int(11) DEFAULT NULL,
`b` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=MyISAM DEFAULT CHARSET=latin2
1 row in set (0,00 sec)

Nie można zakładać że tak będzie zawsze, bo można także jako wartość domyślną dla kolumny TIMESTAMP ustawić NULL. Wtedy tego typu zastąpienie NULL konkretną aktualnym czasem nie ma miejsca:

mysql> CREATE TABLE tab (a INT, b TIMESTAMP NULL);
Query OK, 0 rows affected (0,02 sec)

mysql> INSERT INTO tab VALUES (1, NULL);
Query OK, 1 row affected (0,00 sec)

mysql> drop table tab;
Query OK, 0 rows affected (0,00 sec)

mysql> CREATE TABLE tab (a INT, b TIMESTAMP NULL);
Query OK, 0 rows affected (0,04 sec)

mysql> SHOW CREATE TABLE tab\G
*************************** 1. row ***************************
Table: tab
Create Table: CREATE TABLE `tab` (
`a` int(11) DEFAULT NULL,
`b` timestamp NULL DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin2
1 row in set (0,00 sec)

mysql> INSERT INTO tab VALUES (1, NULL);
Query OK, 1 row affected (0,00 sec)

mysql> SELECT * FROM tab;
+------+------+
| a    | b    |
+------+------+
|    1 | NULL |
+------+------+
1 row in set (0,00 sec)

Dziękuję Marcie za zwrócenie uwagi na to zachowanie.

Jeśli dodamy NULL do kolumny, która jest autoinkrementowana, wstawiana jest kolejna z rzędu wartość. W przypadku GROUP BY, ORDER BY czy DISTINCT wszystkie wartości NULL traktowane są jako identyczne.

Mam nadzieję, że tą serią postów trochę przybliżyłem specyficzne zjawisko, jakim jest w SQL wartość NULL. Te dwa posty tu dopiero początek na drodze ku poznaniu. Ciekawe i nie koniecznie logicznie przewidywalne rzeczy zaczynają się dziać w momencie gdy NULL pojawia się w JOINach. Może w przyszłości trafi to na ten blog, na razie zainteresowanych zapraszam do dokumentacji MySQL i do podręczników opisujących SQL.