DataДжунгли🌳
286 subscribers
11 photos
8 videos
1 file
27 links
Data Engineering на пальцах:
🥸Про Airflow-DAG.
🍀ETL, с сарказмом.
🅾️оптимизируем SQL.
🐍Python трюки каждый пн.
Download Telegram
#DEMeme

Вся правда в одной картинке 📣
Please open Telegram to view this post
VIEW IN TELEGRAM
😁5
#DEMeme

За каждым дашбордом свой проект «Манхеттен»
Мем смешной ситуация не простая 📃✂️🪨
Please open Telegram to view this post
VIEW IN TELEGRAM
😁3
#PyTrickMonday

dict.get() : маленькая таблетка от KeyError-ов

Вы уверен знаете что из словаря можно взять значение по ключу несколькими способами


payload = {"user": {"name": "Alice"}}

# Явное обращение по ключу
nickname = payload["user"]["nickname"] # KeyError, DAG падает

# Спокойный вариант c .get()
nickname = payload.get("user", {}).get("nickname", "Anon")

# "Если ты сигма" - способ (когда поле может отсутствовать целиком)
nickname = (payload.get("user") or {}).get("nickname", "Anon")


Так вот я вам предлагаю использовать .get() ВСЕГДА и у меня на это пять три причины:

1️⃣Безопасность: API вернул 200 OK, но ключа "user" нет - прямое обращение бросит KeyError и уронит скрипт. .get() вернёт None (или ваш дефолт) и программа продолжит работу.

2️⃣Меньше грязного кода: вместо громоздкого: (Плоское лучше вложенного ZenOfPython)

if "key" in resp:
value = resp["key"]

# одна строчка
value = resp.get("key")

3️⃣Гибкий дефолт: jobs.get("DA", "DE") # вернёт 'DE', если ключа 'DA' нет

💡 Если надо "нырнуть" глубоко и не облажаться по пути, так как .get есть только у словарей, можно дефлтом указывать пустой словарь и ваш код не упадет.
email = payload.get("user", {}).get("contacts", {}).get("email")

📣 Отправь этот полезный пост коллеге чтобы он/она не ловили KeyError и были тебе всегда благодарны 😎

#DETip #Python #DataJungle
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11
#SQLWednesday
Как же на самом деле крутится "SELECT" 🤪

Как же "движок" базы данных обрабатывает ваш запрос в каком порядке.
Когда то на собеседовании на джуновскую позицию мне задали такой вопрос, может и вам пригодится. И кстати может вы избавитесь от ошибок в текущей работе ⚠️
Логический порядок такой:
FROM → JOIN → WHERE → GROUP BY → HAVING → WINDOW → SELECT → DISTINCT → ORDER BY → LIMIT/OFFSET

Давайте поближе поглядим что происходит на каждом этапе(Да не все запросы содержат ВСЕ операторы но порядок в любом случае такой):

1. FROM --Сначала SQL надо сказать откуда мы берем данные из какой таблицы.
2. JOIN --Логично если на первом месте таблицы то JOIN точно на втором.
3. WHERE --Фильтруем строки после соединения. Теперь понимаете почему нельзя использовать алиасы из SELECT, потому что он только 7 на очереди👀
4. GROUP BY --Лепим группы, считаем агрегаты.
5. HAVING --Фильтруем уже сведённые группы.
6. WINDOW --Считаем оконные функции (ROW_NUMBER, avg() over…). Они видят итог после WHERE, но до SELECT (уверен вы сейчас такие ЧЕ??)
7. SELECT --Выбираем финальные столбцы, присваиваем алиасы. Только сейчас ⚠️
8. DISTINCT --Убираем дубликаты.
9. ORDER BY --Сортируем. Уже видим алиасы из SELECT.
10. LIMIT/OFFSET --Отрезаем кусок результата.

❗️Ставь 🔥 если не знал что порядок именно такой 👀

Давайте примеры посмотрим:

-- Сколько авторов имеют > 3 статей
SELECT author_id,
COUNT(*) AS article_cnt
FROM articles
GROUP BY author_id
HAVING COUNT(*) > 3;

1. FROM - берём articles
2. GROUP BY - группируем по author_id
3. HAVING - фильтруем готовые группы (в WHERE так не выйдет - агрегатов ещё нет)
4. SELECT - выводим автора и число статей


