Добавим первую таблицу для хранения OHLCV, для этого в веб-консоли QuestDB в редактор кода вставьте код:
CREATE TABLE ohlcv (
ts TIMESTAMP,
symbol SYMBOL CAPACITY 100 INDEX,
timeframe SYMBOL,
open DOUBLE,
high DOUBLE,
low DOUBLE,
close DOUBLE,
volume DOUBLE
) TIMESTAMP(ts) PARTITION BY DAY
DEDUP UPSERT KEYS(ts, symbol, timeframe); и запустите его.
Разбор кода
Общая структура
Команда CREATE TABLE создаёт новую таблицу. Всё что внутри скобок — это колонки и их типы данных. Всё что после скобок — специальные инструкции для QuestDB как хранить и организовывать данные.
Колонки таблицы
ts TIMESTAMPМомент времени когда открылась свеча — дата и время с точностью до микросекунды. Например2024-01-01 09:00:00.000000. Это самая главная колонка в любой time-series таблице — без неё QuestDB не знает что данные временные.symbol SYMBOL CAPACITY 100 INDEXНазвание торгового инструмента —'BTC_USDT','ETH_USDT'и так далее. Здесь три ключевых слова:SYMBOL— специальный тип QuestDB для коротких строк которые часто повторяются. Внутри хранится как число —'BTC_USDT'превращается в1,'ETH_USDT'в2и так далее. Снаружи выглядит как текст, внутри работает как число — быстро и компактно.CAPACITY 100— подсказка базе что уникальных тикеров будет около 100. QuestDB заранее выделит под это память. Не жёсткое ограничение — если тикеров станет больше таблица не сломается.INDEX— индекс строится по этой колонке. Индекс работает как оглавление в книге — вместо того чтобы просматривать все строки подряд, база сразу прыгает на нужный тикер. ЗапросыWHERE symbol = 'BTC_USDT'работают значительно быстрее.
timeframe SYMBOLТаймфрейм свечи —'1m','5m','1h','1d'. ТипSYMBOLпо той же причине что и symbol — значений мало, они повторяются миллионы раз. Индекс здесь не нужен — таймфреймов всего несколько штук и индекс по такой колонке почти ничего не даст.open DOUBLE— цена открытия свечи.high DOUBLE— максимальная цена за время жизни свечи.low DOUBLE— минимальная цена за время жизни свечи.close DOUBLE— цена закрытия свечи.volume DOUBLE— суммарный объём торгов за время свечи. ТипDOUBLEпотому что объём может быть дробным числом: например1523.75 BTC.
Инструкции после скобок
TIMESTAMP(ts) Объявляем колонку ts главной временной осью таблицы. Это обязательное объявление для time-series таблиц в QuestDB. Без него нельзя использовать партиционирование, временные запросы и функцию SAMPLE BY для построения свечей других таймфреймов из минуток.
PARTITION BY DAY Данные физически делятся по дням на диске. Каждый день — отдельная папка:
db\ohlcv\
2026-01-01\ ← все свечи за 1 января
2026-01-02\ ← все свечи за 2 января
2026-01-03\ ← все свечи за 3 января Когда делаешь запрос за конкретный период — база открывает только нужные папки и не трогает остальные. Например для 30 тикеров на минутном таймфрейме это около 43 000 строк в день — оптимальный размер одной партиции.
DEDUP UPSERT KEYS(ts, symbol, timeframe) Защита от дубликатов. Комбинация трёх колонок ts + symbol + timeframe объявляется уникальным ключом таблицы. Логика при каждой вставке строки:
- Если записи с таким ключом нет → вставляет новую строку
- Если запись с таким ключом уже есть → обновляет существующую
Это называется UPSERT — от английских слов UPDATE + INSERT. Практический смысл: одна свеча одного тикера на одном таймфрейме в один момент времени может существовать только в одном экземпляре. Сколько раз ни вставляй одни и те же данные — дубликатов не будет. Это критически важно для торгового робота который получает данные из биржи — даже если одна и та же свеча придёт дважды, база просто обновит её без ошибки.
Дедупликация
Дедупликация гарантирует, что для заданного набора ключевых столбцов будет существовать только одна строка. Когда новая строка соответствует ключам существующей строки, старая строка заменяется.
DEDUP UPSERT KEYS — это защита от дубликатов. Определяет уникальный ключ таблицы из нескольких колонок. В нашем случае:
ts + symbol + timeframe = уникальная свеча Логика при каждой вставке:
- Если записи с таким ключом нет → вставляет новую строку
- Если запись с таким ключом есть → обновляет существующую
Это называется UPSERT = UPDATE + INSERT. Особенно важно для торгового робота — даже если одна и та же свеча придёт дважды из биржи, база просто обновит её без ошибки и без дубликата.
Про индекс
Почему у нас колонка symbol типа SYMBOL с индексом, а колонка timeframe типа SYMBOL без индекса?
Поясним: разница не в количестве уникальных значений, а в том как часто ты фильтруешь по этой колонке и насколько это селективно.
symbol — высокая селективность. У тебя 100 тикеров, и типичный запрос выглядит так: WHERE symbol = ‘BTC_USDT’
Это отсекает 99% строк — остаётся только 1 тикер из 100. Индекс здесь даёт огромный выигрыш — база сразу прыгает на нужные строки не сканируя остальные.
А вот колонка timeframe — низкая селективность. Обычно 3-5 таймфреймов, и типичный запрос: WHERE timeframe = ‘1h’
Это отсекает только 60-80% строк — остаётся каждая третья или пятая строка. Индекс по такой колонке почти бесполезен — базе всё равно придётся читать огромную часть таблицы. При этом индекс занимает место на диске и замедляет запись.
Отсюда вытекает правило для индекса: индекс эффективен когда фильтр отсекает более 90-95% строк. Чем меньше уникальных значений относительно общего объёма данных — тем меньше смысла в индексе.
Что получим после выполнения CREATE TABLE
После успешного выполнения запроса в строке лога внизу консоли появится: [timestamp] ✓ Table created
Зелёная галочка означает что таблица создана без ошибок.
Как проверить результат
Способ 1 — визуально в левой панели
В левой панели веб-консоли в разделе Tables появится таблица ohlcv. Кликни на стрелку рядом с ней — раскроется список всех колонок с их типами данных.

