Раздел 35 · SQLite с нуля

Типы данных и динамическая типизация

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы создали таблицу и написали у столбца 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         │
╰─────────┴──────────────╯

Ошибок нет ни одной. Посмотри, что сделала база:

  1. 300 уже целое, его записали как есть.
  2. Строку '400' можно без потерь превратить в число, база превратила.
  3. 2.0 тоже стало целым 2: дробной части нет, значит ничего не потеряно.
  4. 'большая' числом не сделать, слово легло как текст.
  5. 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, не разбирая их толком. В следующем уроке собираем первую настоящую таблицу: создаём, наполняем, читаем и удаляем.

домашка

Домашка