-- Хотим все ордера + успешные доставки
SELECT o.id,
d.tracking_code
FROM orders o
LEFT JOIN deliveries d
ON d.order_id = o.id
AND d.status = 'shipped' -- фильтр СРАЗУ при соединении
WHERE o.created_at >= current_date - interval '30 days';

Фильтр в JOIN … ON ускоряет LEFT JOIN
Предикат status = 'shipped' выполняется до WHERE, поэтому пустые доставки всё ещё вернутся как NULL, но оптимизатор не тащит лишние статусы.
Это один из способов как можно ускорить ваш запрос вынося из WHERE в JOIN.


WITH active AS (
SELECT *
FROM sessions
WHERE started_at >= current_date - 7
)
SELECT user_id,
started_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY started_at) AS rn
FROM active
ORDER BY user_id, rn;

Оконка «видит» фильтр из WHERE, но идёт раньше SELECT
WHERE уже отрезал старые сессии.
ROW_NUMBER считается, потом идёт SELECT, где мы выводим rn.

Красиво не правда ли? 😃
Пересылайте коллегам и друзьям шпаргалку по SQL сохраняйте себе в телеграм 😮


Давайте в комментариях обсудим как улучшить этот запрос:


-- Ищем заказы c дорогими товарами и уже оплаченным счётом
SELECT o.id,
o.created_at,
c.total_amount,
c.status AS charge_status,
p.name AS product_name,
p.price
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
JOIN charges c ON c.order_id = o.id
WHERE p.price > 1000 -- фильтр по цене
AND c.status = 'paid' -- фильтр по чеку
AND o.created_at >= current_date - interval '90 days';

За лучший ответ получите статус "Порядочного" в чате DE Data Talks 🙂
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍3
Media is too big
VIEW IN TELEGRAM
Ну что сегодня пятница а значит время продолжить большой разбор #Airflow 🚀

Сегодня в большом видео примере вы узнаете:

1. Как писать свой первый #DAG.
2. В чем разница между requests.get() и requests.Session().get() она есть и приличная.
3. Напишем на #python красивый класс для API #CoinMarketCap.
4. Запустим наш даг, получим ошибку и еще разок запустим 😎


Также напоминаю что весь код лежит тут в репозитории, он публичный и доступен для Вас.

Пересылайте друзьям и коллегам кто хочет научится писать свой первый даг но не знает с чего начать 📣

Любые вопросы в комментариях, всегда к вашим услугам.

В следующем видео сделаем #CICD и установим #Postgres на наш сервер чтобы там хранить данные не пропускайте! 🙄
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7
#DEmeme

Сколько дашбордов не делай, а бизнесс все равно в эксель смотрит 😶
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
#DEMeme
Слово AI из каждого утюга, возможно даже сам утюг под собой скрывает нейросеть ⚙️

В этом меме много боли на самом деле 😄
Please open Telegram to view this post
VIEW IN TELEGRAM
💯5😁1
#PyTrickMonday

С началом новой недели дорогие подписчики!
Сегодня расскажу как сделать собственный контекст-менеджер через contextlib.contextmanager

Уверен вы прекрасно знакомы с конструкцией with open(file, 'r') as file_to_open:
Или при работе с базой with engine.connect() as conn:
А вот как сделать свой собственный контекстный менеджер? Сейчас все покажу! 💡

Для начала что такое менеджер контекста with?
Это такой специальный объект имеющий два магических метода - магия в том что они прячутся за словом with и вы их не видите 👀

__enter__() # запускается при входе в with-блок
__exit__() # вызывается при выходе (даже если внутри случился Exception)

Для примера давайте перепридумаем функцию open()

from contextlib import contextmanager # импорт контекстного менеджера

@contextmanager
def open_file(path, mode="r"):
print("открывашка")
f = open(path, mode)
try:
yield f # всё, что до yield это __enter__; все что после это
ачалом нов
    finally:
print("закрывашка")
f.close()


with open_file("send_log.txt") as f: # наш собственный контекстный оператор
data = f.readline()
print(data)

# Вывод:
открывашка
2025-05-30 16:24:14,565 | INFO | Connecting to 149.154.167.91:443/TcpFull...
закрывашка

Самое важное что тут надо понять что там где yield - это то что вы делаете в теле контекста 😃

