Menu

Импорт CSV в SQLite: команда .import и режим --csv

Разбираемся, как загрузить CSV-файл в SQLite через команду .import: что делать с заголовками, как дописать данные в существующую таблицу, поменять разделитель и обойти типичные ошибки.

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

Импорт CSV живёт в CLI, а не в SQL

В диалекте SQL у SQLite нет инструкции IMPORT. Загрузка CSV — это возможность командной оболочки sqlite3, точечная команда .import. Тут важно перестроить мышление, если вы пришли из MySQL с его LOAD DATA INFILE или из Postgres с COPY: там команды выполняются на стороне сервера, а .import — это уже работа клиентской утилиты, которая сама читает файл и под капотом выполняет INSERT.

Поэтому всё, о чём пойдёт речь дальше, подразумевает, что вы находитесь внутри оболочки sqlite3:

sqlite3 mydata.db

Если же вам нужно загружать данные из кода приложения — на Python, Node или Go — то CSV вы будете читать средствами самого языка и складывать в базу через параметризованные запросы INSERT. Этот подход мы разберём в главе про интеграцию с приложениями. А сейчас речь именно про CLI.

Базовый импорт через .import

Самый короткий путь — сказать SQLite, что файл в формате CSV, и натравить .import на сам файл, указав имя таблицы.

.mode csv
.import people.csv people

В зависимости от того, существует ли уже таблица people, произойдёт одно из двух:

  • Таблицы нет — SQLite создаст её сам, взяв имена столбцов из первой строки CSV. Все столбцы получат тип TEXT.
  • Таблица уже есть — SQLite вставит в неё все строки файла как данные. Строка с заголовками, если она есть, попадёт туда же как обычная запись.

Именно на втором сценарии чаще всего и спотыкаются при первой попытке импорта. Если у вашего CSV есть заголовок, а таблица уже создана — заголовок нужно пропустить явно.

Как пропустить заголовок при загрузке CSV в существующую таблицу SQLite

Передайте .import флаг --skip 1, чтобы он проигнорировал первые N строк:

CREATE TABLE people (
    name TEXT,
    age  INTEGER,
    city TEXT
);

.import --csv --skip 1 people.csv people

--csv — это сокращение для .mode csv, действующее только в рамках этой команды, так что отдельно режим выставлять не нужно. --skip 1 отбрасывает строку с заголовками. Все оставшиеся строки попадают в таблицу people в порядке столбцов.

Быстрая проверка после импорта:

SELECT count(*) FROM people;
SELECT * FROM people LIMIT 5;

Порядок столбцов в файле должен совпадать с порядком столбцов в таблице. Никакого сопоставления по заголовкам тут нет — .import просто кладёт N-е поле в N-й столбец, и всё.

Пусть SQLite сам создаст таблицу

Если вы только щупаете данные, проще всего вообще обойтись без CREATE TABLE — пусть .import сам соберёт таблицу по заголовку CSV-файла:

.mode csv
.import sales.csv sales

.schema sales

.schema sales покажет примерно такое:

CREATE TABLE sales(
  "order_id" TEXT,
  "amount" TEXT,
  "ordered_at" TEXT
);

Обрати внимание: все колонки получились TEXT. Это сделано намеренно — .import не пытается угадывать типы. Если хочешь, чтобы amount был настоящим числом, а ordered_at — нормальным таймстампом, сначала создай таблицу руками с нужными типами, а потом импортируй с флагом --skip 1. Дальше за дело возьмётся type affinity SQLite: числовые строки сами приведутся к INTEGER и REAL при вставке.

Свои разделители: TSV, pipe, точка с запятой

Режим .mode csv работает с запятой. Для файлов с табуляцией переключаем режим:

.mode tabs
.import data.tsv events

Если разделитель отличается от стандартного, задайте его через .separator после выбора режима:

.mode csv
.separator "|"
.import pipe_data.txt events

Стоит запомнить один момент: .mode csv работает по правилам экранирования из RFC 4180 — поля с запятыми или переводами строк внутри корректно обрабатываются, если они заключены в кавычки ". А вот .mode tabs — это простой режим разбиения по символу, без какого-либо экранирования. Если в вашем файле есть поля в кавычках с разделителями внутри, оставайтесь в .mode csv и просто поменяйте разделитель.

Разбираем на реальном примере

Допустим, файл orders.csv выглядит так:

order_id,customer,amount,ordered_at
1001,Ada,49.99,2026-01-12
1002,Boris,12.50,2026-01-13
1003,"Chen, Wei",199.00,2026-01-14

