Колонни и времеви бази
Накратко: Колонната база пази всяка колона отделно, за да чете бързо отчети върху милиони редове, а базата за времеви редове пази измервания с час, като тези на сензор.
Проблем
Отчетът иска две колони, а чете седем
Таблицата sales от предишния урок има седем колони: поръчка, ден, град, категория, продукт, количество и сума. Отчетът за оборота по месеци иска две от тях, day и amount.
Обикновената база пази данните по редове: всичко за една продажба е заедно на диска. За да прочете две колони, тя чете целите редове и изхвърля останалите пет. При милиард продажби това са стотици гигабайти, от които трябват малко над една четвърт.
Идеята
Всяка колона отделно
Колонната база (ClickHouse, BigQuery, Snowflake, DuckDB) пази всяка колона в отделен файл. Отчетът за оборота отваря само файловете на day и amount. А колоната съдържа еднакъв вид стойности една до друга (само дати, само градове), и затова се свива много по-добре от ред, в който са смесени.
Най-простото свиване е серия: вместо „София, София, София…“ пишеш „София × 50“. Цялата колона city, подредена по град, е седем серии:
Работи само ако еднаквите стойности са една до друга. Ето колко серии има колоната, когато редовете са по реда на поръчките, и колко, когато са подредени по град:
От 128 стойности остават 39 серии, ако редовете не са подредени, и 7, ако са. Затова в ClickHouse всяка таблица се прави с ORDER BY. Редът на колоните в него е редът на подреждане на диска.
По-точно, за любопитните
LAG(city, 1, '-') дава града от предишния ред (а за първия ред '-'), и всеки ред, в който градът се сменя, започва нова серия. IS NOT вместо <> е, защото NULL <> NULL не е истина, а NULL IS NOT NULL е ясно „не“. Истинските колонни бази ползват и други начини за свиване: речник (градовете стават числа 1–7), разлики между съседни числа, и общ алгоритъм отгоре (LZ4, ZSTD). Свиване 10 пъти не е рядкост.
Идеята
Кога колонна база, кога не
Колонната база е отлична за отчети: малко колони, много редове, суми и групи. Тя е лоша за касата. Една нова поръчка значи запис в седем отделни файла, а UPDATE на един ред често значи да се пренапише цяло парче от колоната. Затова колонните бази обичат да пишат на големи пакети (хиляди реда наведнъж) и рядко да променят. Това е разделението OLTP и OLAP от предишния урок, само че на диска.
Проблем
Витрината с млечните
Хладилната витрина с млечните продукти има сензор, който на всеки 15 минути записва температурата. Над 8 °C сиренето и млякото бързо се развалят, и магазинът трябва да знае кога е станало. Такива данни са времеви ред: стойности, подредени по време, които само се добавят и почти никога не се променят. Два дни мерения:
Два дни по 96 мерения би трябвало да са 192, а са 184. Осем липсват, и ще видиш къде.
Идеята
Сваляне до часове
Никой не гледа 35 000 точки на графика за година. Мерения се групират по час (или ден) с няколко числа за всеки: средна, най-ниска и най-висока. Това е сваляне (downsampling). Часът е първите 13 знака от времето:
Средната за 14:00 е 8.9 °C, а най-високата е 9.7 °C. Вратата е стояла отворена.
Пробвай
Кога е алармата
Покажи часовете, в които поне едно мерене е над 8 °C (колони hour и max_temp, най-високата температура в часа), по ред на часа.
Решение
SELECT substr(at, 1, 13) || ':00' AS hour, MAX(temp) AS max_temp
FROM fridge
GROUP BY hour
HAVING MAX(temp) > 8
ORDER BY hour;GROUP BY по часа и HAVING MAX(temp) > 8. Със средната (AVG) час, в който температурата е била 9 °C за 15 минути и 4 °C останалото време, не би се видял. За развалени продукти е важна най-високата, а не средната.
Идеята
Плъзгаща средна
Сензорите понякога дават отделна грешна стойност, а витрината се отваря за секунди при всеки клиент. Затова алармата често гледа не едно мерене, а средната от последните четири (последния час). Тя се плъзга с всеки нов ред: прозоречна функция с ROWS BETWEEN 3 PRECEDING AND CURRENT ROW:
Средната е над 8 °C чак в 14:45, когато вратата е отворена от час. Едно случайно високо мерене би я вдигнало с около 1 °C, но не над 8.
Прозоречната функция е във вътрешната заявка, а периодът е отвън. WHERE се изпълнява преди прозореца: ако условието за периода е в същата заявка, средната за 13:30 не вижда 12:45, 13:00 и 13:15 и е само от едно мерене.
Пробвай
Дупката
Сензорът мери на 15 минути. Намери къде е изгубил връзка: последното мерене преди дупката и първото след нея (колони last_before и first_after).
Решение
SELECT before AS last_before, at AS first_after
FROM (SELECT at, LAG(at) OVER (ORDER BY at) AS before FROM fridge) AS t
WHERE ROUND((julianday(at) - julianday(before)) * 24 * 60) > 15;LAG(at) дава предишното мерене, а julianday превръща времето в дни. Разликата по 24 × 60 е в минути, и всичко над 15 е дупка. ROUND е нужен, защото дните са дробни числа и 15 минути излизат като 15.000000xxx. Без умножението разликата е в дни (0.01 за 15 минути) и никога не е над 15.
Идеята
Срок за пазене
Суровите мерения растат с 35 000 реда на година за един сензор, а магазинът има стотици. Обичайното е таблица по часове, която се пази дълго, и сурови мерения, които се пазят кратко (тук за един ден). Таблицата по часове се прави като в склада за данни, с CREATE TABLE … AS SELECT:
Часовете 03:00 и 04:00 ги няма и в нея: от тях няма нито едно мерене. Графиката трябва да покаже празно място, а не да свърже 02:00 и 05:00 с права линия, сякаш всичко е било наред.
Упражнение
Чистене и отчет
1. Таблицата по часове вече е направена.
Изтрий суровите мерения отпреди 25 октомври 2026. Таблицата fridge_hourly остава цяла.
Решение
DELETE FROM fridge WHERE at < '2026-10-25';DELETE FROM fridge WHERE at < '2026-10-25'. Текстът '2026-10-25 00:00' е по-голям от '2026-10-25', затова полунощното мерене остава. С <= '2026-10-25 00:00' би се изтрило и първото мерене на деня, който пазиш. Проверката гледа и fridge_hourly: часовете остават 46.
2. Отчет за деня.
За всеки ден: най-ниската, най-високата и средната температура (закръглена до 0.01) и броя мерения (колони day, min_temp, max_temp, avg_temp, readings), по ред на деня.
Решение
SELECT substr(at, 1, 10) AS day, MIN(temp) AS min_temp, MAX(temp) AS max_temp, ROUND(AVG(temp), 2) AS avg_temp, COUNT(*) AS readings
FROM fridge
GROUP BY day
ORDER BY day;substr(at, 1, 10) е денят. COUNT(*) показва, че на 25-ти има 88 мерения, а не 96. Отчет без броя би скрил, че сензорът е мълчал два часа.
Идеята
Бази за времеви редове
За времеви редове има и отделни бази: TimescaleDB (разширение на PostgreSQL, което сам дели таблицата по време и сам сваля до часове), InfluxDB и Prometheus (за метрики на сървъри). Всички правят същото като в урока: подреждат по време, свиват колоните, пазят свалени таблици и трият сурови данни след срок. Колонните ClickHouse и DuckDB също се справят добре с тях.
По-точно, за любопитните
В TimescaleDB таблица по часове, която се обновява сама, е CREATE MATERIALIZED VIEW … WITH (timescaledb.continuous) AS SELECT time_bucket('1 hour', at) …, а срокът е SELECT add_retention_policy('fridge', INTERVAL '1 day'). В SQLite няма такова: таблицата по часове и триенето се пускат по график от програмата.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: CREATE TABLE: нова таблица · Естествен ключ и денормализация · Индекси · OLTP и OLAP
- Продължава в: Как се избира база
- Задачи по темата: Видове бази данни