Давайте что то более жизненное разберем.
Например "Я хочу знать сколько памяти мой датафрейм съел после открытия файла большого"
Вот как это можно красиво сделать:


import os, psutil
from contextlib import contextmanager

@contextmanager
def mem_used(name: str = "block"):
proc = psutil.Process(os.getpid()) # берет текущий процесс
start = proc.memory_info().rss # размер в bytes
try:
yield # вот тут вы откроете ваш файл
finally:
end = proc.memory_info().rss # было памяти у процесса в начале
diff = end - start # сколько съело ваше чтение bytes
mb = 1024 ** 2 # bytes в MiB

print(f"MemoryUsed: {diff/mb:+.2f} MiB")


with mem_used("parse_json"):
big_df = pd.read_json("og.json", lines=True, orient="records")

# MemoryUsed: +1.17 MiB


Пересылайте коллегам, чтобы они наконец-то они узнали что же за магия скрывается за WITH 🪄

Теперь домашнее задание напишите контекстный менеджер считающий время выполнение вашего кода with timer("любое имя процесса")
вывод должен быть такой "Имя процесса: время в секундах"
Присылайте решения в комменты 🔽

Делаем сами без помощи GPT! в этом посте все есть, чтобы вы справились, разогрейте мозги в начале недели 🧠
Please open Telegram to view this post
VIEW IN TELEGRAM
4🔥3
2️⃣🔤🔤🔤🔤🔤 без ИИ #DEInsight

ИИ технологии настолько сильно проникли в мою жизнь что уже и не припомню когда бы был хоть день, когда я не задавал какой то вопрос GPT.
И мне стало интересно "А что если я на работе вообще не буду пользоваться ИИ"
Вот что я понял за 2 недели, работая без ИИ помошников(GPT, co-Pilot). Будет душновато отркываем форточки 😄

0️⃣Все ответы есть в ДОКУМЕНТАЦИИ или в Reddit или в StackOver. Читайте доку и вам не придется пытать и оскорблять ГПТ пока он не даст ответ 🙂
1️⃣Получая ответ от ГПТ я просто использовал, не запоминая особо, а через неделю мог спросить тоже самое легко, но прочитав доку, поресерчив в интренете и найдя ответ он остался в моей памяти и за две недели я освежил много уже забытых знаний и получил кое что новое. Я это "выстрадал" поэтому оно отпечаталось.
2️⃣Использование ИИ создает ложное ощущение что твоя экспертиза растет ведь ты можешь сделать гораздо больше. Но это иллюзия и самообман, когда ты не провел ресерч должным образом, а просто выбрал "думающую" модель для лучшего ответа она сама за тебя почитала доку и дала тебе решение, твой вклад в это только задданый вопрос.
3️⃣Чтобы запоминать и получать опыт надо иметь взаимосвязь: Выявление проблемы - Поиск ответа(чтение доки, редита) - Использование найденных ответов -- после нескольких итераций таких -- Находишь решение и объяснение этому решению. И весь этот процесс ресерча создает прочные нейронные связи и это тот же дофамин но более сложный. А ИИ за вас делает ресерч и выдает решение и когда у "вас" получилось вы тоже получаете дофамин, но быстрый - и это проблема.
3️⃣🔤1️⃣ Проведите аналогию между съесть шоколадку и полежать дома сейчас чтобы стало хорошо, или продолжать следить за питанием и заниматься спортом чтобы отлично выглядеть но потом.
Вы получите удовольствие в обоих случаях, но во втором это более сложный путь и он скучнее, но для вашего здоровья и фигуры гораздо более правильный выбор да и опыт который вы получите куда более применим чем шоколад и диван 🙂
Использование ИИ в решении ваших рабочих(любых) задач это таже ловушка и с этим надо быть аккуратнее.
4️⃣Есть иллюзия что ИИ помогает быстрее решать задачи, это не правда. Скорость только в начале упала, но "поскрипя" пару дней быстро восстанавливаешься. Потому что то что раньше делалось на автомате, потом делегировалось ИИ и поэтому пришлось "поскрипеть".
5️⃣У меня вновь появилось желание изучать новое, оно как будто бы заснуло на время.
6️⃣В первые два дня я не мог написать рекурсивную функцию примерно час 😄 А раньше такое делалалось изи. Я мог и не писать ее, но мне хотелось.
7️⃣Критическое мышление с ИИ притупляется - факт. Обычно у вас так - "ой че то он херь пишет, сам сделаю". А это уже звоночек что, не получая рузультата, такого как надо вам вы уже не тратите время на осмысления, почему он пошел не туда. Или может вы его туда завели 😄просто потому что не углубились в проблему которую решаете.
8️⃣Английский. Я так ленился сам читать и переводить и писать, что давал ГПТ и говорил ответь вот что и просто копировал отсылая обратно. Поделав это самостоятельно вновь, я понял что опять же у меня была иллюзия что это "Я" общаюсь, потому что я писал в промпт что надо перевести на английский. Но это вообще не я и это не мои слова я просто в очередной раз просил ГПТ решить задачу за меня. Даже не техническую.


