Data Science. SQL hub
По всем вопросам- @workakkk @itchannels_telegram - 🔥лучшие ит-каналы @ai_machinelearning_big_data - Machine learning @pythonl - Python @pythonlbooks- python книги📚 @datascienceiot - ml книги📚 РКН: https://vk.cc/cIi9vo #VRHSZ
نمایش بیشتر📈 تحلیل کانال تلگرام Data Science. SQL hub
کانال Data Science. SQL hub (@sqlhub) در بخش زبانی روسی بازیگری فعال است. در حال حاضر جامعه شامل 35 839 مشترک است و جایگاه 3 835 را در دسته فناوری و برنامهها و رتبه 18 129 را در منطقه روسيا دارد.
📊 شاخصهای مخاطب و پویایی
از زمان ایجاد در невідомо، پروژه رشد سریعی داشته و 35 839 مشترک جذب کرده است.
بر اساس آخرین دادهها در تاریخ 13 ژوئن, 2026، کانال فعالیت پایداری دارد. در ۳۰ روز گذشته تغییر اعضا برابر -8 و در ۲۴ ساعت گذشته برابر -11 بوده و همچنان دسترسی گستردهای حفظ شده است.
- وضعیت تأیید: تأیید نشده
- نرخ تعامل (ER): میانگین تعامل مخاطب 9.82% است و در ۲۴ ساعت نخست پس از انتشار، محتوا معمولاً 4.08% واکنش نسبت به کل مشترکان کسب میکند.
- دسترسی پستها: هر پست به طور میانگین 3 522 بازدید دریافت میکند. در اولین روز معمولاً 1 461 بازدید جمعآوری میشود.
- واکنشها و تعامل: مخاطبان بهطور فعال حمایت میکنند؛ میانگین واکنش به هر پست 13 است.
- علایق موضوعی: محتوا بر موضوعات کلیدی مانند sql, индекс, postgres, index, sqlite تمرکز دارد.
📝 توضیح و سیاست محتوایی
نویسنده این فضا را محل بیان دیدگاههای شخصی توصیف میکند:
“По всем вопросам- @workakkk
@itchannels_telegram - 🔥лучшие ит-каналы
@ai_machinelearning_big_data - Machine learning
@pythonl - Python
@pythonlbooks- python книги📚
@datascienceiot - ml книги📚
РКН: https://vk.cc/cIi9vo
#VRHSZ”
به لطف بهروزرسانیهای پرتکرار (آخرین داده در تاریخ 14 ژوئن, 2026)، کانال همواره بهروز و دارای دسترسی بالاست. تحلیلها نشان میدهد مخاطبان بهطور فعال با محتوا تعامل دارند و آن را به نقطه اثرگذاری مهم در دسته فناوری و برنامهها تبدیل کردهاند.
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
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
