Создание таблицы через CREATE TABLE
В SQLite любые структурированные данные хранятся в таблицах, и каждая таблица начинается с инструкции CREATE TABLE. Вы задаёте имя, перечисляете колонки и при желании добавляете ограничения. SQLite сохраняет схему прямо в файл базы данных — и таблицей уже можно пользоваться.
Минимальный рабочий пример создания таблицы SQL:
Три столбца, один первичный ключ, одно правило NOT NULL. SQLite сам подставил id, потому что это целочисленный primary key, а во второй строке оставил email пустым (NULL) — мы ведь нигде не запретили. Вот и вся суть: имя таблицы, столбцы, ограничения. Всё остальное на этой странице — лишь вариации на ту же тему.
Разбираем синтаксис по частям
Описание столбца строится по схеме имя ТИП ограничение ограничение .... В классическом SQLite тип указывать необязательно (подробнее — на странице про type affinity), но лучше всё же его писать всегда: и людям, и инструментам так понятнее.
Несколько моментов, на которые стоит обратить внимание:
- Ограничения пишутся через пробел:
NOT NULL UNIQUEу поляskuозначает, что действуют оба правила сразу. DEFAULT 1дляin_stockпозволяет не указывать эту колонку приINSERT.- В SQLite булевы значения хранятся в
INTEGER— отдельного типаBOOLEANтут нет.0— этоfalse,1—true. - Запятая после последней колонки — синтаксическая ошибка. SQL строже, чем JavaScript.
CREATE TABLE IF NOT EXISTS: чтобы скрипт не падал при повторном запуске
Если выполнить CREATE TABLE для базы, где такая таблица уже есть, SQLite выдаст ошибку:
Ошибка: таблица users уже существует
В первый раз это нормально, но к сотому разу начинает раздражать. IF NOT EXISTS превращает запрос в no-op, если таблица уже существует:
Второй CREATE TABLE просто ничего не делает — ни ошибки, ни изменений в схеме. Именно такая форма нужна в стартовом коде, миграционных скриптах и везде, где один и тот же SQL может выполниться не один раз.
Но есть нюанс: IF NOT EXISTS сверяет только имя. Если таблица с таким именем уже есть, но колонки в ней другие, SQLite оставит её как есть. Он не станет «чинить» или «обновлять» схему за вас — для этого существуют миграции.
Ограничения: правила, которые живут вместе со схемой
Ограничения (constraints) — это способ перенести валидацию данных на уровень самой базы. Вот четвёрка, к которой вы будете обращаться постоянно:
PRIMARY KEY— однозначно идентифицирует строку. Подробнее об этом — в отдельной статье про первичные ключи.NOT NULL— колонка обязана содержать значение.DEFAULT значение— подставляется, когдаINSERTпропускает колонку. Можно указать литерал или выражение, напримерdatetime('now').CHECK (выражение)— должно быть истинным для каждой строки.UNIQUE (col, col)— ограничение уровня таблицы, гарантирующее уникальность комбинации значений.
Ограничения срабатывают при каждом INSERT и UPDATE. Строка, нарушающая хотя бы одно из них, отклоняется, и весь запрос падает с ошибкой. Поймать кривые данные на уровне БД куда дешевле, чем разгребать их потом, когда они уже расползлись по приложению.
Внешние ключи (foreign key) в SQLite
Внешний ключ говорит: «эта колонка ссылается на строку в другой таблице». Он держит данные в согласованном состоянии — вы не сможете сослаться на несуществующего пользователя, а при правильных настройках удаление пользователя каскадно подчистит и его заказы.
SQLite-нюанс, который стоит запомнить раз и навсегда: проверка внешних ключей по умолчанию выключена. Чтобы ограничения действительно работали, нужно выполнить PRAGMA foreign_keys = ON для каждого нового соединения. Большинство драйверов делают это сами или дают соответствующую настройку; если ваш — нет, выполните прагму сразу после подключения.
ON DELETE CASCADE в этом примере означает, что при удалении пользователя автоматически удалятся и все его посты. Есть и другие варианты: SET NULL, RESTRICT, а также поведение по умолчанию — NO ACTION, которое запрещает удаление, если у записи есть дочерние строки.
CREATE TABLE AS SELECT в SQLite
Иногда хочется быстро сохранить результат запроса как отдельную таблицу — сделать снапшот, бэкап или временную таблицу для анализа. С этим отлично справляется CREATE TABLE ... AS SELECT:
В новую таблицу попадут имена колонок, типы (по мере возможности) и сами данные. А вот чего туда не попадёт — не менее важно: ни первичного ключа, ни NOT NULL, ни индексов, ни внешних ключей. Это просто плоский слепок. Используйте его как отправную точку для разовых задач, а не как способ клонировать настоящую схему.
Если нужна только структура без данных, допишите WHERE 0:
Получаешь пустую таблицу с той же структурой колонок — удобно для архивных таблиц, которые ты заполнишь позже.
Временные таблицы в SQLite
Таблица TEMP существует только в рамках текущего подключения к базе. Закрыл соединение — таблицы как не бывало: ни чистить ничего не надо, ни схема нигде не остаётся:
Хорошие сценарии: подготовить строки для многошагового запроса, сохранить промежуточные результаты, которые слишком громоздки для CTE, или изолировать данные конкретного соединения в долгой сессии. CREATE TEMP TABLE и CREATE TEMPORARY TABLE — это одно и то же.
Можно связать с AS SELECT: конструкция CREATE TEMP TABLE snapshot AS SELECT ... часто применяется, чтобы зафиксировать срез данных в середине анализа.
Экранирование имён
В большинстве случаев имена таблиц и колонок — это обычные идентификаторы без кавычек. Но если приходится использовать зарезервированное слово или имя с пробелом, оберните его в двойные кавычки (так велит стандарт SQL) либо в обратные кавычки (наследие MySQL, которое SQLite тоже понимает):
Работает, конечно, но каждый раз возиться с кавычками при обращении к таблице — то ещё удовольствие. Лучше сразу давать обычные имена: orders, selection, user_id — и забыть про кавычки как страшный сон.
Живой пример: create table sqlite на практике
Соберём всё в кучу — небольшая схема для приложения задач, с IF NOT EXISTS, чтобы её можно было гонять при каждом запуске:
Такую схему уже не стыдно отправить в прод: повторный запуск ничего не сломает, внешние ключи реально работают, CHECK следит за тем, чтобы done хранил адекватные значения, значения по умолчанию расставлены, а временные метки заполняются сами.
Дальше: типы данных колонок
В CREATE TABLE можно написать INTEGER, TEXT, REAL — но SQLite, как известно, относится к типам колонок весьма вольно. На следующей странице разберём пять классов хранения, которыми SQLite пользуется на самом деле, и поймём, почему тип, который вы указали, далеко не всегда совпадает с тем, что окажется в базе.
Часто задаваемые вопросы
Как создать таблицу в SQLite?
Базовый синтаксис — CREATE TABLE имя (колонка1 ТИП, колонка2 ТИП, ...). У каждой колонки есть имя и (необязательно) тип, а к ней можно прицепить ограничения: PRIMARY KEY, NOT NULL, DEFAULT и другие. Запрос выполняется сразу же, и таблица сохраняется в файле базы данных.
Зачем нужен IF NOT EXISTS в CREATE TABLE?
Конструкция CREATE TABLE IF NOT EXISTS имя (...) создаёт таблицу только в том случае, если её ещё нет. Без этой проверки повторный запуск скрипта на уже существующей базе упадёт с ошибкой table already exists. На практике это стандартная страховка для миграций и кода, который выполняется при старте приложения.
Можно ли создать таблицу из SELECT в SQLite?
Да, для этого есть CREATE TABLE new_name AS SELECT ... — новая таблица собирается прямо из результата запроса. Но учтите: копируются только имена колонок и сами данные, а вот ограничения, первичные ключи и индексы из исходной таблицы не переносятся. Такой приём удобен для снапшотов и временных служебных таблиц, но полноценную схему так не построить.
Чем временная таблица отличается от обычной?
CREATE TEMP TABLE (он же CREATE TEMPORARY TABLE) создаёт таблицу, которая живёт только в рамках текущего соединения и исчезает, как только вы его закроете. Обычные таблицы хранятся в файле базы постоянно. Временные таблицы удобны, когда нужно отложить промежуточные результаты запроса, не засоряя основную схему.