Вывод какой напрашивается:
ИИ это прекрасно, но надо делать передышку и вернуть ресерч в вашу жизнь. Иначе вы буквально тупеете, а думаете что эксперт 🤷‍♀️
После пары недель я чувствую прилив знаний и дофамина от решения задач самостоятельно.
Быстро понимаешь где у тебя реальные знания есть(или были) а где ГПТ-завеса.
Когда думал что экономил время на самом деле экономил мозги.


Такие философские рассуждения.
Это лишь мое мнение - мой опыт(НЕ ИСТИНА), вы можете думать иначе, пишите в коментариях что думаете 🪄
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5👏2
#SQLWednesday

Сегодня обсудим классический вопрос с собеседования для #DataEngineer. 🪄
Вопрос звучит так:
"Перед тобой #csv файл 20ГБ, как откроешь?"
Конечно никакой pd.read_csv() тут не поможет это просто уложит ваше ядро в #JupyterNotebook.
-- Или поможет ? Дочитай до конца и узнаешь.
Такой вопрос могут задать на самом деле и Junior и Middle да и выше. Потому что не всегда очевидно как это сделать красиво и эффективно.

Самый эффективный способ работать с такого размера файлом это естесвенно загрузить его в базу.
#Postgres уже имеет нативный инструмент для работы с csv файлами и вот как это выглядит.


COPY raw.sales
FROM '/var/lib/postgresql/big_sales.csv' -- путь до файла на сервере или локальный путь(если постгре локальная).
WITH (
FORMAT csv, -- CSV-парсер
HEADER true, -- пропускаем строку заголовков
DELIMITER ',', -- если вдруг не запятая
QUOTE '"', -- кавычки по умолчанию
ENCODING 'UTF8' -- кодировка можно изменить если будет "криво"
);

Легко и просто. Но мы не всегда имеем доступ к серверной файловой системе, так вот как это сделать прям из JupyterNotebook не ломая ядро?

1. Для нашего эксперимента я сгенерировал файл гигант вес которого 13.4GB
2. У меня уже есть развернутый постгрес на удаленном сервере(вы можете локально установить)
3. Будем пользоваться любимым пандас с chunksize


import pandas as pd
import sqlalchemy as sa

DB = dict(
user="",
password="",
host="",
port=5432,
dbname="",
)
engine = sa.create_engine(
f"postgresql+psycopg2://{DB['user']}:{DB['password']}@{DB['host']}:{DB['port']}/{DB['dbname']}",
)

# параметры CSV
csv_path = "big_file.csv"
chunk_rows = 100_000 # будем грузить по 100K строк
table_name = "example_csv"
schema = "raw"

# создаём таблицу один раз с нужными колонками
with engine.begin() as conn:
conn.exec_driver_sql(f"""
CREATE SCHEMA IF NOT EXISTS {schema};
DROP TABLE IF EXISTS {schema}.{table_name};
CREATE TABLE {schema}.{table_name} (
col_0 text, col_1 text, col_2 text, col_3 text, col_4 text,
col_5 text, col_6 text, col_7 text, col_8 text, col_9 text
);
""")

# грузим чанками
reader = pd.read_csv(
csv_path,
chunksize=chunk_rows,
iterator=True,
header=0,
)

for i, chunk in enumerate(reader, 1):
chunk.to_sql(
name=table_name,
con=engine,
schema=schema,
if_exists="append",
index=False,
method="multi",
)
print(f"Чанк {i}: {len(chunk)} строк залито")

print("Готово!")


Пересылайте коллегам, чтобы не терялись на собеседованиях и знали что ответить 💡

