Цикл по массиву и объединение в цикл приводят к BigQuery

#loops #google-bigquery #code-cleanup

#циклы #google-bigquery #очистка кода

Вопрос:

Я написал скрипт для выполнения следующего

  • создайте массив day_event , содержащий EventID для событий при условии 1, для которых требуется очистка методом 1
  • создайте массив night_event для условия 2, для которого требуется очистка методом 2
  • создайте массив cross_day_event для условия 3, для которого требуется метод очистки 3
  • повторите цикл запроса day_event и очистите каждое событие, используя метод 1
  • повторите цикл запроса night_event и очистите каждое событие, используя метод 2
  • повторите цикл запроса cross_day_event и очистите каждое событие, используя метод 3
    — ПРИМЕЧАНИЕ: после очистки одна строка будет разбита на несколько строк
  • запрашивайте исходные данные, но удаляйте исходную запись и объединяйте все очищенные данные

Я считаю, что сценарий мог бы быть намного чище, но я не знаю как. Есть предложения? Спасибо!

 declare day_event array<int64>;
declare night_event array<int64>;
declare cross_day_event array<int64>;
declare i int64 default 0;
declare j int64 default 0;
declare k int64 default 0;

-- query three arrays based on different conditions. 
-- each condition needs a slightly different cleaning method.
set day_event = array(
select eventId from
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
from `event_raw_data`)
where condition_1
);
set night_event = array(
select eventId from
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
from `event_raw_data`)
where condition_2
);
set cross_day_event = array(
select eventId from
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
from `event_raw_data`)
where condition_3
);

-- array 1 and condition 1
create or replace temp table day_event_clean as
select cast(null as int64) as eventId, cast(null as timestamp) as startTime_adj, cast(null as timestamp) as endTime_adj;

loop 
set i = i 1;
if i > array_length(day_event) then leave;
end if;
create or replace temp table day_event_clean as
-- here is the cleaning I need to do
select t.eventId, greatest(startTime_adj, dt) as startTime_adj, least(timestamp_add(dt, interval 12 hour), endTime_adj) as endTime_adj from 
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj from `event_raw_data` where eventId = day_event[ordinal(i)]) t cross join
unnest(generate_timestamp_array(timestamp(date(startTime_adj)), timestamp_add(timestamp(date(endTime_adj)), interval 12 hour), interval 12 hour)) dt
union all select * from day_event_clean;
-- end of cleaning
end loop;

-- array 2 and condition 2
create or replace temp table night_event_clean as
select cast(null as int64) as eventId, cast(null as timestamp) as startTime_adj, cast(null as timestamp) as endTime_adj;

loop
set j = j 1;
if j > array_length(night_event) then leave;
end if;
create or replace temp table night_event_clean as
select t.eventId, greatest(startTime_adj, dt) as startTime_adj, least(timestamp_add(dt, interval 12 hour), endTime_adj) as endTime_adj from 
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj from `event_raw_data` where eventId = night_event[ordinal(j)]) t cross join
unnest(generate_timestamp_array(timestamp_add(timestamp(date(startTime_adj)), interval 12 hour), timestamp_add(timestamp(date(endTime_adj)), interval 12 hour), interval 12 hour)) dt
union all select * from night_event_clean;
end loop;

-- array 3 and condition 3
create or replace temp table cross_day_event_clean as
select cast(null as int64) as eventId, cast(null as timestamp) as startTime_adj, cast(null as timestamp) as endTime_adj;

loop
set k = k 1;
if k > array_length(cross_day_event) then leave;
end if;
create or replace temp table cross_day_event_clean as 
select t.eventId, greatest(startTime_adj, dt) as startTime_adj, least(timestamp_add(dt, interval 12 hour), endTime_adj) as endTime_adj from 
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj from `event_raw_data` where eventId = cross_day_event[ordinal(k)]) t cross join
unnest(generate_timestamp_array(timestamp_add(timestamp(date(startTime_adj)), interval 12 hour), timestamp(date(endTime_adj)), interval 12 hour)) dt
union all select * from cross_day_event_clean;
end loop;

--query the original data and union with all the cleaned data
select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj from `event_raw_data` where eventId not in
(condition_1 or condition_2 or conditon_3) union all
select * from day_event_clean where eventId is not null union all
select * from night_event_clean where eventId is not null union all
select * from cross_day_event_clean where eventId is not null
order by startTime_adj;
  

