Menu

Типы данных в SQLite: классы хранения и динамическая типизация

Как SQLite на самом деле хранит значения: пять классов хранения, почему типизация динамическая и какие подводные камни ждут тех, кто пришёл из Postgres или MySQL.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Пять классов хранения вместо десятков типов

В SQLite любое значение хранится в одном из пяти классов хранения (storage classes):

  • NULL — отсутствие значения.
  • INTEGER — целое число со знаком, занимает от 1 до 8 байт в зависимости от величины.
  • REAL — число с плавающей точкой по стандарту IEEE, 8 байт.
  • TEXT — строка в кодировке базы (как правило, UTF-8).
  • BLOB — сырые байты, сохраняются ровно в том виде, в котором вы их передали.

И всё. Никакого отдельного BOOLEAN, DATETIME, VARCHAR или DECIMAL. В других СУБД типов данных — десятки, а в SQLite их всего пять, и все остальные «типы» строятся поверх них.

typeof() показывает реальный класс хранения каждого значения. Вы увидите integer, real, text, blob. Эти четыре плюс null — всё, что вообще знает SQLite.

Динамическая типизация SQLite

А теперь то, что обычно удивляет тех, кто пришёл из Postgres или MySQL. В SQLite (без режима STRICT) тип, который вы указываете у колонки, — это скорее пожелание, чем обязательство. Настоящий тип хранится вместе с самим значением:

Обе строки были приняты без вопросов. В колонке id в одной строке лежит целое число, в другой — текст; в body — наоборот, текст и число. SQLite спокойно хранит значения любого класса в любой колонке.

Это и есть динамическая типизация, и сделано так намеренно. Благодаря ей SQLite прощает многое — удобно для прототипов и быстрых скриптов. Но та же особенность означает, что опечатка в коде приложения может годами тихо записывать данные не того типа. Если такой компромисс вас не устраивает — а для большинства продакшен-схем так и должно быть — на помощь приходят таблицы STRICT. До них мы скоро доберёмся.

Аффинность типов в двух словах

Объявленный тип колонки не игнорируется — он задаёт ей аффинность (type affinity). Когда вы вставляете значение, SQLite пытается привести его к аффинности колонки, если преобразование проходит без потерь. Колонка TEXT, в которую попало число 42, сохранит его как текст '42'; колонка INTEGER, получившая строку '42', запишет её как целое 42. А вот если преобразование грозит потерей информации, исходный тип сохраняется как есть.

Первая строка: целое 42 приведено к тексту '42', а строка '100' — к числу 100. Вторая строка: '3.5' нельзя без потерь конвертировать в INTEGER, поэтому значение осталось текстом. Про affinity будет отдельный разговор чуть позже — пока запомните главное: тип колонки всё-таки влияет на то, как хранятся данные, даже если жёстко его не навязывает.

Тип boolean в SQLite

Отдельного класса хранения BOOLEAN в SQLite попросту нет. Булевы значения хранятся как целые числа: 0 — это false, 1 — true:

Ключевые слова TRUE и FALSE распознаются (начиная с SQLite 3.23) и превращаются в 1 и 0. Объявление BOOLEAN даёт колонке числовую аффинность, но не ограничивает её значениями 0/1 — без режима STRICT вы спокойно вставите туда 'maybe', и SQLite даже не пикнет.

Дата и время в SQLite

Типа DATETIME тут тоже нет. Вместо него выбираете один из трёх вариантов хранения, и встроенные функции работы с датами умеют со всеми тремя:

  • TEXT в формате ISO-8601: '2026-04-23 14:30:00'.
  • REAL — юлианский день.
  • INTEGER — секунды Unix-эпохи.

Текст в формате ISO-8601 — самый популярный вариант: он корректно сортируется как строка, читается человеком, да и все встроенные функции (date(), time(), datetime(), strftime(), julianday()) умеют с ним работать. Выберите один формат на колонку и придерживайтесь его — смешивать разные представления в одном столбце означает нарваться на проблемы через полгода.

VARCHAR, CHAR и другие знакомые имена