Чтобы поиграть дома в DE тетрадку с кодом прилагаю 🧠

#DataJungle #Postgres #ETL
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14
Media is too big
VIEW IN TELEGRAM
Сегодня раворачиваем #Postgres, настраиваем #CICD, выходим из #vim !🤝

Это финальное видео в серии о том как деплоить #Airflow и делать свои даги.
Как и обещал рассказал про CICD и о том как развернуть postgres, чтобы наши данные было бы где хранить.

Как разворачивать postgres в #ubuntu как настоящий девопс?

1. Устанавливаем саму базу:

sudo apt install postgresql


2. Разрешаем слушать внешние адреса файл ( /etc/postgresql/*/main/postgresql.conf ):
 
listen_addresses = '*'


3. Разрешаем коннект именно с вашего IP(файл /etc/postgresql/*/main/pg_hba.conf ) на нижней строке добавляем.

host DB_NAME USSERNAME YOUR_IP md5


4. Заходим в CLI psql создаем базу и пользователя:

sudo -u postgres psql

CREATE DATABASE DB_NAME;
CREATE USER USERNAME WITH PASSWORD 'PASSWORD';


Все на этом базовый деплой базы будет завершен. Все остальные подробности в видео!

Атракцион невиданной щедрости! 🔔
И самое главное, я обещал что вы поиграете в реального #DataEngineer что для этого надо:
1. Быть подписанным на классный канал про дата инжиниринг @data_jungle.
2. Быть в чате этого канала.
3. В комментарии к этому посту написать "хочу поиграть в DE ВАШ НИК В ГИТХАБ и ВАШ IP ноута с которого работаете".

После этого вы получите
- инвайт в репозиторий
- крэды в Airflow
- крэды в БД на ЧТЕНИЕ(это значит что инсерт вы сможете сделать только через ДАГ, а читать можете с jupyter из дома).

Это позволит вам со своей ветки пушить код в наш скромный продакшн 👀 я буду лично проводить ревью кода и писать комментарии чтобы вы могли становится лучше.

Правила игры:
- нельзя использовать метод pandas.to_sql все делаем только через хук как на видео, предварительно создавайте таблицы через таску в #airflow в схеме raw_data.
- данные можете качать какие угодно, любой #API который нравится, просто добавляйте ваш ключ в переменные и пользуйтесь.
- нельзя хранить больше 1М строк в вашей таблице в базе.

Не бойтесь уронить Airflow, вы учитесь это нормально, задавайте вопросы не стейсняйтесь, и да прибудет с вами сила 🔫
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1
#PyTrickMonday

С началом новой недели друзья, сегодня 3 нереально крутых приёма в #pandas, про которые большинство даже не догадываются 👀

1️⃣Series.explode() - превращаем «списки в ячейках» в длинную таблицу одной строчкой. Очень полезная штука если например у вас есть вот такой объект когда к 1 заказу есть два айтема и вы бы хотели получить такой вид:
order_id items
0 101 BTC
1 101 ETH
2 102 DOGE


import pandas as pd

orders = pd.DataFrame({
"order_id": [101, 102],
"items": [["BTC", "ETH"], ["DOGE"]]
})
flat = orders.explode("items", ignore_index=True)

Просто используйте метод explode() для вашей колонки с вложенными данными ✔️

2️⃣ #DataFrame .pipe() - конвейер #DataEngineering по взрослому.

Pandas любит цепочки вызовов вместо лапши кода. Если вам надо применить к датафрейму много функций то можно выстроить это в pipeline очень легко и просто.


def add_profit(df):
return df.assign(profit=df.sales - df.cost)

def top(df, n=5):
return df.nlargest(n, "profit")

df = (pd.read_csv("shop.csv")
.pipe(add_profit) # добавляем новую колонку профит
.pipe(top, n=3)) # берем топ 3 по профиту

# передаёшь прямо в pipe свои аргументы функций

Очень удобно, меньше кода больше эффективности ✔️

3️⃣DataFrame.convert_dtypes() - минус RAM в одну команду.
Загружаешь CSV - и видишь большинство типов object и для дальнейшей работы надо делать преобразование типов .astype().


raw = pd.read_csv("huge.csv")
clean = raw.convert_dtypes()

# переводит «строки» в современный тип string[pyarrow] или string
# меняет int64 на nullable Int64, NaN больше не проблема
# падает объём памяти до ×4 раз (часто - и больше)

