SQL for pairing rows by value of field

SQL for pairing rows by value of field



I have a table:


create table transaction_log (
id serial,
operation_type character varying(36),
date timestamp with time zone,
sum double precision,
user_id integer,
PRIMARY KEY(id)
);



It keeps two type of operations: block and unblock for multiple users (contragent_id) and lots (lot_id) and datetime field of operation.


block


unblock


operation



For example:


id, sum, operation_type, date, user_id
1, 5900, blocked, 2018-01-05 11:00, 1
2, 3500, blocked, 2018-01-08 12:00, 2
3, 5900, unblock, 2018-02-11 09:00, 1
4, 1000, blocked, 2018-01-09 05:00, 3
5, 3500, unblock, 2018-01-24 19:00, 2



So I need to get SQL to fetch all block operations with date of corresponding unblock operation, if it exists. E.g.: block_ID, sum, blocked_date, unblock_date. So, from example data I need to get:
For example:


block_ID, sum, blocked_date, unblock_date
1, 5900, 2018-01-05 11:00, 2018-02-11 09:00
2, 3500, 2018-01-08 12:00, 2018-01-24 19:00
4, 1000, blocked, 2018-01-09 05:00, null



I suppose that I need a WITH statement for this, but I can't get it how to match records correctly.



Any help appreciated.



BTW Postgres 9.4






sample data and expected output more fruitful

– Zaynul Abadin Tuhin
Sep 6 '18 at 16:24






Sample data with SQL Fiddle sqlfiddle.com/#!15/68b3c/9

– Zhlobopotam
Sep 6 '18 at 17:48






Sorry, I just found out that we are running 9.4

– Zhlobopotam
Sep 7 '18 at 7:26






So for a user there cannot be two blocked records following each other; there must be an unblock record inbetween. Same vice versa: there cannot be two unblock records following each other; there must be a blocked record inbetween. You say that two events can happen at exactly the same timestamp. Would this always mean "blocked and instantly unblocked" or could it also mean "unblocked and instantly blocked again"?

– Thorsten Kettner
Sep 7 '18 at 7:45






Yes. Updated descripion. two events can happen at exactly the same timestamp means "blocked and instantly unblocked"

– Zhlobopotam
Sep 7 '18 at 8:06





2 Answers
2



You can try below way


with block as
(
select * from transactions
where operation='blocked'
),
unblock as
(
select * from transactions
where operation='unblock'
)
select block.id as block_ID, block.sum,
block.date, unblock.date from block
left join unblock on block.user_id=unblock.user_id






This would not work. Id is unique for each record in table, not for block-unblock pair. I added examples to question.

– Zhlobopotam
Sep 6 '18 at 17:46






@Zhlobopotam sqlfiddle.com/#!15/a787f8/3

– Zaynul Abadin Tuhin
Sep 6 '18 at 18:10






@Zhlobopotam does it helps?

– Zaynul Abadin Tuhin
Sep 6 '18 at 20:36






Example works, but with more real date - nope sqlfiddle.com/#!17/99da1/7 (here block operation with same lot_id and contragent_id must be matched with next unblock record with same lot_id and contragent_id

– Zhlobopotam
Sep 7 '18 at 7:18







@Zhlobopotam could you please give me expected result according to your real data your

– Zaynul Abadin Tuhin
Sep 7 '18 at 7:47



If your data is consistent, you are merely looking for the minimum unblock date following a block date. You can get this in a subquery in your SELECT clause:


SELECT


select user_id, sum, date as block,
(
select min(ub.date)
from blocktable ub
where ub.operation_type = 'unblock'
and ub.user_id = b.user_id
and ub.date >= b.date
) as unblock
from blocktable b
where operation_type = 'blocked';



Or in the FROM clause with a lateral join:


FROM


select b.user_id, b.sum, b.date as block, ub.unblock
from blocktable b
left join lateral
(
select min(ub.date) as unblock
from blocktable ub
where ub.operation_type = 'unblock'
and ub.user_id = b.user_id
and ub.date >= b.date
) b
where operation_type = 'blocked';



With a lateral join can even get the whole row:


select *
from blocktable b
left join lateral
(
select *
from blocktable ub
where ub.operation_type = 'unblock'
and ub.user_id = b.user_id
and ub.date >= b.date
order by ub.date
fetch first 1 row only
) as unblock
where operation_type = 'blocked';



Another option to get the single date would be LEAD:


LEAD


select user_id, sum, block, unblock
from
(
select
user_id,
sum,
date as block,
lead(date) over (partition by user_id order by date, operation_type) as unblock,
operation_type
from mytable
) block_and_unblock
where operation_type = 'blocked';



If you want a query that works great even in case of inconsistencies (especially two blocked or two unblocked records following each other), you may have to select the data sequentially, i.e. in a recursive query or with an app.



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

ャフサォクコ ケウ,コ,ワ メ,ロスョノ゙,クネ,フムカヤヲニ,エコ゚ツ ウイオン゙ケワサネォキモュキォウイノンコチ゚メヌナイゥフュ,カヒウネェ ネ,ホノケ,ムュキ ッボーミュハ,チ ツス ィ メウイマヤ,゙ウチ ヅ ロ,ォジヌェ ャヌット ェ,マャ,チナエヒネソキツテ トホヲヲミーァ

How do I collapse sections of code in Visual Studio Code for Windows?