«Программирование подобно подростковому сексу: одна ошибка — и ты поддерживаешь ее всю оставшуюся жизнь.» Не помню, где я встретил этот афоризм, но наблюдение весьма верное. По крайней мере, если речь идет о долгоживущих платформах; программы все же обычно живут меньше своих создателей.
Вот например, когда-то кто-то из создателей MS-DOS решил, что имена директорий в пути файла должны разделяться с не косой чертой (/), а обратной косой чертой (\). Между прочим, у этого человека были веские причины так поступить. Но нам всем пофик. Если бы все проклятия, которые произнесли в его адрес программисты, пишущие кроссплатформенный код, записать в одну книгу, то получится всеобъемлющая энциклопедия матерщины.
Плавно переходим к теме заметки. Вообще, с концепцией NULL в SQL все не очень хорошо в плане того, что различные СУБД несколько по-разному интерпретируют идею «отсутствие значения» в различных ситуациях. Но вот кто особо отличился на поприще изготовления граблей для невинных программистов из концепции NULL, так это Oracle. В Oracle пустые строки — это NULL.
Я не знаю, кто и зачем учинил это надругательство над здравым смыслом, и не могу придумать ему оправданий. И бог с ним, что очень часто нужно различать отсутствие значения и наличие четкого и однозначного пустого значения. Это недоразумение, скривившись, можно пережить. Проблема не в этом, а в том, что в результате строковые типы требуют трепетного и особого обращения с собой, о чем очень легко забыть. Строка в любой момент в результате операций с ней может стать пустой, т.е. NULL, а NULL требует специальной обработки: троичная логика, IS NULL и т.п. Этот факт безотказно генерирует массу поводов для седых — а иногда и вырванных — волос.
Вот вам пример по мотивам моей практики:
1 2 3 4 |
SELECT * FROM dual WHERE '' LIKE '%1234%' |
Это SQL возвращает 0 строк, что не удивляет. Пустая строка не содержит в себе 1234.
1 2 3 4 |
SELECT * FROM dual WHERE NOT ('' LIKE '%1234%') |
Эм… это почему опять 0 строк? А потому, что ''
— это NULL, и '' LIKE 'все что угодно'
возвращает не TRUE или FALSE, а NULL.
Вам этот пример кажется не таким уж и сложным для обнаружения? Хорошо, найдите ошибку в этом запросе:
1 2 3 4 |
SELECT * FROM some_table t WHERE t.string_field NOT LIKE '%mask%' |
Благодаря тому неизвестному мне гениальному товарищу, у которого в голове срослось пустое с несуществующим, в Oracle приходится не забывать писать вот так:
1 2 3 4 |
SELECT * FROM some_table t WHERE (t.string_field IS NULL OR t.string_field NOT LIKE '%mask%') |
«Поубивал бы гадов» (c)
Upd. Если мне не удалось вас впечатлить масштабом разрушений, попробуйте еще вот такой запросик выполнить:
1 2 3 |
SELECT RPAD('', 10) FROM dual |
Upd 2. Вот ещё пример прямо из практики, с пылу с жару. Коллега написал функцию для выдирки дат из строки формата dd.mm.yyyy регекспами. Рекурсивную. Ораклу сразу стало хорошо:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
create or replace function get_dates_from_str(in_str varchar2, in_n number default 1) return varchar2 is res varchar2(500); npos integer; begin npos := REGEXP_INSTR(in_str, '[0-9]{2}\.[0-9]{2}\.[0-9]{4}'); if npos = 0 then res := ''; return res; end if; if in_n > 1 then res := ', '; else res := ''; end if; res := res || substr(in_str, npos, 10) || get_dates_from_str(substr(in_str, npos + 10), in_n + 1); return res; end get_dates_from_str; |
Если дата будет в конце строки, то на (по задумке) последней итерации REGEXP_INSTR
получит на вход пустую строку. И вернёт, как вы уже догадались, NULL — а не как не 0. В документации REGEXP_INSTR об этом безобразии ни слова. Стыдно было писать, я надеюсь…
Возникает вопрос: а как себя будет вести БД, если строка задана как NOT NULL и в базу сохраняется пустая строка?
Запретит ли он вставку или вставит пустую строку?
Запретит.