В прошлом уроке мы создали таблицу и написали у столбца id слово INTEGER. В PostgreSQL такая запись строгая: попробуй положить туда слово, и база откажет. SQLite устроена иначе, и новичка это обычно удивляет. Разберёмся, какие значения она хранит и что значит тип столбца.
Ящики с наклейками
Представь склад кофейни с ящиками. На ящике наклейка «Зёрна». В строгом складе кладовщик проверяет каждую коробку и не пустит в ящик пакет сахара. В складе SQLite наклейка скорее пожелание. Кладовщик посмотрит на коробку: если её можно без потерь превратить в то, что написано на наклейке, превратит. Если нельзя, положит как есть.
Получается, что в SQLite тип есть у каждого значения, а у столбца только пожелание к типу. Так устроена динамическая типизация.
Пять классов хранения
Любое значение в SQLite относится к одному из пяти классов хранения:
| Класс | Что хранит | Пример |
|---|---|---|
NULL | отсутствие значения | NULL |
INTEGER | целое число со знаком | 42, -7 |
REAL | число с дробной частью | 4.2, 0.5 |
TEXT | строку | 'латте' |
BLOB | байты как есть | x'CAFE' |
BLOB хранит байты без всякого толкования: картинку, архив, что угодно. Отдельного логического типа и типа для дат нет. Правду и ложь хранят числами 1 и 0, а даты строками вида '2026-10-24', к датам вернёмся в уроке про встроенные функции.
Узнать класс значения помогает функция typeof:
sqlite> SELECT typeof(42), typeof(4.2), typeof('42'), typeof(NULL), typeof(x'CAFE');
╭────────────┬─────────────┬──────────────┬──────────────┬─────────────────╮
│ typeof(42) │ typeof(4.2) │ typeof('42') │ typeof(NULL) │ typeof(x'CAFE') │
╞════════════╪═════════════╪══════════════╪══════════════╪═════════════════╡
│ integer │ real │ text │ null │ blob │
╰────────────┴─────────────┴──────────────┴──────────────┴─────────────────╯
42 и '42' для SQLite разные значения: первое число, второе строка. Кавычки решают.
Тип столбца это пожелание
Создадим таблицу стаканов, где размер объявлен целым числом, и положим туда всё подряд:
sqlite> CREATE TABLE cups (size INTEGER);
sqlite> INSERT INTO cups VALUES (300), ('400'), (2.0), ('большая'), (NULL);
sqlite> SELECT size, typeof(size) FROM cups;
╭─────────┬──────────────╮
│ size │ typeof(size) │
╞═════════╪══════════════╡
│ 300 │ integer │
│ 400 │ integer │
│ 2 │ integer │
│ большая │ text │
│ NULL │ null │
╰─────────┴──────────────╯
Ошибок нет ни одной. Посмотри, что сделала база:
300уже целое, его записали как есть.- Строку
'400'можно без потерь превратить в число, база превратила. 2.0тоже стало целым2: дробной части нет, значит ничего не потеряно.'большая'числом не сделать, слово легло как текст.NULLосталсяNULL: пустота в любом столбце остаётся пустотой.
Это поведение называется родством типа. Столбец с типом INTEGER имеет родство с целыми числами: он старается хранить целые, но не настаивает. Родство SQLite выводит из слова в объявлении по простым правилам. В имени есть INT: целое. Есть CHAR, CLOB или TEXT: текст. Есть REAL, FLOA или DOUB: дробное. Поэтому VARCHAR(10) для SQLite обычный текст, и длина в скобках ни на что не влияет.
Столбец можно объявить вообще без типа. Тогда родства нет, и каждое значение хранится ровно таким, каким пришло:
sqlite> CREATE TABLE anything (value);
sqlite> INSERT INTO anything VALUES (300), ('400'), (2.5);
sqlite> SELECT value, typeof(value) FROM anything;
╭───────┬───────────────╮
│ value │ typeof(value) │
╞═══════╪═══════════════╡
│ 300 │ integer │
│ '400' │ text │
│ 2.5 │ real │
╰───────┴───────────────╯
Здесь '400' осталась строкой. Утилита взяла её в кавычки: в режиме qbox она так помечает текст, который легко спутать с числом. Числа к тому же прижаты вправо, а текст влево.
Ловушка с проверками
Динамическая типизация влияет не только на хранение. Сравнение числа с текстом в SQLite не ошибка: любой текст считается больше любого числа. Поэтому проверка «цена больше нуля» пропускает слово:
sqlite> CREATE TABLE prices (amount INTEGER CHECK (amount > 0));
sqlite> INSERT INTO prices VALUES ('дорого');
sqlite> SELECT amount, typeof(amount) FROM prices;
╭────────┬────────────────╮
│ amount │ typeof(amount) │
╞════════╪════════════════╡
│ дорого │ text │
╰────────┴────────────────╯
Ограничения вроде CHECK подробно разберём в уроке про ограничения. Пока запомни: тип в объявлении столбца сам по себе мусор не остановит.
Строгие таблицы
Иногда гибкость мешает: хочется, чтобы база сама ловила ошибки. Для этого в SQLite 3.37 появились строгие таблицы. Слово STRICT после закрывающей скобки включает проверку:
sqlite> CREATE TABLE cups_strict (size INTEGER) STRICT;
sqlite> INSERT INTO cups_strict VALUES ('400');
sqlite> INSERT INTO cups_strict VALUES ('большая');
Error near line 3: cannot store TEXT value in INTEGER column cups_strict.size
sqlite> SELECT size, typeof(size) FROM cups_strict;
╭──────┬──────────────╮
│ size │ typeof(size) │
╞══════╪══════════════╡
│ 400 │ integer │
╰──────┴──────────────╯
Строку '400' строгая таблица всё ещё превращает в число, это приведение без потерь. А слово получает отказ. Сообщение начинается с Error near line 3: так sqlite3 сообщает об ошибке, которая случилась во время выполнения, и указывает номер строки ввода.
В строгой таблице можно писать только INTEGER, INT, REAL, TEXT, BLOB и ANY. Тип ANY хранит что угодно, но без приведения: '400' останется строкой.
Зачем так сделано
Создатели SQLite считают гибкость достоинством: база проще, старые файлы читаются любыми версиями, и в одну колонку настроек можно сложить значения разных типов. В учебной базе «Пенки» это видно. У напитка «Какао» объём записан строкой '300 мл', а у «Тархуна» дробью 0.5. Такие данные приходят из старых программ и ручного ввода, и SQLite хранит их без потерь. Зачем базе вообще ограничивать допустимые значения, рассказывает урок о NULL и целостности.
Практический совет: объявляй типы столбцов всегда, даже если база их не требует. Это документация для людей и родство для базы. А для новых таблиц, где важна аккуратность, бери STRICT.
Попробуй сам
В песочнице пустая база. Создай обычную и строгую таблицу и запиши в обе одно и то же:
Пятая строка падает нарочно: строгая таблица не пускает слово. Поменяй тип столбца в loose на TEXT и посмотри, что typeof скажет про 7.
Проверь себя
Задание
Дальше
Мы уже несколько раз писали CREATE TABLE и INSERT, не разбирая их толком. В следующем уроке собираем первую настоящую таблицу: создаём, наполняем, читаем и удаляем.
домашка