Data Science. SQL hub
По всем вопросам- @workakkk @itchannels_telegram - 🔥лучшие ит-каналы @ai_machinelearning_big_data - Machine learning @pythonl - Python @pythonlbooks- python книги📚 @datascienceiot - ml книги📚 РКН: https://vk.cc/cIi9vo #VRHSZ
Show more📈 Analytical overview of Telegram channel Data Science. SQL hub
Channel Data Science. SQL hub (@sqlhub) in the Russian language segment is an active participant. Currently, the community unites 35 839 subscribers, ranking 3 835 in the Technologies & Applications category and 18 129 in the Russia region.
📊 Audience metrics and dynamics
Since its creation on невідомо, the project has demonstrated rapid growth, gathering an audience of 35 839 subscribers.
According to the latest data from 13 June, 2026, the channel demonstrates stable activity. Although there has been a change in the number of participants by -8 over the last 30 days and by -11 over the last 24 hours, overall reach remains high.
- Verification status: Not verified
- Engagement rate (ER): The average audience engagement rate is 9.82%. Within the first 24 hours after publication, content typically collects 4.08% reactions from the total number of subscribers.
- Post reach: On average, each post receives 3 522 views. Within the first day, a publication typically gains 1 461 views.
- Reactions and interaction: The audience actively supports content: the average number of reactions per post is 13.
- Thematic interests: Content is focused on key topics such as sql, индекс, postgres, index, sqlite.
📝 Description and content policy
The author describes the resource as a platform for expressing subjective opinions:
“По всем вопросам- @workakkk
@itchannels_telegram - 🔥лучшие ит-каналы
@ai_machinelearning_big_data - Machine learning
@pythonl - Python
@pythonlbooks- python книги📚
@datascienceiot - ml книги📚
РКН: https://vk.cc/cIi9vo
#VRHSZ”
Thanks to the high frequency of updates (latest data received on 14 June, 2026), the channel maintains relevance and a high level of publication reach. Analytics show that the audience actively interacts with content, making it an important point of influence in the Technologies & Applications category.
SALES со следующей структурой:
CREATE TABLE SALES (
ID NUMBER,
REGION VARCHAR2(20),
SALE_DATE DATE,
AMOUNT NUMBER
);
Пример данных:
| ID | REGION | SALE_DATE | AMOUNT |
|----|--------|-----------|--------|
| 1 | North | 01-JAN-24 | 100 |
| 2 | North | 02-JAN-24 | 120 |
| 3 | South | 01-JAN-24 | 90 |
| 4 | South | 03-JAN-24 | 200 |
| 5 | East | 01-JAN-24 | 150 |
| 6 | East | 02-JAN-24 | 100 |
🧩 Найти:
Для каждого региона — ту дату, в которую была вторая по величине сумма продаж.
Если в регионе меньше двух дат — не выводить его вовсе.
🎯 Подвох:
- нельзя использовать LIMIT, FETCH FIRST, QUALIFY и подзапросы с ROWNUM напрямую (нужно решение через оконные функции Oracle)
- многие пытаются взять MAX(AMOUNT) с OFFSET 1, но в Oracle это не так просто
✅ Ожидаемый результат:
| REGION | SALE_DATE | AMOUNT |
|--------|-----------|--------|
| North | 01-JAN-24 | 100 |
| South | 01-JAN-24 | 90 |
| East | 02-JAN-24 | 100 |
🔍 Решение:
```sql
SELECT REGION, SALE_DATE, AMOUNT
FROM (
SELECT
REGION,
SALE_DATE,
AMOUNT,
DENSE_RANK() OVER (PARTITION BY REGION ORDER BY AMOUNT DESC) AS rnk,
COUNT(DISTINCT SALE_DATE) OVER (PARTITION BY REGION) AS cnt
FROM SALES
)
WHERE rnk = 2 AND cnt >= 2;
```
📌 **Объяснение подвоха:**
- `DENSE_RANK` гарантирует, что если есть одинаковые суммы, они получат один и тот же ранг
- `COUNT(DISTINCT SALE_DATE)` проверяет, что у региона хотя бы две разные даты (иначе регион исключается)
- Работает чисто на оконных функциях, без подзапросов с ROWNUM — идеально для Oracle
🧪 Проверь результат и попробуй адаптировать под похожие задачи с TOP-N логикой.
@sqlhuborders:
| id | customer_id | amount | status |
|-----|-------------|--------|-----------|
| 1 | 101 | 200 | completed |
| 2 | 102 | 150 | NULL |
| 3 | 101 | 300 | completed |
| 4 | 103 | NULL | completed |
| 5 | 102 | 100 | completed |
| 6 | 101 | 250 | NULL |
Задача: найти всех клиентов, у которых сумма заказов больше 500.
Ты пишешь запрос:
SELECT customer_id, SUM(amount) as total
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 500;
❓ Вопрос:
Какие клиенты вернутся? Есть ли тут подвох? Что произойдёт с заказами, где amount или status — NULL?
🔍 Подвох:
На первый взгляд запрос правильный: мы группируем по клиентам и суммируем их заказы. Но вот критичные моменты:
1️⃣ Что происходит с NULL в `amount`?
В SQL агрегатные функции (например, SUM) игнорируют NULL значения. Это значит:
- Заказ id=4 (`amount = NULL`) не участвует в суммировании.
- Заказ id=6 (`amount = 250`) участвует, потому что amount не NULL.
2️⃣ Считаем по каждому клиенту:
- customer_id=101:
- id=1: 200
- id=3: 300
- id=6: 250
Итого: 200 + 300 + 250 = 750
- customer_id=102:
- id=2: 150
- id=5: 100
Итого: 150 + 100 = 250
- customer_id=103:
- id=4: NULL (игнорируется)
Итого: 0
3️⃣ Кто попадёт в результат:
Только customer_id=101 (с суммой 750 > 500).
---
✅ Результат:
| customer_id | total |
|-------------|--------|
| 101 | 750 |
---
💥 Подвох #2:
Допустим ты случайно написал:
HAVING SUM(amount) IS NOT NULL AND SUM(amount) > 500;
Кажется логичным? А вот нет: SUM всегда возвращает 0 (не NULL), даже если у клиента нет заказов с ненулевой суммой.
➡️ Даже клиент с только NULL значениями (например, customer_id=103) получит SUM(amount) = 0, а не NULL.
Это частая ловушка:
COUNT, SUM, AVG игнорируют NULL внутри, но результат НЕ NULL (обычно 0 или NULL в зависимости от агрегата).
---
🛠 Что ещё важно:
• Если хочешь учитывать только выполненные заказы (status = 'completed'), нужно добавить:
WHERE status = 'completed'
⚠️ Не пиши условие в HAVING для фильтрации строк — лучше фильтровать через WHERE до группировки.
✅ Вывод:
- ✅ Агрегатные функции типа SUM игнорируют NULL внутри группы.
- ✅ SUM возвращает 0, даже если все значения NULL (НЕ NULL, как думают многие).
- ✅ HAVING применяется ПОСЛЕ группировки, а WHERE — ДО.
- ✅ Ошибки часто возникают, если условие на фильтр пишут в HAVING вместо WHERE.
💡 Бонус-вопрос:
Что будет, если заменить SUM(amount) на COUNT(amount) в SELECT и HAVING? И как это повлияет на клиентов с NULL значениями?
@sqlhub
# получить образ
docker pull turbolytics/sql-flow:latest
# тестовая проверка конфигурации
docker run -v $(pwd)/dev:/tmp/conf \
-v /tmp/sqlflow:/tmp/sqlflow \
turbolytics/sql-flow:latest \
dev invoke /tmp/conf/config/examples/basic.agg.yml /tmp/conf/fixtures/simple.json
# запуск против Kafka
docker-compose -f dev/kafka-single.yml up -d # поднять Kafka
docker run -v $(pwd)/dev:/tmp/conf \
-e SQLFLOW_KAFKA_BROKERS=host.docker.internal:29092 \
turbolytics/sql-flow:latest \
run /tmp/conf/config/examples/basic.agg.mem.yml --max-msgs-to-process=10000
▪ Github
@sqlhub cargo add gluesql и уже можно писать SQL-запросы к данным в памяти.
🤖 GitHub
@sqlhubsales, где хранятся данные о продажах:
CREATE TABLE sales (
sale_id INT PRIMARY KEY,
sale_date DATE,
product_id INT,
quantity INT,
price DECIMAL(10,2)
);
INSERT INTO sales (sale_id, sale_date, product_id, quantity, price) VALUES
(1, '2024-01-01', 101, 1, 100.00),
(2, '2024-01-02', 102, 2, 150.00),
-- ...
-- остальные данные
;
Каждый день формируется отчёт, где суммируются продажи по дням:
SELECT sale_date, SUM(quantity * price) AS total_sales
FROM sales
GROUP BY sale_date;
✅ Вчера сумма в отчёте была 1,000,000. Сегодня — 980,000, хотя новых записей не удаляли.
📝 Ваша задача:
1. Найти, какие записи "исчезли" из отчёта, если данных в таблице sales фактически не удаляли.
2. Определить, почему эти записи больше не попадают в итоговый запрос.
3. Исправить отчёт, чтобы сумма снова стала 1,000,000.
Ограничения:
- Таблица не изменилась по количеству строк.
- Никто не менял код запроса.
- sale_date, quantity, price остались без изменений.
Подсказка: возможно, дело в NULL, JOIN или неправильной агрегации.
🕵️ Что проверяет задача:
- Знание SQL-агрегации
- Понимание NULL и работы SUM
- Умение анализировать запросы «не через код», а через их результат
- Навык находить «скрытые» ошибки данных (например, sale_date стал NULL)
💡 Решение:
При проверке выяснится, что часть записей имеет `sale_date = NULL` (например, кто-то обновил поле sale_date на NULL).
Итоговый запрос:
```sql
SELECT sale_date, SUM(quantity * price) AS total_sales
FROM sales
GROUP BY sale_date;
```
не учитывает строки, где `sale_date IS NULL`, потому что GROUP BY игнорирует NULL как отдельную группу (не попадает ни в один существующий `sale_date`).
Чтобы увидеть эти записи:
```sql
SELECT sale_date, COUNT(*), SUM(quantity * price)
FROM sales
GROUP BY sale_date;
```
Для восстановления суммы нужно добавить обработку NULL, например:
```sql
SELECT COALESCE(sale_date, 'unknown') AS sale_date, SUM(quantity * price) AS total_sales
FROM sales
GROUP BY COALESCE(sale_date, 'unknown');
```
✅ Теперь сумма снова будет 1,000,000, а "пропавшие" продажи попадут в отдельную категорию unknown.
🎯 Эта задача учит:
✅ Всегда думать о данных, а не только о коде
✅ Проверять поля на NULL даже там, где их не ожидаешь
✅ Уметь объяснять ошибки «бизнес-заказчику», а не только исправлять запрос
🔥 Отличная тренировка внимательности и понимания нюансов SQL-агрегации!
@sqlhub
SELECT name, salary
FROM employees
WHERE dept = 'R&D';
- В 1974‑м публикуют первую спецификацию; академики критикуют за «слишком поверхностный английский», но программисты в восторге.
3. Почему SEQUEL стал SQL
- Торговая марка “SEQUEL” уже принадлежала авиастроительной компании *Hawker Siddeley*.
- IBM, опасаясь суда, в 1976 г. официально отказывается от «E» и оставляет SQL (Structured Query Language).
- *Небольшая путаница осталась навсегда: кто‑то произносит «эс‑кью‑эл», кто‑то — «сиквел».*
4. Коммерческий взлёт
- 1978 | Первая демонстрация System R внутри IBM | показала, что SQL работает быстрее ожиданий |
- 1979 | Стартап Relational Software (позже Oracle**) выпускает **Oracle V2 — первый коммерческий SQL‑движок | IBM ещё не успела выйти на рынок
- 1981 | IBM выпускает SQL/DS для мейнфреймов | стандарт де‑факто закрепляется
- 1983 | Дебют DB2 — теперь SQL есть почти в каждом крупном банке
5. Стандартизация и эволюция
- ANSI SQL‑86 → SQL‑92 (появился `JOIN ... ON`) → SQL:1999 (рекурсия, триггеры) → SQL:2003 (XML) → … → SQL:2023 (JSON, property graphs).
- Каждые 3–5 лет комитет добавляет «модные» возможности, но 90 % повседневных запросов всё ещё укладываются в синтаксис 1980‑х.
6. Забавные факты, которые украсят small talk 🍸
1. NULL ≠ 0 и NULL ≠ NULL — «неизвестное значение» нарушает законы логики, за что его зовут *“пятой ногой”* реляционной алгебры.
2. `SELECT *` — наследие печати на станке. Звёздочка означала «все колонки», чтобы не писать их руками в 132‑символьных перфокартах.
3. Команда `GO` в MS SQL Server не принадлежит стандарту SQL — это директива из старого клиента isql.
4. В Oracle долго не было `LIMIT`, а в MySQL — `RIGHT JOIN`. Поэтому админы шутили: «истинный межплатформенный SQL — это `SELECT 1;`».
5. Первый SQL‑вирус — червь *Slammer* (2003) — парализовал интернет за 10 минут через уязвимость в SQL Server 2000.
6. SQL — декларативный язык, но внутри СУБД каждый SELECT превращается в процедурный план.
7. `DROP DATABASE` придумали позже, чем `CREATE`. Сначала удалять целую БД казалось слишком опасным.
7. Почему SQL живёт дольше модных NoSQL‑наследников
- Математическая база. Таблицы + операции Кодда образуют алгебру с предсказуемой оптимизацией.
- Стандарты и переносимость. Код двадцатилетней давности можно запустить в современной Postgres или MariaDB.
- Большая экосистема. От Excel‑плагинов до BigQuery — везде так или иначе поддерживается SQL‑диалект.
- Сопротивляемость моде. Каждый «убийца SQL» (MapReduce, GraphQL, документные БД) в итоге добавляет свой адаптер SELECT ….
Итог: SQL родился как эксперимент IBM, пережил смену названий и юридические баталии, но в итоге стал «лентой Мёбиуса» мира данных: можно зайти с любой стороны — и всё равно окажешься в FROM.
CREATE INDEX idx_orders_high_value
ON orders(order_date)
WHERE total_amount > 1000;
2) Функциональные (function-based) индексы
Если фильтруете или джойните по функции, создайте индекс прямо по выражению:
CREATE INDEX idx_orders_year
ON orders (EXTRACT(YEAR FROM order_date));
3) GROUPING SETS, ROLLUP, CUBE
Для одновременной агрегации по нескольким группировкам без UNION ALL:
SELECT region, category, SUM(sales) AS total
FROM sales
GROUP BY ROLLUP (region, category);
4) Материализованные представления с QUERY REWRITE
В Oracle можно сделать автоматическую подмену сложного запроса предрасчитанным результатом (материализованным представлением):
CREATE MATERIALIZED VIEW mv_sales_by_month
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
ENABLE QUERY REWRITE
AS
SELECT TRUNC(order_date, 'MM') AS month, SUM(total_amount) AS total
FROM orders
GROUP BY TRUNC(order_date, 'MM');
Теперь запрос SELECT month, SUM(total_amount) FROM orders GROUP BY month; автоматически будет использовать mv_sales_by_month.
5) WITH PL/SQL FUNCTION RESULT CACHE
Кэшируйте результат функции, чтобы при одинаковых входных данных не пересчитывать:
CREATE OR REPLACE FUNCTION get_tax_rate(p_region VARCHAR2)
RETURN NUMBER RESULT_CACHE RELIES_ON (tax_table) IS
v_rate NUMBER;
BEGIN
SELECT rate INTO v_rate FROM tax_table WHERE region = p_region;
RETURN v_rate;
END;
6) PARALLEL HINT для ускорения запросов
Явно указывайте параллельное выполнение запроса, чтобы задействовать несколько процессов:
SELECT /*+ PARALLEL(orders, 4) */ customer_id, SUM(total_amount)
FROM orders
GROUP BY customer_id;
7) DBMS_STATS.AUTO_SAMPLE_SIZE для сбора статистики
Используйте автоматический подбор размера выборки для более точной оптимизации плана выполнения:
EXEC DBMS_STATS.GATHER_TABLE_STATS('HR', 'ORDERS', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE);
• Совет: проверяйте планы выполнения через DBMS_XPLAN.DISPLAY_CURSOR, чтобы видеть реальные шаги запроса, а не только предполагаемые.
@sqlhubio.Pipe() создаём Reader и Writer, и используя CopyTo() и CopyFrom() переносим данные.
r, w := io.Pipe()
doneChan := make(chan struct{}, 1)
go func() {
defer close(doneChan)
_, err := db1.PgConn().CopyTo(ctx, w, `copy table1 to stdin binary`)
if err != nil {
slog.Error("error", "error", err)
return
}
_ = w.Close()
doneChan <- struct{}{}
}()
_, err = db2.PgConn().CopyFrom(ctx, r, `copy table1 from stdout binary`)
_ = r.Close()
select {
case <-doneChan:
case <-ctx.Done():
}
Вся прелесть тут в том что используем наиболее быстрый способ с точки зрения PostgreSQL.
Используя `copy (select * from where ... order by ... limit ...) to stdout `можем регулировать нагрузку на чтение, следить за прогрессом и управлять копированием данных.
В качестве Reader может выступать что угодно, хоть файл csv, хоть другая СУБД, но тогда данные придётся дополнительно конвертировать в формат понимаемый PostgreSQL - csv или tsv, и использовать copy ... from stdin (format csv).
Нюанс: copy ... from stdin binary , binary обязывает использовать одинаковые типы данных, нельзя будет integer колонку перенести в колонку smallint, если такое требуется, то параметр binary надо опустить.
Весь код тут. И ещё немного кода для вдохновения.
@Golang_googleuser_events со следующей структурой:
CREATE TABLE user_events (
user_id INT,
event_time TIMESTAMP,
event_type VARCHAR(50),
platform VARCHAR(50)
);
🎯 Каждая строка описывает событие пользователя:
- user_id — идентификатор пользователя,
- event_time — время события,
- event_type — тип события (`login`, purchase, logout, error и т.д.),
- platform — платформа (`iOS`, Android, `Web`).
Требуется:
1. Найти пользователей, которые:
- Выполнили покупку (`purchase`),
- Но не заходили в систему (`login`) в течение последних 7 дней перед покупкой.
2. Найти пользователей, у которых:
- Более 30% всех событий за последний месяц составляют события типа error.
3. Рассчитать для каждого пользователя:
- Среднее время между входом (`login`) и следующим выходом (`logout`).
- Если logout отсутствует после login — игнорировать такую сессию.
---
## Дополнительные условия:
- Считайте, что данные могут быть объемными: миллионы строк.
- Решение должно быть оптимизировано: избегайте подзапросов в подзапросах без индексов, старайтесь минимизировать количество проходов по данным.
- Можно использовать оконные функции (`WINDOW FUNCTIONS`) и временные таблицы (`CTE`) для упрощения запросов.
- Платформу можно игнорировать в расчетах.
---
## Что оценивается:
- Умение использовать оконные функции и агрегаты.
- Умение правильно интерпретировать условия задачи в SQL-операции.
- Оптимизация запросов под большие объемы данных.
- Чистота, читаемость и структурированность кода SQL-запросов.
---
Примечание:
Эта задача проверяет как технические навыки работы с SQL, так и внимательность к деталям формулировки задачи. Небрежная реализация может дать неверные результаты, особенно на больших данных.
🔥 Подсказки и намёки для решения задачи
## Задание 1: Найти пользователей с покупками без логина за последние 7 дней
**Намёк:**
- Используйте оконную функцию LAG() или MAX() с фильтрацией событий login.
- Для каждой покупки проверяйте, был ли login в пределах 7 дней до события purchase.
- Можно применить LEFT JOIN событий login к событиям purchase.
## Задание 2: Найти пользователей с долей ошибок > 30%
**Намёк:**
- Используйте оконные функции COUNT(*) и SUM(CASE WHEN event_type = 'error' THEN 1 ELSE 0 END).
- Постройте долю ошибок на основе всех событий пользователя за последние 30 дней (`WHERE event_time >= CURRENT_DATE - INTERVAL '30 days'`).
## Задание 3: Рассчитать среднее время между login и следующим logout
**Намёк:**
- Используйте оконную функцию LEAD() для поиска следующего события после login.
- Пара login -> logout должна иметь корректный порядок по времени.
- Отбрасывайте случаи, где следующего logout нет или это событие другого типа.
@sqlhub
SELECT * FROM pg_create_logical_replication_slot('test_slot', 'wal2json');
2. Запускаем wal-listener:
wal-listener --dsn "host=localhost port=5432 user=postgres dbname=test" --slot test_slot
3. Получаем JSON-объекты при изменениях в базе данных.
https://github.com/ihippik/wal-listener
#devops #девопс #PostgreSQL #sql
@sqlhub
Available now! Telegram Research 2025 — the year's key insights 
