#sql #google-bigquery #sum #partition
# #sql #google-bigquery #сумма #раздел
Вопрос:
У меня есть таблица SQL в следующем виде:
Результат timestamp user_id
1 125 1
1 125 0
2 125 1
2 144 0
3 156 1
3 125 1
и т.д….
Если я хочу найти скользящее среднее значение результатов пользователей, я могу использовать:
SELECT user_id, timestamp,
AVG(result) OVER (PARTITION BY user_id ORDER BY timestamp ASC ROWS UNBOUNDED PRECEDING)
FROM table
Если у пользователя был только один результат на метку времени, это работает нормально, но если у пользователя есть несколько результатов в одной метке времени, он будет усредняться по результатам на этой метке времени плюс метки времени перед ним.
Итак, мой вопрос в том, как я могу убедиться, что оно усредняет только результаты, которые произошли, когда временная метка меньше текущей временной метки?
Заранее спасибо
Ответ №1:
По сути, вам нужен другой столбец, чтобы разорвать связи, например, первичный ключ таблицы, например, — в противном случае одноранговые строки рассматриваются все вместе в среднем по окну. Предполагая id
:
select t.*
avg(result) over(partition by user_id order by timestamp, id) as avg_result
from mytable t
Обратите внимание, что rows
спецификация фрейма здесь не нужна (как в вашем исходном запросе).
Если у вас нет такого столбца, мы можем сгенерировать его с row_number()
помощью , что гарантирует, что каждому номеру присваивается только один раз. Однако результат не является согласованным, то есть при наличии связей одной и той же строке может быть присвоен другой номер строки во время последовательного выполнения одного и того же запроса:
select t.*
avg(result) over(partition by user_id order by timestamp, rn) as avg_result
from (
select t.*,
row_number() over(partition by user_id order by timestamp) as rn
from mytable t
) t
Комментарии:
1. Я согласен со всем этим, но, похоже, это не работает — оно по-прежнему усредняется по строкам, которые имеют одинаковую временную метку, вместо того, чтобы усреднять только те, у которых временная метка меньше текущей временной метки. — Я использую Google BigQuery, так что, может быть, именно поэтому?
Ответ №2:
Как насчет этого?
SELECT [timestamp]
, [user_id]
, [result]
, [final_avg] = (
CONVERT(FLOAT, SUM([result]) OVER(PARTITION BY [user_id], [timestamp] ORDER BY [timestamp], [user_id], [result] ROWS UNBOUNDED PRECEDING))
/ COUNT([timestamp]) OVER(PARTITION BY [user_id], [timestamp] ORDER BY [timestamp], [user_id], [result] RANGE UNBOUNDED PRECEDING))
FROM table
ORDER BY [timestamp], [user_id], [result]
Результат:
timestamp user_id result final_avg
1 125 0 0
1 125 1 0.5
2 125 1 1
2 144 0 0
3 125 1 1
3 156 1 1
Комментарии:
1. Привет, Марк, спасибо за ответ, к сожалению, это не сработает — вторая строка должна игнорировать первую в скользящем подсчете, поскольку временная метка одинакова
2. Нет проблем. Какого результата вы хотите достичь? Например, каким будет ожидаемый результат
final_avg
для первых трех строк в таблице результатов выше?3. Конечный результат будет: final_avg: null, null, 0.5, null, 0.666666, null. Единственное среднее значение для примера будет 125, поскольку для остальных это первая запись, спасибо за вашу помощь!
Ответ №3:
Вы можете использовать спецификацию рамки окна. В стандартном SQL это было бы:
SELECT user_id, timestamp,
AVG(result) OVER (PARTITION BY user_id
ORDER BY timestamp ASC
RANGE BETWEEN UNBOUNDED PRECEDING AND INTERVAL '1 SECOND' PRECEDING
)
FROM table;
Редактировать:
В BigQuery вы бы сделали:
Это стандартная функциональность SQL, но не все базы данных поддерживают ее.
SELECT user_id, timestamp,
AVG(result) OVER (PARTITION BY user_id
ORDER BY UNIX_SECONDS(timestamp) ASC
RANGE BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
)
FROM table;
Предполагается, что секунды являются достаточной степенью детализации для различения предыдущих времен.
Комментарии:
1. Привет, Гордон, спасибо за ответ — к сожалению, я подаю в суд на Bigquery, и он не поддерживает это