Загрузка данных
import json
from datetime import datetime, timedelta
from urllib.parse import urljoin
import psycopg
import requests
from airflow import DAG
from airflow.operators.python import PythonOperator
from data_models.user_activity_log_model import UserActivityModel
from data_models.user_order_log_model import UserOrderModel
default_args = {
'owner': 'airflow',
'concurrency': 1,
'retries': 3,
'retry_delay': timedelta(seconds=10),
}
API_host = 'https://de-start-sprint-etl-airflow-api.de.education-services.ru'
PG_CONNECTION = {
'dbname':'',
'user':'',
'host':'',
'password':'',
'port':6432
}
def insert_user_order_log(**kwargs):
ds = kwargs['ds']
payload = {'limit': '20000000', 'filter': {'date': ds}}
resp = requests.post(urljoin(API_host, 'user_order_log'), data=json.dumps(payload))
resp.raise_for_status()
data = resp.json()
query = f"""INSERT INTO public.user_order_log( uniq_id, date_time, city_id, city_name, customer_id, first_name, last_name, item_id, item_name, quantity, payment_amount)
VALUES ( %(uniq_id)s, %(date_time)s, %(city_id)s, %(city_name)s, %(customer_id)s, %(first_name)s, %(last_name)s, %(item_id)s, %(item_name)s, %(quantity)s, %(payment_amount)s);"""
with psycopg.connect(
**PG_CONNECTION
) as conn:
with conn.cursor() as cur:
for row in data:
respmodel = UserOrderModel(**row)
cur.execute(query, respmodel.model_dump())
print('committed')
def insert_user_activity_log(**kwargs):
ds = kwargs['ds']
payload = {'limit': '20000000', 'filter': {'date': ds}}
resp = requests.post(urljoin(API_host, 'user_activity_log'), data=json.dumps(payload))
resp.raise_for_status()
data = resp.json()
query = f"""INSERT INTO public.user_activity_log( uniq_id, date_time, action_id, customer_id, quantity)
VALUES ( %(uniq_id)s, %(date_time)s, %(action_id)s, %(customer_id)s, %(quantity)s);"""
with psycopg.connect(
**PG_CONNECTION
) as conn:
with conn.cursor() as cur:
for row in data:
respmodel = UserActivityModel(**row)
cur.execute(query, respmodel.model_dump())
print('committed')
def load_d_customer():
query = """
WITH
api_data AS ( --актуальные на дату расчёта данные в источнике
SELECT DISTINCT
ON (uol.customer_id)
uol.customer_id,
uol.first_name,
uol.last_name
FROM public.user_order_log uol
WHERE
uol.date_time::date = '{{ds}}'
ORDER BY uol.customer_id, date_time DESC
)
INSERT INTO public.d_customer_stg (id, customer_id, first_name, last_name)
SELECT --получаем дельту и вставляем данные
dim.id,
uol.customer_id,
uol.first_name,
uol.last_name
FROM api_data uol
LEFT JOIN public.d_customer dim ON dim.customer_id = uol.customer_id
AND '{{ds}}' BETWEEN start_date AND end_date
WHERE ((uol.first_name != dim.first_name
OR uol.last_name != dim.last_name) and dim.end_date='9999-12-31') -- and dim.end_date='9999-12-31' условие нужно, чтобы не менять историю, даже если на источнике поменялось
OR dim.customer_id IS NULL;
BEGIN TRANSACTION;
UPDATE public.d_customer
set end_date = '{{ds}}'::date - interval '1 day'
WHERE id IN (SELECT id
FROM public.d_customer_stg
WHERE id IS NOT NULL); --обновляем end_date
INSERT INTO public.d_customer(customer_id, first_name, last_name, start_date, end_date)
SELECT stg.customer_id,
stg.first_name,
stg.last_name,
'{{ds}}',
'9999-12-31'
FROM public.d_customer_stg stg; -- вставляем актуальное инфо
INSERT INTO public.d_customer(customer_id, first_name, last_name,start_date, end_date)
SELECT DISTINCT
customer_id,
'неизвестно',
'неизвестно' ,
'{{ds}}',
'9999-12-31'
FROM public.user_activity_log uol
LEFT JOIN public.d_customer dim USING (customer_id)
WHERE dim.id IS NULL; --добавляем клиентов, которые пока не совершали заказов
COMMIT TRANSACTION;
"""
with psycopg.connect(
**PG_CONNECTION
) as conn:
with conn.cursor() as cur:
cur.execute(query)
def load_d_city():
query = """
WITH
api_data AS ( --актуальные на дату расчёта данные в источнике
SELECT DISTINCT
ON (uol.city_id)
uol.city_id,
uol.city_name
FROM public.user_order_log uol
WHERE
uol.date_time::date = '{{ds}}'
ORDER BY uol.city_id, date_time DESC
)
INSERT INTO public.d_city_stg (id, city_id, city_name)
SELECT --получаем дельту и вставляем данные
dim.id,
uol.city_id,
uol.city_name
FROM api_data uol
LEFT JOIN public.d_city dim on dim.city_id = uol.city_id
and '{{ds}}' BETWEEN start_date AND end_date
WHERE (uol.city_name != dim.city_name and dim.end_date='9999-12-31') -- and dim.end_date='9999-12-31' условие нужно, чтобы не менять историю, даже если на источнике поменялось
OR dim.city_id IS NULL;
BEGIN TRANSACTION;
UPDATE public.d_city
set end_date = '{{ds}}'::date - interval '1 day'
WHERE id IN (SELECT id
FROM public.d_city_stg
WHERE id IS NOT NULL); --обновляем end_date
INSERT INTO public.d_city(city_id, city_name, start_date, end_date)
SELECT stg.city_id,
stg.city_name,
'{{ds}}',
'9999-12-31'
FROM public.d_city_stg stg; -- вставляем актуальное инфо
COMMIT TRANSACTION;
""" # задаем наш запрос
with psycopg.connect(
**PG_CONNECTION
) as conn: # подключаемся к БД
with conn.cursor() as cur:
cur.execute(query) # выполняем скрипт
def load_d_item():
query = """
WITH
api_data AS ( --актуальные на дату расчёта данные в источнике
SELECT DISTINCT
ON (uol.item_id)
uol.item_id,
uol.item_name
FROM public.user_order_log uol
WHERE
uol.date_time::date = '{{ds}}'
ORDER BY uol.item_id, date_time DESC
)
INSERT INTO public.d_item_stg (id, item_id, item_name)
SELECT --получаем дельту и вставляем данные
dim.id,
uol.item_id,
uol.item_name
FROM api_data uol
LEFT join public.d_item dim on dim.item_id = uol.item_id
and '{{ds}}' BETWEEN start_date AND end_date
WHERE (uol.item_name != dim.item_name and dim.end_date='9999-12-31') -- and dim.end_date='9999-12-31' условие нужно, чтобы не менять историю, даже если на источнике поменялось
OR dim.item_id IS NULL;
BEGIN TRANSACTION;
UPDATE public.d_item
set end_date = '{{ds}}'::date - interval '1 day'
WHERE id IN (SELECT id
FROM public.d_item_stg
WHERE id IS NOT NULL); --обновляем end_date
INSERT INTO public.d_item(item_id, item_name, start_date, end_date)
SELECT stg.item_id,
stg.item_name,
'{{ds}}',
'9999-12-31'
FROM public.d_item_stg stg; -- вставляем актуальное инфо
COMMIT TRANSACTION;
"""
with psycopg.connect(
**PG_CONNECTION
) as conn:
with conn.cursor() as cur:
cur.execute(query)
def load_f_order():
query = """
INSERT INTO public.f_order (order_id, create_date, customer_id, city_id, item_id, quantity, payment_amount)
SELECT DISTINCT ON (uniq_id)
uniq_id AS order_id,
date_time::date AS create_date,
cus.id AS customer_id,
c.id AS city_id,
it.id AS item_id,
quantity,
payment_amount
FROM public.user_order_log uol
JOIN public.d_city c on c.city_id = uol.city_id and '{{ds}}' BETWEEN c.start_date AND c.end_date
JOIN public.d_customer cus ON cus.customer_id = uol.customer_id and '{{ds}}' BETWEEN cus.start_date AND cus.end_date
JOIN public.d_item it on it.item_id = uol.item_id and '{{ds}}' BETWEEN it.start_date AND it.end_date
WHERE uol.date_time::date = '{{ds}}'
ORDER BY uniq_id, date_time DESC;
"""
with psycopg.connect(
**PG_CONNECTION
) as conn:
with conn.cursor() as cur:
cur.execute(query)
def load_f_activity():
query = """
INSERT INTO public.f_activity (activity_id, create_date, customer_id, action_id, quantity)
SELECT DISTINCT ON (uniq_id)
uniq_id AS activity_id,
date_time::date AS create_date,
cus.id AS customer_id,
ual.action_id ,
quantity
FROM public.user_activity_log ual
JOIN public.d_customer cus ON cus.customer_id = ual.customer_id and '{{ds}}' BETWEEN cus.start_date AND cus.end_date
WHERE ual.date_time::date = '{{ds}}'
ORDER BY uniq_id, date_time DESC;
"""
with psycopg.connect(
**PG_CONNECTION
) as conn:
with conn.cursor() as cur:
cur.execute(query)
with DAG('api_data_load',
default_args=default_args,
start_date=datetime(2024, 1, 1),
schedule_interval='@daily',
catchup=True,
max_active_runs=1) as dag:
order_log = PythonOperator(task_id='order_log',
python_callable=insert_user_order_log,
provide_context=True)
activity_log = PythonOperator(task_id='activity_log',
python_callable=insert_user_activity_log,
provide_context=True)
d_customer = PythonOperator(task_id='d_customer',
python_callable=load_d_customer,
provide_context=True)
d_city = PythonOperator(task_id='d_city',
python_callable=load_d_city,
provide_context=True)
d_item = PythonOperator(task_id='d_item',
python_callable=load_d_item,
provide_context=True)
f_order = PythonOperator(task_id='f_order',
python_callable=load_f_order,
provide_context=True)
f_activity = PythonOperator(task_id='f_activity',
python_callable=load_f_activity,
provide_context=True)
(order_log >> activity_log >> d_customer >> d_city >>
d_item >> f_order >> f_activity)