Способ 2 — запросом показать все таблицы
SHOW TABLES;
Вернёт список всех таблиц в базе. Таблица ohlcv должна быть в списке.
Способ 3 — запросом показать структуру таблицы
SHOW COLUMNS FROM ohlcv;
Вернёт список всех колонок с их типами данных — можно убедиться что все колонки созданы правильно.
Способ 4 — проверить метаданные таблицы
Кликни на таблицу ohlcv в левой панели — появится информация:
Partitioning → Day ✓ партиционирование работает
Deduplication → Enabled ✓ защита от дубликатов включена
Pending Rows → 0 ✓ таблица пустая, готова к записи
Способ 5 — убедиться что таблица пустая
SELECT count() FROM ohlcv;
Вернёт 0 — таблица создана и пустая, данных ещё нет.
Добавление данных в QuestDB — INSERT
выполните этот код:
INSERT INTO ohlcv (ts, symbol, timeframe, open, high, low, close, volume)
VALUES
('2024-01-01T09:00:00.000000Z', 'BTC_USDT', '1m', 42000.0, 42150.0, 41950.0, 42100.0, 15.5),
('2024-01-01T09:01:00.000000Z', 'BTC_USDT', '1m', 42100.0, 42200.0, 42050.0, 42180.0, 12.3),
('2024-01-01T09:00:00.000000Z', 'ETH_USDT', '1m', 2200.0, 2220.0, 2195.0, 2210.0, 320.0); Разбор синтаксиса
INSERT INTO ohlcv — в какую таблицу вставляем данные.
(ts, symbol, timeframe, open, high, low, close, volume) — список колонок в которые вставляем значения. Порядок колонок здесь важен — значения в VALUES должны идти в том же порядке.
VALUES — после этого слова идут сами данные. Каждая строка в скобках — одна свеча. Строки разделяются запятой. Последняя строка заканчивается точкой с запятой.
Обратите внимание на формат значений
Время всегда в формате ISO 8601:
'2024-01-01T09:00:00.000000Z'
год-мес-деньTчас:мин:сек.микросекундыZ Буква T разделяет дату и время. Буква Z в конце означает timezone UTC. QuestDB всегда хранит время в UTC.
Строки — в одинарных кавычках: 'BTC_USDT', '1m'.
Числа — без кавычек, дробная часть через точку: 42000.0, 15.5.
Вставка нескольких строк за один запрос
Можно вставить сколько угодно строк за один INSERT — это эффективнее чем вставлять по одной:
INSERT INTO ohlcv (ts, symbol, timeframe, open, high, low, close, volume)
VALUES
('2024-01-01T09:00:00.000000Z', 'BTC_USDT', '1m', 42000.0, 42150.0, 41950.0, 42100.0, 15.5),
('2024-01-01T09:01:00.000000Z', 'BTC_USDT', '1m', 42100.0, 42200.0, 42050.0, 42180.0, 12.3),
('2024-01-01T09:02:00.000000Z', 'BTC_USDT', '1m', 42180.0, 42300.0, 42100.0, 42250.0, 18.7),
('2024-01-01T09:00:00.000000Z', 'ETH_USDT', '1m', 2200.0, 2220.0, 2195.0, 2210.0, 320.0),
('2024-01-01T09:01:00.000000Z', 'ETH_USDT', '1m', 2210.0, 2230.0, 2205.0, 2225.0, 280.0),
('2024-01-01T09:02:00.000000Z', 'ETH_USDT', '1m', 2225.0, 2240.0, 2220.0, 2235.0, 310.0); Важно для реального робота
В боевом режиме данные в QuestDB пишутся не через INSERT в веб-консоли, а через код на Python или через протокол InfluxDB Line Protocol — это значительно быстрее. Веб-консоль и INSERT используем для обучения, тестирования и ручной вставки небольших объёмов данных.
Что физически появилось на диске
После первой вставки данных в папке C:\QuestDB\data\db\ появится папка таблицы:
C:\QuestDB\data\db\
ohlcv~N\ ← папка таблицы (N — внутренний ID)
2024-01-01\ ← партиция — один день
ts.d ← данные колонки ts
symbol.d ← данные колонки symbol
timeframe.d ← данные колонки timeframe
open.d ← данные колонки open
high.d ← данные колонки high
low.d ← данные колонки low
close.d ← данные колонки close
volume.d ← данные колонки volume
_meta ← метаданные таблицы
_txn ← журнал транзакций
Каждая колонка — отдельный файл .d. Это колоночное хранение данных — одна из главных особенностей QuestDB которая делает запросы по отдельным колонкам очень быстрыми.








