#sql #snowflake-cloud-data-platform
#sql #snowflake-cloud-data-platform
Вопрос:
У меня есть следующая таблица, которая называется t
:
user_id | val | tstamp |
---|---|---|
1 | a | 2021-01-01 10:00:00 |
1 | b | 2021-01-01 10:01:00 |
1 | X | 2021-01-01 10:02:00 |
1 | X | 2021-01-01 10:04:00 |
1 | a | 2021-01-01 10:05:00 |
1 | X | 2021-01-01 10:06:00 |
2 | X | 2021-01-01 10:07:00 |
2 | X | 2021-01-01 10:08:00 |
3 | a | 2021-01-01 10:07:00 |
3 | a | 2021-01-01 10:08:00 |
4 | 2021-01-01 10:08:00 | |
5 | c | 2021-01-01 10:09:00 |
5 | 2021-01-01 10:10:00 | |
6 | 2021-01-01 10:09:00 | |
6 | X | 2021-01-01 10:10:00 |
7 | d | 2021-01-01 10:09:00 |
7 | X | 2021-01-01 10:10:00 |
из которого мне нужно последнее (по tstamp
) ненулевое значение val
для каждого пользователя, которого нет X
. Если для пользователя нет другого значения, кроме X
, оно должно быть просто X
(например, user 2). Если для пользователя вообще не было значения, оно должно остаться NULL
(например, для пользователя 4). Предполагаемый результат для приведенной выше таблицы следующий:
user_id | last_val |
---|---|
1 | a |
2 | X |
3 | a |
4 | |
5 | c |
6 | X |
7 | d |
В основном я ищу способ сделать это с наименьшим количеством необходимых CTE, поскольку у меня уже есть решение для этого, которое выглядит следующим образом:
with user_values as (
select
user_id,
count(distinct val) as types_of_vals,
coalesce(boolor_agg(val = 'X'),false) as has_value_x
from t
group by 1
),
user_last_value as (
select distinct
user_id,
last_value(val) over (partition by user_id order by tstamp) as last_value_without_x
from t
where val!='X'
)
select distinct
user_id,
case
when types_of_vals=0 then null
when (types_of_vals=1 and has_value_x) then 'X'
else
last_value_without_x
end as last_val
from t
left join user_values using (user_id)
left join user_last_value using (user_id)
order by user_id;
Это кажется слишком сложным, но оно дает желаемый результат. Я почти уверен, что есть лучший способ сделать это.
Альтернативным способом для этого может быть
select
*,
last_value(val) ignore 'X' over (partition by user_id order by tstamp) as last_value_without_x, -- this won't work
last_value(val) ignore nulls over (partition by user_id order by tstamp) as last_value_without_nulls
from t
а затем в более позднем CTE я могу объединить last_value_without_x
и last_value_without_nulls
, чтобы получить то, что нужно, но, к сожалению, произвольные значения не могут быть проигнорированы last_value()
.
Я использую Snowflake, если это поможет.
Ответ №1:
Я бы просто использовал row_number()
для этого:
select user_id, val as last_val
from (select t.*,
row_number() over (partition by user_id
order by (val is not null) desc,
(val = 'X') desc,
timestamp desc)
) as seqnum
from t
) t
where seqnum = 1;
Вы можете использовать явные преобразования для order by
(т.е. (val = 'x')::int desc
), если хотите четко указать преобразование типов.
Комментарии:
1. Я считаю это наиболее читаемым, полным и простым в использовании решением. Спасибо!
Ответ №2:
Использование NULLIF
:
WITH cte AS (
SELECT *, NULLIF(val, 'X') AS val_without_x FROM t
)
select *,
last_value(val_without_x) ignore nulls
over (partition by user_id order by tstamp) as last_value_without_x,
last_value(val) ignore nulls
over (partition by user_id order by tstamp) as last_value_without_nulls
from cte
Ответ №3:
Попробуйте это
select user_id, val last_val from (
select user_Id, val, row_number() over (partition by user_id order by tstamp desc) rn
from t
where val !='x' or (select count(*) from t t2 where t2.user_id=t.user_id and t2.val !='X' )=0
) x
where rn=1
Ответ №4:
с данными:
WITH data(user_id, val, tstamp) AS (
SELECT * FROM VALUES
(1, 'a', '2021-01-01 10:00:00'),
(1, 'b', '2021-01-01 10:01:00'),
(1, 'X', '2021-01-01 10:02:00'),
(1, 'X', '2021-01-01 10:04:00'),
(1, 'a', '2021-01-01 10:05:00'),
(1, 'X', '2021-01-01 10:06:00'),
(2, 'X', '2021-01-01 10:07:00'),
(2, 'X', '2021-01-01 10:08:00'),
(3, 'a', '2021-01-01 10:07:00'),
(3, 'a', '2021-01-01 10:08:00'),
(4, null,'2021-01-01 10:08:00'),
(5, 'c', '2021-01-01 10:09:00'),
(5, null,'2021-01-01 10:10:00'),
(6, null,'2021-01-01 10:09:00'),
(6, 'X', '2021-01-01 10:10:00'),
(7, 'd', '2021-01-01 10:09:00'),
(7, 'X', '2021-01-01 10:10:00')
)
следующее дает вам правильные ответы и немного короче, чем ответ Гордона:
SELECT DISTINCT
user_id,
LAST_VALUE(val) IGNORE NULLS OVER (PARTITION BY user_id ORDER BY val != 'X', tstamp)
FROM data;
и если вам не нравится DISTINCT it, ROW_NUMBER можно переместить в QUALIFY и по-прежнему использовать однослойный выбор:
SELECT DISTINCT
user_id,
val
FROM data
QUALIFY ROW_NUMBER() OVER (PARTION BY user_id ORDER BY val != 'X' DESC NULLS LAST, tstamp DESC) = 1
Также сортировка моих изменений NULL обрабатывается с помощью ПОРЯДКА сортировки по значению с ПОСЛЕДНИМИ значениями