I have two tables. One contains user state transitions.
create table state (
id serial primary key,
ctime timestamp with time zone not null,
state text not null
);
insert into state (ctime, state) values
('2019-05-01 03:58:40+00', 'Busy')
, ('2019-05-01 03:58:42+00', 'Ready')
, ('2019-05-01 04:00:14+00', 'Busy')
, ('2019-05-01 04:16:26+00', 'Ready')
, ('2019-05-01 04:16:36+00', 'Busy')
;
And the other one contains user’s actions.
create table action (
id serial primary key,
ctime timestamp with time zone not null,
action text not null
);
insert into action (ctime, action) values
('2019-05-01 03:58:42+00', 'vasah')
, ('2019-05-01 03:58:42+00', 'mituh')
, ('2019-05-01 04:00:14+00', 'jumuf')
, ('2019-05-01 04:00:16+00', 'vibaj')
, ('2019-05-01 04:00:16+00', 'sasij')
, ('2019-05-01 04:16:21+00', 'husih')
, ('2019-05-01 04:16:26+00', 'radod')
, ('2019-05-01 04:16:30+00', 'zadub')
, ('2019-05-01 04:16:35+00', 'mimoh')
, ('2019-05-01 04:16:36+00', 'rimoh')
, ('2019-05-01 04:16:37+00', 'zahuf')
, ('2019-05-01 04:16:37+00', 'fisak')
;
It is easy to union these tables and visually see in which state an action was performed.
select *
from (
select ctime, state, null from state
union all
select ctime, null, action from action) x
order by ctime;
Output:
2019-05-01 06:58:40+03 | Busy |
2019-05-01 06:58:42+03 | | mituh
2019-05-01 06:58:42+03 | | vasah
2019-05-01 06:58:42+03 | Ready |
2019-05-01 07:00:14+03 | | jumuf
2019-05-01 07:00:14+03 | Busy |
2019-05-01 07:00:16+03 | | vibaj
2019-05-01 07:00:16+03 | | sasij
2019-05-01 07:16:21+03 | | husih
2019-05-01 07:16:26+03 | Ready |
2019-05-01 07:16:26+03 | | radod
2019-05-01 07:16:30+03 | | zadub
2019-05-01 07:16:35+03 | | mimoh
2019-05-01 07:16:36+03 | Busy |
2019-05-01 07:16:36+03 | | rimoh
2019-05-01 07:16:37+03 | | zahuf
2019-05-01 07:16:37+03 | | fisak
How can I “fill the blanks” so the output will be like this?
2019-05-01 06:58:40+03 | Busy |
2019-05-01 06:58:42+03 | Busy | mituh
2019-05-01 06:58:42+03 | Busy | vasah
2019-05-01 06:58:42+03 | Ready |
2019-05-01 07:00:14+03 | Ready | jumuf
2019-05-01 07:00:14+03 | Busy |
2019-05-01 07:00:16+03 | Busy | vibaj
2019-05-01 07:00:16+03 | Busy | sasij
2019-05-01 07:16:21+03 | Busy | husih
2019-05-01 07:16:26+03 | Ready |
2019-05-01 07:16:26+03 | Ready | radod
2019-05-01 07:16:30+03 | Ready | zadub
2019-05-01 07:16:35+03 | Ready | mimoh
2019-05-01 07:16:36+03 | Busy |
2019-05-01 07:16:36+03 | Busy | rimoh
2019-05-01 07:16:37+03 | Busy | zahuf
2019-05-01 07:16:37+03 | Busy | fisak
Advertisement
Answer
This is a place where lag(ignore nulls)
is really useful. But not available in Postgres. So you can do this in two steps. Assign groups based on state
. Then spread the value:
select t.*,
max(state) over (partition by grp) as imputed_state
from (select t.*, count(state) over (order by ctime) as grp
from t
) t;
Note that t
is actually your union all
query. I’ve abstracted it to a single table alias to illustrate the important part of the logic.
Also, you have ties in your data (rows with the same time). That means that the answer is indeterminate — ties can go either before or after the state with the same time.
Here is a db<>fiddle.