I have the following table called 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 |
from which I need the latest (by tstamp ) non-null value from val for each user that is not X. If there is no other value for a user other than X, it should just be X (like user 2). If there was no value for a user whatsoever, it should remain NULL (like for user 4). The intended outcome for the above table is the following:
| user_id | last_val |
|---|---|
| 1 | a |
| 2 | X |
| 3 | a |
| 4 | |
| 5 | c |
| 6 | X |
| 7 | d |
I'm mainly looking for a way to do this with the least amount of CTEs necessary as I already have a solution for this, which goes as follows:
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;
This seems to be too complicated, but it produces the intended outcome. I'm pretty sure there is a better way to do this.
An alternative way for it could be by
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
and then in a later CTE I can coalesce the last_value_without_x and last_value_without_nulls to arrive at what's needed, but unfortunately, arbitrary values cannot be ignored by last_value().
I'm using Snowflake if that helps.