Ответ №1:

Быстрая рекомендация № 1 от быстрого просмотра верхней части вашего скрипта

Итак, приведенный ниже фрагмент слишком подробный и, что наиболее важно, запрашивает одну и ту же таблицу три раза — так что стоимость в 3 раза больше, чем должна быть!!!

 -- query three arrays based on different conditions. 
-- each condition needs a slightly different cleaning method.
set day_event = array(
select eventId from
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
from `event_raw_data`)
where condition_1
);
set night_event = array(
select eventId from
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
from `event_raw_data`)
where condition_2
);
set cross_day_event = array(
select eventId from
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
from `event_raw_data`)
where condition_3
);    
  

вместо above вы должны использовать below simple и только с одним сканированием вашего скрипта таблицы

 set (day_event, night_event, cross_day_event) = (
  select as struct
    array_agg(if(condition_1, id, null) ignore nulls ) array1,
    array_agg(if(condition_2, id, null) ignore nulls ) array2,
    array_agg(if(condition_3, id, null) ignore nulls ) array3
  from (
    select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj
    from `event_raw_data` 
  )
);   
  

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

Еще один совет — использование циклов в вашем случае вообще не оправдано — вам не нужно выполнять цикл только для перебора элементов массивов — вы скорее делаете это заданным способом (sql-способом — в одном запросе), используя функцию unnest

Наконец, если вам все еще понадобится помощь — я рекомендую вам упростить пример вашей логики цикла и опубликовать его как отдельный вопрос — в противном случае он (текущий вопрос) слишком широк, чтобы ответить и рассмотреть все возможные улучшения (которых здесь довольно много)

Обновить

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

 -- array 1 and condition 1
create or replace temp table day_event_clean as
select cast(null as int64) as eventId, cast(null as timestamp) as startTime_adj, cast(null as timestamp) as endTime_adj;

loop 
set i = i 1;
if i > array_length(day_event) then leave;
end if;
create or replace temp table day_event_clean as
-- here is the cleaning I need to do
select t.eventId, greatest(startTime_adj, dt) as startTime_adj, least(timestamp_add(dt, interval 12 hour), endTime_adj) as endTime_adj from 
(select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj from `event_raw_data` where eventId = day_event[ordinal(i)]) t cross join
unnest(generate_timestamp_array(timestamp(date(startTime_adj)), timestamp_add(timestamp(date(endTime_adj)), interval 12 hour), interval 12 hour)) dt
union all select * from day_event_clean;
-- end of cleaning
end loop;   
  

вы можете просто использовать только один простой запрос

 create or replace temp table day_event_clean as
select t.eventId, greatest(startTime_adj, dt) as startTime_adj, least(timestamp_add(dt, interval 12 hour), endTime_adj) as endTime_adj 
from (
  select eventId, timestamp_sub(startTime_CST, interval  6 hour) as startTime_adj, timestamp_sub(endTime_CST, interval  6 hour) as endTime_adj 
  from `event_raw_data` 
  where eventId in unnest(day_event)
) t 
cross join unnest(
  generate_timestamp_array(timestamp(date(startTime_adj)), timestamp_add(timestamp(date(endTime_adj)), interval 12 hour), interval 12 hour)
) dt     
  

это не только менее подробно и проще в обслуживании — это экономит вам $$$, потому что вместо сканирования event_raw_data таблицы столько раз, сколько элементов в соответствующем массиве, приведенный выше запрос выполняет только единицы!!!

тот же подход применим к остальным двум циклам

и, наконец, после того, как вы примените все вышесказанное и получите компактный и управляемый скрипт — я действительно надеюсь, что вы увидите, что весь ваш оригинальный [огромный] скрипт может быть реализован как один относительно простой запрос — всего один. как я упоминал ранее — если вы дойдете до этого момента и вам все еще понадобится помощь — отправьте новый вопрос, используя скрипт, к которому вы сможете добраться к этому моменту

удачи : o)

Комментарии:

1. Смог очистить свой код, следуя примерам. Теперь за ним легко следить, и он выполняется намного быстрее. Большое вам спасибо!!