Типы данных в SQLite: полное руководство по классам хранения и аффинитивности

Большинство баз данных имеют определённые типы данных, но SQLite значительно отличается от других систем баз данных. В статье рассказывается о динамической системе типов SQLite и различных типах данных для тех, кто только начинает работать с SQLite.

SQLite — это лёгкая база данных, которая хранит все данные в одном файле. Её ключевая особенность — гибкая система типов. В отличие от MySQL или PostgreSQL, где тип данных столбца задаётся жёстко, SQLite использует динамическую типизацию.

Принцип работы типов в SQLite

В SQLite не нужно указывать тип данных для столбцов при создании таблицы. Вместо этого SQLite использует динамическую типизацию, то есть присваивает тип каждому вставленному значению на основе его содержимого.

Тем не менее при создании столбцов в таблицах SQLite можно указывать типы данных. Это служит подсказкой для базы данных о том, какой тип данных следует ожидать.

При этом SQLite может хранить данные любого типа в любом столбце, независимо от объявленного типа.

Классы хранения в SQLite

В SQLite каждое отдельное значение (а не столбец таблицы) принадлежит к одному из пяти классов хранения (Storage Class). Это физический формат хранения данных в файле БД:

  1. NULL: Значение NULL.
  2. INTEGER: Целое число со знаком.
  3. REAL: Число с плавающей запятой.
  4. TEXT: Текстовая строка.
  5. BLOB: Последовательность байтов (бинарные данные), хранимая "как есть".

Аффинитивность типов

Аффинитивность типа — это не свойство значения, а свойство столбца. Это рекомендация для SQLite о том, в каком формате предпочтительнее хранить данные в этом столбце.

Аффинитивность не является жёстким ограничением. В столбец с любой аффинитивностью можно вставить значение любого класса хранения. Однако, если это значение имеет подходящий формат, SQLite попытается его преобразовать в соответствии с аффинитивностью столбца.

SQLite определяет пять видов аффинитивности столбца:

  1. TEXT: Данные в столбце хранятся в текстовом формате.
  2. NUMERIC: Столбец предпочитает числовое хранение (INTEGER или REAL), но может принять и TEXT.
  3. INTEGER: Ориентирован на хранение целых чисел.
  4. REAL: Столбец предпочитает хранение в виде числа с плавающей запятой.
  5. BLOB: Для значений в этом столбце преобразования не производятся.

При создании столбца можно указать аффинитивность с типом, чтобы дать SQLite подсказку о типе данных, предполагаемых к хранению в этом столбце. Затем SQLite будет использовать эту информацию для определения класса хранения значений в этом столбце.

Важно помнить: SQLite позволяет хранить данные любого типа в любом столбце, независимо от объявленного типа.

Допустим, вы создали следующую таблицу:

CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
score REAL
);

В этой таблице столбец id должен хранить целочисленные значения, столбец name — текстовые, а столбец score — значения с плавающей точкой. Однако можно вставить текстовое значение в столбец score, и SQLite сохранит его как текстовое значение без каких-либо проблем.

INSERT INTO users (id, name, score) VALUES (1, 'Alice', 'unknown');
SELECT * FROM users WHERE id = 1;

Это вернёт

1 | Alice | unknown

Правила аффинитивности типов в SQLite

При вставке или обновлении значения SQLite применяет следующие правила преобразования, основанные на аффинитивности целевого столбца:

  1. INTEGER: Значение хранится как целое число, если оно может быть преобразовано в целое число без потери информации. Если значение не может быть преобразовано в целое число, SQLite будет хранить его как вещественное значение.
  2. REAL: Значение хранится как вещественное, если оно может быть преобразовано в вещественное без потери информации. Если значение не может быть преобразовано в вещественное, SQLite будет хранить его как текст.
  3. TEXT: Значения хранятся как текстовые.
  4. NUMERIC: SQLite преобразует и сохраняет значение как целое или вещественное, если это возможно. Если преобразование невозможно, значение сохраняется как TEXT.
  5. BLOB: значения хранятся в виде BLOB (исторически этот вид аффинитивности назывался NONE, но позже был переименован).

Эти правила применяются последовательно и определяют, как исходный класс хранения значения может измениться перед сохранением в столбце.

В таблице ниже показано, как SQLite обрабатывает разные типы значений при вставке в столбец с аффинитивностью INTEGER:

Исходное значение (и его класс хранения)Что происходит при вставке в INTEGER столбецИтоговый класс хранения в столбце
42 (INTEGER)Сохраняется как естьINTEGER
'42' (TEXT)Конвертируется в целое числоINTEGER
3.14 (REAL)Не конвертируется (теряется дробная часть) → сохраняется как REALREAL
'Hello' (TEXT)Не конвертируется → сохраняется как REALREAL
x'0012FF' (BLOB)Не конвертируется → сохраняется как REALREAL

Ключевой вывод: Аффинитивность INTEGER стремится к целочисленному хранению, но в случае неудачи преобразования в целое число, значение сохраняется как REAL. Это ключевое отличие аффинитивности INTEGER от NUMERIC, которая может откатиться и к TEXT.

Сравнение классов хранения и аффинитивности:

КонцепцияКласс храненияАффинитивность типа
Что это?Физический формат хранения данных в файле БД«Предпочтение» столбца к формату данных (подсказка для SQLite)
К чему относится?Отдельное значение в ячейкеСтолбец таблицы
Пример42 (INTEGER), 'Привет' (TEXT)CREATE TABLE t (id INTEGER, name TEXT);

Как SQLite определяет аффинитивность по имени типа

Хотя у SQLite всего пять видов аффинитивности, при объявлении типа столбца можно использовать множество имён-псевдонимов. SQLite определяет аффинитивность, сопоставляя название типа с шаблонами:

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

В отличие от других СУБД, SQLite не имеет встроенных типов DATE,BOOLEAN и подобных. Однако вы можете хранить такие данные, используя один из пяти классов хранения.

К типам не поддерживаемым SQLite относятся:

Работа с датами в SQLite

Хотя в SQLite нет отдельного типа DATE, даты можно хранить, используя один из классов хранения: TEXT (формат ISO-8601), INTEGER (временная метка Unix) или REAL (число дней юлианского календаря).

Как правило, для дат лучше использовать человекочитаемый формат ISO-8601 (например, 2024-09-01 01:48:00), поскольку этот формат наиболее широко поддерживается и с ним легко работать в SQLite.

Ниже приведены примеры того, как можно работать с датами в SQLite:

-- Формат ISO-8601, хранящийся как TEXT (YYYY-MM-DD HH:MM:SS)
INSERT INTO events (name, date) VALUES ('Birthday', '2024-09-01');
INSERT INTO events (name, date) VALUES ('Appointment', '2024-09-01 01:48:00');
-- Временная метка Unix, хранящаяся как INTEGER (целое число секунд с 1970-01-01)
INSERT INTO events (name, date) VALUES ('Meeting', 1723772054);
-- Число дней юлианского календаря, хранится как REAL (Дни с полудня в Гринвиче 24 ноября 4714 года до н.э.)
INSERT INTO events (name, date) VALUES ('Conference', 2459786.574074);

Для работы с датами в этих форматах SQLite предоставляет набор встроенных функций, таких как date(), time() и strftime(). Например, с их помощью можно извлекать компоненты даты или форматировать вывод.

SQLite определяет, как интерпретировать значение даты, по его классу хранения: значения типа REAL рассматриваются как числа юлианского дня, INTEGER — как временные метки Unix, а TEXT — как строки в формате ISO-8601.

Сравнение типов данных SQLite с другими СУБД

Чтобы лучше понять уникальность подхода SQLite, сравним его систему типов с двумя популярными СУБД:

ХарактеристикаSQLiteMySQLPostgreSQL
Подход к типамДинамическая типизация (тип у значения)Статическая типизация (тип у столбца)Строгая статическая типизация
Основные типы5 классов хранения: NULL, INTEGER, REAL, TEXT, BLOBБолее 20 типов: INT, VARCHAR, DATE, BOOL, ENUM, SET и др.Богатая система типов: INT, VARCHAR, DATE, JSON, ARRAY, UUID, гео-типы
ГибкостьМожно хранить любой тип в любом столбцеЖёсткие ограничения по типам столбцовСтрогие ограничения, но с поддержкой кастов и преобразований
Дата/ВремяНет встроенного типа. Хранятся как TEXT, INTEGER или REALDATE, TIME, DATETIME, TIMESTAMP, YEARDATE, TIME, TIMESTAMP, INTERVAL, timetz, timestamptz
Логический типНет встроенного BOOLEANBOOL/BOOLEAN (синоним TINYINT(1))BOOLEAN с true/false
ПеречисленияНет поддержки ENUMENUM('val1', 'val2')ENUM через CREATE TYPE
Двоичные данныеBLOB (единый тип)BLOB, TINYBLOB, MEDIUMBLOB, LONGBLOBBYTEA, Large Objects
Числовые типыINTEGER, REAL (масштабируемые)TINYINT, INT, BIGINT, DECIMAL, FLOAT, DOUBLESMALLINT, INTEGER, BIGINT, DECIMAL, NUMERIC, REAL, DOUBLE PRECISION
Текстовые типыTEXT (единый, до 1ГБ)CHAR, VARCHAR, TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXTCHAR, VARCHAR, TEXT (неограниченный)
JSONПоддержка через расширения и функции JSON1JSON (с версии 5.7)JSON, JSONB (бинарный, с индексацией)
МассивыНе поддерживаютсяНе поддерживаютсяINT[], TEXT[] и другие массивы
ПроизводительностьБыстрее для простых операций, меньше накладных расходовОптимизирована под сложные запросы и большие объёмыОптимизирована под сложные операции и ACID
ИспользованиеВстраиваемые системы, мобильные приложения, локальное хранениеВеб-приложения, средние/большие проектыСложные приложения, аналитика, геоданные, enterprise
Преобразование типовАвтоматическое (основано на аффинитивности)Строгое с возможными предупреждениями/ошибкамиСтрогое с явным приведением (CAST, ::)
Пример объявленияCREATE TABLE t (id ANY, name ANY);CREATE TABLE t (id INT, name VARCHAR(100));CREATE TABLE t (id INTEGER, name TEXT);

Ключевые выводы из сравнения

  1. SQLite — максимально гибкий: Один столбец может хранить разные типы данных, что упрощает прототипирование
  2. MySQL — баланс гибкости и структуры: Поддерживает много типов, но сохраняет строгость объявлений
  3. PostgreSQL — максимальная строгость и богатство: Самая развитая система типов с дополнительными возможностями (массивы, JSONB, пользовательские типы)

Практические аспекты производительности

Сравнение подходов к типам данных напрямую влияет на производительность в разных сценариях:

Операция / СценарийSQLiteMySQLPostgreSQLОбъяснение различий
Вставка 1000 записей⚡⚡⚡ Очень быстро (меньше проверок типов)⚡⚡ Быстро (оптимизированный bulk insert)⚡ Быстрее в последних версияхSQLite меньше проверяет типы при вставке
SELECT с фильтрацией по типу⚡⚡ Хорошо⚡⚡⚡ Отлично (специализированные индексы)⚡⚡⚡ Отлично (расширенные индексы)Статическая типизация позволяет лучшую оптимизацию
Изменение типа столбца⚡⚡⚡ Тривиально (динамическая типизация)⚡ Затратно (ALTER TABLE пересоздаёт таблицу)⚡⚡ Проще чем MySQL (но требует времени)SQLite фактически не меняет тип, а меняет аффинитивность
Хранение смешанных типов в столбце⚡⚡⚡ Нативно поддерживается❌ Требует TEXT/VARCHAR❌ Требует TEXT/VARCHARДинамическая типизация — ключевое преимущество SQLite
Сортировка числовых значений⚡⚡ Быстро, но может требовать явного CAST⚡⚡⚡ Быстро (тип известен заранее)⚡⚡⚡ Быстро (оптимизированные алгоритмы)SQLite сортирует как текст, если значения хранятся как TEXT
Работа с датами⚡ Умеренно (функции вычисляются)⚡⚡⚡ Быстро (специализированный тип)⚡⚡⚡ Быстро (богатые временные функции)Встроенные типы DATE оптимизированы для операций
Индексация по типу⚡⚡ Универсальные индексы⚡⚡⚡ Специализированные (FULLTEXT, пространственные)⚡⚡⚡ Расширенные (GIN, GiST для JSON/массивов)Богатые системы типов = больше вариантов индексации
Объём занимаемой памяти⚡⚡⚡ Минимальный (гибкое хранение)⚡⚡ Эффективный (но фиксированные размеры)⚡ Эффективный (но overhead для сложных типов)SQLite экономит память за счёт динамического определения типа