Одна строчка экономит кучу памяти ✔️

Напиши в комментариях сколько удалось сэкономить места с .convert_dtypes() проверь на каком то жирном DataFrame🧐

Пересылай друзьям и коллегам, уверен они будут благодарны тебе 👀
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9😱3🤯1
Если вдруг вы засомневаетесь в себе просто напишите в GPT вопрос:

"В чем моя уникальность?"

Ляяяяя я конечно гений 🤑😁😅

Кидайте в коменты ваши ответы 👋
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
#SQLWednesday

Сегодня будем разбирать задачу с собеседования #DataEngineer или #DataAnalyst задача покажет хорошее знание SQL а также ваши способности как аналитика!
Итак ДАНО:

😀У нас есть таблица поездок на самокатах.
Нужно посчитать, сколько в среднем простаивает самокат между поездками каждый день.
Вот так выгледят данные:

scooter_id | ride_id | user_id | ride_start_time
101 | R1 | 2001 | 2024-06-01 09:00:00
101 | R2 | 2002 | 2024-06-01 11:00:00
101 | R3 | 2003 | 2024-06-01 15:00:00
101 | R4 | 2004 | 2024-06-02 10:30:00

Задачу можно сформулировать вот так: "для каждого самоката и каждой даты посчитать среднее время простоя между поездками (в минутах или часах — на твой вкус 😎)"
Давайте решать
(я не сторонник CTE) поэтому сначала давайте напишем запрос который бы показал предыдущее время поездки каждого скутера и поможет нам в этом функция #LAG:

SELECT
scooter_id,
ride_start_time,
DATE(ride_start_time) AS ride_date,
LAG(ride_start_time) OVER (PARTITION BY scooter_id ORDER BY ride_start_time) AS prev_start_time
FROM scooter_rides

Что получим(не очень красиво отображается, убрал в выводе ride_date):

| scooter_id | ride_start_time | prev_start_time |
|------------|---------------------|---------------------|
| 101 | 2024-06-01 09:00:00 | NULL |
| 101 | 2024-06-01 11:00:00 | 2024-06-01 09:00:00 |
| 101 | 2024-06-01 15:00:00 | 2024-06-01 11:00:00 |
| 101 | 2024-06-02 10:30:00 | 2024-06-01 15:00:00 |

Это именно то что мы хотели, чтобы посчитать разницу между поездками нам нужна колонка предыдущей поездки ⚡️
Что же делает LAG - смещает вашу колонку на единицу на основании PARTITION BY. Кстати в LAG(column, offset) вторым параметром можно передать на сколько строк вы хотите сместится по умолчанию это 1.
Уверен вы уже поняли как решать, но позвольте закончить - я сразу посчитаю разницу между текущем и предыдущим временем и сразу извлеку #EPOCH (секунды чтобы легко конвертировать в минуты):

SELECT
scooter_id,
ride_date,
ROUND(AVG(downtime_minutes), 1) AS avg_downtime_minutes
FROM (
SELECT
scooter_id,
DATE(ride_start_time) AS ride_date,
EXTRACT( EPOCH FROM ride_start_time - LAG(ride_start_time) OVER (PARTITION BY scooter_id ORDER BY ride_start_time)) / 60 AS downtime_minutes
FROM scooter_rides
) AS t
WHERE downtime_minutes IS NOT NULL -- чтобы пропустить строку с нулом
GROUP BY scooter_id, ride_date
ORDER BY scooter_id, ride_date;

Вуаля

| scooter_id | ride_date | avg_downtime_minutes |
|------------|-------------|----------------------|
| 101 | 2024-06-01 | 180.0 |
| 101 | 2024-06-02 | 1170.0 |


Важно что надо помнить при решении таких задач ВСЕГДА начинайте с внутреннего запроса с оконной функцией, потому что сформировав его вы уже решили задачу на 70%.

Пересылайте друзьям и коллегам, сохраняйте себе в избранное в телеграмме чтобы не забыть ☺️
Можете ли вы решить задачу как то еще пишите в комментариях 😄
#DataJungle #SQL
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
#PyTrickMonday

С началом новой недели друзья! Самое время разобрать парочку полезных #python трюков 🐍