Обратите внимание: в третьей строке внутри поля в кавычках стоит запятая. Вот полный лог сессии:

В реальной оболочке весь блок INSERT заменяется одной командой .import --csv --skip 1 orders.csv orders. Поле "Chen, Wei" остаётся целым, потому что режим CSV корректно обрабатывает кавычки. Колонка amount приходит как настоящее число, а order_id — как целое, и всё благодаря типам столбцов.

Импорт CSV внутри транзакции

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

BEGIN;
.import --csv --skip 1 big_file.csv events
COMMIT;

Одна эта мелочь превращает импорт длиной в несколько минут в дело пары секунд. Если что-то пойдёт не так посреди загрузки, ROLLBACK откатит частично загруженные данные — это же удобно и при повторных попытках.

Ускориться можно ещё сильнее: удалите индексы до импорта и пересоздайте их после. Перестроение индекса на каждой строке съедает кучу времени.

Типичные ошибки и как их лечить

Error: expected N columns but found M — количество полей в строке не совпадает с числом колонок таблицы. Обычные причины:

  • Лишняя запятая внутри незакавыченного поля. Перевыгрузите файл с корректным экранированием по CSV или переключитесь с .mode tabs на .mode csv (по стандарту RFC 4180).
  • Пустая строка в конце файла. Поправьте файл вручную или поиграйтесь с --skip.
  • В таблице больше колонок, чем в CSV. Либо добавьте недостающие поля в файл, либо залейте данные в промежуточную (staging) таблицу нужной формы, а оттуда уже скопируйте в боевую.

Строка с заголовками попала в данные — забыли указать --skip 1 при загрузке CSV в существующую таблицу SQLite. Удалите эту строку (DELETE FROM t WHERE rowid = 1) и запустите импорт заново уже с флагом.

Числа сохранились как строки — вы дали .import создать таблицу самому, и все колонки получились TEXT. Удалите таблицу, опишите её руками с типами INTEGER/REAL и импортируйте CSV заново.

Error: no such file — путь считается относительно того каталога, откуда вы запустили sqlite3, а не относительно файла базы. Используйте абсолютный путь либо сделайте cd в нужную папку до запуска оболочки.

CLI выводит номер строки, на которой споткнулся, — это самый быстрый способ найти проблемную запись в большом файле.

Кратко повторим

  • .import — это dot-команда CLI, а не SQL. Запускать её нужно внутри оболочки sqlite3.
  • Флаг --csv корректно обрабатывает кавычки, а --skip 1 пропускает строку с заголовками.
  • Если таблицы нет, .import создаст её по заголовку CSV — но все колонки будут TEXT. Лучше создать таблицу самому, чтобы получить нормальные типы.
  • Большие импорты заворачивайте в BEGIN/COMMIT, иначе на каждую строку будет своя транзакция.
  • Порядок колонок в файле должен совпадать с порядком колонок в таблице.

Дальше: выгружаем данные обратно

Импорт — это только половина истории. Та же оболочка умеет выгружать результаты запросов или целые таблицы обратно в CSV, JSON или SQL. Пригодится для бэкапов, дата-пайплайнов и передачи данных в другие инструменты. Об этом поговорим в разделе про экспорт данных.

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

Как загрузить CSV-файл в базу SQLite?

Откройте базу через консольную утилиту sqlite3, переключитесь в CSV-режим командой .mode csv и выполните .import data.csv table_name. Если таблицы ещё нет, SQLite создаст её сам, взяв имена колонок из первой строки файла. Если таблица уже существует — все строки файла попадут в неё как данные, поэтому почти всегда нужен флаг .import --skip 1, чтобы пропустить шапку.

Как импортировать CSV с заголовком в уже существующую таблицу SQLite?

Запустите .import --csv --skip 1 data.csv table_name. Флаг --skip 1 говорит SQLite пропустить первую строку, чтобы заголовок не превратился в обычную запись. Без него в таблице появится строка, где значениями будут литеральные названия колонок.

Почему импорт CSV в SQLite падает с ошибкой 'expected N columns but found M'?

В файле есть строки, у которых количество колонок не совпадает с таблицей — обычно из-за запятых внутри значений, неэкранированных кавычек или пустой строки в конце. Используйте .mode csv (или флаг --csv) вместо .mode tabs — тогда SQLite разберёт кавычки по RFC 4180. И загляните в файл текстовым редактором: CLI выводит номер проблемной строки, по нему битую запись находишь за секунды.

Coddy programming languages illustration

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

НАЧАТЬ