mod() для нарезки на батчи

В бытность работы в Вайлдберриз, часто приходилось реализовывать загрузку таблицы частями - мой airflow-оператор успешно работал на кусках примерно по 100 тыс. - 1 млн. строк, в зависимости от ширины таблицы. Проблем с нарезкой не было - почти всегда было какое то непрерывно растущее поле - обычно целочисленное, условный row_id. Нашёл минимальное/максимальное значение и делаешь выборку

...
for cycle in range(min_id, max_id, chunk_size):
    query = f"select * from table where row_id between {cycle} and {cycle + chunk_size - 1}"
    cur.execute(query)
...

или более тяжёлый для сервера вариант (сортировка), зато батчи получаются ровные, независимо от пропусков в row_id

...
while cycle < max_id:
    query = f"select * from table where row_id > {cycle} order by row_id limit {chunk_size}"
    cur.execute(query)
    # cycle = row_id из последней строки в батче
...

Если целочисленного поля нет, можно обойтись датой или таймстампом (условно create_dt/create_dttm) по аналогии

Неожиданно вспомнилось про это, когда надо было сделать джойн огромных таблиц и запрос никак не укладывался в лимит по спиллам. Нарезать по первому варианту - муторно, в id много пропусков. По второму варианту - сортировать огроменную таблицу, да ещё несколько раз - тоже не хочется (по идее тоже убьют запрос по спиллам). Подсмотрел у коллеги такой вариант:

const = 2  # количество батчей на которые разбиваем запрос
i = 1

while i <= const:
    print(f'батч {i} из {const}')

    query = f'''
    insert into dst_table
    select *
    from t1
    join t2 on t1.id = t2.id
    where mod(t1.serno, {const}) = {i}'''
    
    cur.execute(query)
    i = i + 1
...

“Углехак” (ugly hack) - как говорил мой тимлид в Вайлдберриз, но удобно для одноразовых скриптов

Written on April 13, 2024