1️⃣Универсальная распаковка (* и **)
Как по-красоте «разворачивать» коллекции и аргументы? Уверен вы уже видели эти загадочные звезды в коде

def connect(host, port, user, password):
print(f"Connecting to {host}:{port} as {user}")

cfg = {"host":"localhost","port":5432}
auth = {"user":"postgres","password":"secret"}

# Объединяем словари и распаковываем сразу в функцию
connect(**{**cfg, **auth})
# Connecting to localhost:5432 as postgres

# Распаковка списка в позиционные аргументы
coords = [10.0, 20.0, 30.0]
print(*coords) # 10.0 20.0 30.0

# Для настоящего сигмы
first, *middle, last = ["start", "step1", "step2", "end"]
print(first, middle, last) # start ['step1','step2'] end

Правила такие:
- * собирает или пакует(распакует) в лист кусочек вашего итерируемого объекта даже строки:

>>> s='1234'
>>>first, *mid, last = s
>>> first
'1'
>>> mid
['2', '3', '4']
>>> last
'5'

- ** именованные параметры работает только для словарей, очень удобно передавать в функции или классы как объект.

def log_event(event_type, **details):
print(f"Event: {event_type}")
for key, value in details.items():
print(f" {key}: {value}")

# Вызов с произвольными именованными аргументами
log_event(
"user_login",
user_id=123,
timestamp="2025-06-15T10:00:00",
ip="192.168.0.1"
)

# Вывод:
# Event: user_login
# user_id: 123
# timestamp: 2025-06-15T10:00:00
# ip: 192.168.0.1



2️⃣Типизированные датаклассы #dataclass
Идеально для работы с API или объектами из базы данных. Идеальное решения для бэкенда.

from dataclasses import dataclass

@dataclass
class User:
id: int
name: str
email: str

# Плюс: с Python 3.10+ можно явно указать List[str], Optional[int] и т.д.

u = User(id=1, name="Alice", email="[email protected]")
print(u)
# User(id=1, name='Alice', email='[email protected]')

И тут напршивается а что если использовать распаковку ** с дата классами? Да легко!


# Предположим, получаем параметры из API вот в таком виде и нам надо дальше с ними удобно работать.
data = {"id":2,"name":"Bob","email":"[email protected]"}
user = User(**data) # Всё в одну строку!

# получаем доступ к любому элементу
user.id, user.name # 2, 'Bob'


Что мы получаем зная такие классные трюки?

- */** вы избавляетесь от ручного «распихивания» параметров.
- Единая точка правды для данных, dataclass позволяет задать структуру данных в одном месте, а затем
распаковывать в неё имеющиеся словари и доставать данные.
Никаких «лишних» проверок типов и ручного присвоения - всё берётся из аннотаций.
Ваша модель данных всегда синхронизирована: если вы добавите новое поле, IDE и линтер сразу подскажут места, где нужно передать или обработать его.

Где используем?

Конфигурации: храните настройки в YAML/JSON, загружайте как словарь и сразу распаковывайте в @dataclass.
API-клиенты: передавайте параметры запросов или заголовков одним **kwargs, избегая многословных вызовов.
Модели данных: вместо голых словарей для бизнес-логики оперируйте @dataclass - IDE подскажет нужные атрибуты и защитит от опечаток.

Пересылайте коллегам чтобы они уже сегодня использовали 👀
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6😱2
Всем привет 👋
Вот как раз интересное исследование на тему моего эксперимента.

Частое использование ChatGPT ОТУПЛЯЕТ и критическое мышление у человека.

Учёные Массачусетского технологического института провели исследование влияния ИИ на мозг человека. В исследовании приняли участие 54 участника 18-39 лет. Участники были разделены на 3 группы: тех, кто писали эссе с помощью ChatGPT, тех, кто писал с помощью Google и тех, кто не использовал никаких инструментов.

У группы, использовавшей ChatGPT, была САМАЯ НИЗКАЯ активность мозга. При этом не были задействованы области мозга, отвечающие за креативность и мышление.

Главное- ChatGPT увеличивал продуктивность на 60%, но при этом снижал когнитивные способности на 32%.
1
Привет дорогой подписчик!
Я был в отпуске 🏝️
с завтра посты будут выходить вновь регулярно
По понедельникам змеиные трюки #PyTrickMonday
По средам разбираем SQL #SQLWednesday
И конечно большие разборы интересных тем 😄
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👏1