Ключевые выводы о производительности

  1. SQLite выигрывает в гибкости и простоте: Меньше проверок типов = быстрее простые операции
  2. MySQL и PostgreSQL выигрывают в сложных сценариях: Предсказуемость типов = лучшая оптимизация запросов
  3. Для смешанных данных SQLite вне конкуренции: Только SQLite позволяет хранить числа, текст и даты в одном столбце без преобразований
  4. Специализированные операции быстрее со статической типизацией: Сортировка, поиск по датам, индексация

Рекомендации по выбору на основе производительности

Требование к производительностиЛучший выборПричина
Быстрое прототипирование с меняющейся структуройSQLiteНе нужно пересоздавать таблицы при изменении типов
Высокая нагрузка на чтение с сложными запросамиPostgreSQLОптимизатор запросов лучше работает со статическими типами
Много операций вставки/обновленияMySQL или SQLiteЗависит от структуры данных
Работа с геоданными или полнотекстовым поискомPostgreSQL или MySQLСпециализированные типы и индексы
Ограниченные ресурсы (память, диск)SQLiteМинимальный overhead на хранение типов

Когда что выбирать

СценарийРекомендуемая СУБДПочему
Мобильное приложениеSQLiteЛёгкость, встраиваемость, один файл
Быстрый прототипSQLiteНе нужно заранее определять точные типы
Веб-приложение средней сложностиMySQLБаланс производительности и возможностей
Сложное enterprise-приложениеPostgreSQLБогатые типы данных, JSON, расширяемость
Аналитика и отчётыPostgreSQLПоддержка сложных запросов и оконных функций

Заключение

Теперь вы знакомы с классами хранения (TEXT, INTEGER, REAL, BLOB и NULL) и аффинитивностью типов (TEXT, NUMERIC, INTEGER, REAL и BLOB), используемыми в системе динамических типов SQLite, а также с взаимосвязью между ними. Важно понимать: аффинитивность типа — это лишь рекомендация. SQLite сохранит в столбец данные любого класса хранения, независимо от объявленной аффинитивности.

Рекомендации по выбору аффинитивности типа:

Что хотите хранитьРекомендуемая аффинитивностьКласс хранения
ТекстTEXTTEXT
Целые числаINTEGERINTEGER
Дробные числаREAL или NUMERICREAL

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

❓ В чём главное отличие SQLite от других СУБД в работе с типами данных?

SQLite использует динамическую типизацию, тогда как большинство других СУБД (MySQL, PostgreSQL) используют статическую типизацию. Это означает, что в SQLite тип данных привязан к значению, а не к столбцу.

❓ Может ли это привести к проблемам с данными?

Да, если не быть внимательным. Например, если хранить текстовые значения в столбце с аффинитивностью INTEGER, могут возникнуть проблемы при математических операциях. Рекомендуется следовать объявленным типам для предотвращения ошибок.

❓ Как правильно выбрать аффинитивность для столбца?

❓ Почему SQLite сохраняет unknown в REAL столбце?

Это демонстрирует гибкость SQLite: хотя столбец объявлен как REAL, SQLite позволяет хранить там любой класс хранения. На практике так делать не рекомендуется.

❓ Есть ли разница между BIGINT, INT и INTEGER в SQLite?

Нет, нет разницы. Все эти объявления дают столбцу аффинитивность INTEGER. SQLite использует сопоставление по шаблону: любое имя типа, содержащее "INT", получает аффинитивность INTEGER.

❓ Как лучше хранить булевы значения в SQLite?

Рекомендуется использовать INTEGER с значениями 0 (false) и 1 (true). Или TEXT со значениями TRUE/FALSE, если важна читаемость.

❓ Можно ли изменить аффинитивность существующего столбца?

Прямо — нет. Нужно создать новую таблицу с нужной структурой, скопировать данные и удалить старую таблицу. Или использовать ALTER TABLE ... RENAME COLUMN (в новых версиях SQLite).

❓ Влияет ли аффинитивность на производительность?

Косвенно — да. Правильная аффинитивность позволяет SQLite эффективнее использовать индексы и выполнять операции. Например, сравнение чисел в INTEGER столбце быстрее, чем в TEXT.

Комментарии


Дополнительные материалы

Предыдущая Статья

Настройка одного набора цветов для светлого и тёмного режимов

Следующая Статья

Понимание различных типов SSH ключей