SQLite принимает привычные по другим СУБД названия типов — VARCHAR(255), CHAR(10), NVARCHAR, DECIMAL(10,2), DOUBLE, FLOAT, INT, BIGINT, MEDIUMINT. Парсятся они без проблем. Просто по правилам аффинности каждое из них сводится к одному из пяти классов хранения.

VARCHAR(255) не ограничивает длину 255 символами — SQLite просто игнорирует это число. DECIMAL(10, 2) не хранит число с фиксированной точностью — колонка получает numeric affinity, а данные ложатся как INTEGER или REAL. Эти имена оставлены лишь для того, чтобы схемы, скопированные из других СУБД, не падали при запуске, — но связанных с ними ограничений в SQLite нет.

Если нужна точная арифметика для денег, храните центы (или копейки) как INTEGER. С REAL рано или поздно вылезут ошибки округления уже в третьем знаке после запятой.

NULL — это отдельный класс хранения

NULL — не просто «нет значения», а полноценное значение со своим классом хранения, который возвращает typeof():

b показывается как null. Это важный момент: NULL не равен ничему — даже другому NULL. Условие b = NULL никогда не истинно, нужно писать b IS NULL. Подробнее об этом — в разделе про операторы и NULL, но начинается всё именно здесь, с класса хранения.

Хранение байтов в типе BLOB

Тип данных BLOB хранит сырые байты как есть — удобно для небольших картинок, хэшей, закодированных данных, в общем, для всего, что не является текстом или числом:

Литерал x'...' позволяет записывать блобы в SQL в шестнадцатеричном виде; из кода приложения обычно удобнее передавать массив байт через параметр. length() для блоба возвращает количество байт, а не символов.

Практический момент: SQLite без проблем хранит большие блобы, но тянуть 50-мегабайтный блоб через каждый запрос, который трогает строку, — медленно. Для больших файлов лучше держать сам файл на диске, а в базе хранить путь к нему.

Что стоит запомнить

  • Пять классов хранения — NULL, INTEGER, REAL, TEXT, BLOB — покрывают всё.
  • Булевы значения — это целые числа; даты можно хранить как текст, как REAL или как INTEGER (на ваш выбор).
  • Объявленные типы колонок — это подсказки, а не жёсткие контракты (если только вы не используете STRICT-таблицы).
  • VARCHAR(255) и его собратья парсятся без ошибок, но длину и точность, которые они подразумевают в других СУБД, SQLite не контролирует.
  • typeof(value) — ваш лучший друг, когда непонятно, что на самом деле лежит в колонке.

Дальше: type affinity

То «подсказочное» поведение, которое мы здесь упомянули мимоходом, на самом деле описано чёткими правилами — пять классов affinity, выводимых из имени объявленного типа и применяемых при каждой вставке. Об этом — следующая страница, и именно там ключ к тому, чтобы заранее понимать, что SQLite сделает с вашими значениями.

Часто задаваемые вопросы

Какие типы данных поддерживает SQLite?

В SQLite всего пять классов хранения: NULL, INTEGER, REAL, TEXT и BLOB. Каждое значение в базе хранится в одном из них. Привычные VARCHAR(255), DATETIME или BOOLEAN в CREATE TABLE тоже принимаются — это сделано для совместимости, — но при сохранении они всё равно сводятся к одному из этих пяти.

Есть ли в SQLite отдельный тип boolean или datetime?

Как отдельных классов хранения — нет. Булевы значения хранятся как INTEGER (0 и 1), хотя ключевые слова TRUE и FALSE SQLite тоже понимает. Дата и время хранятся одним из трёх способов: TEXT (строки в формате ISO-8601), REAL (юлианский день) или INTEGER (Unix-время в секундах). Кодировку выбираете вы сами, а встроенные функции для работы с датами корректно работают со всеми тремя вариантами.

Почему типизация в SQLite называется динамической?

В большинстве СУБД колонка с типом INTEGER просто не примет строку. В SQLite (по умолчанию) объявленный тип — это, скорее, подсказка: реальный тип хранится вместе со значением, поэтому в колонке TEXT спокойно может оказаться число, если вы его туда положите. Иногда это удобно, а иногда стреляет в ногу. Если такое поведение не нужно — используйте STRICT-таблицы, они эту вольность отключают.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