Создание таблицы через 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) создаёт таблицу, которая живёт только в рамках текущего соединения и исчезает, как только вы его закроете. Обычные таблицы хранятся в файле базы постоянно. Временные таблицы удобны, когда нужно отложить промежуточные результаты запроса, не засоряя основную схему.