SQL для объединения строк по значению поля - PullRequest
0 голосов
/ 06 сентября 2018

У меня есть таблица:

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)
);

Он поддерживает два типа операций: block и unblock для нескольких пользователей (contragent_id) и лотов (lot_id) и поля даты и времени operation.

  • Один пользователь может иметь несколько операций блокировки и разблокирования
  • Записи с блочной операцией могут иметь одну разблокированную операцию после.
  • Блоки и разблокировки являются последовательными. Для пользователя должен быть блок, а следующий блок может быть только после того, как произошла операция разблокирования.
  • Дата и время разблокирования могут совпадать с блокировкой. Это означает, что заблокирован и мгновенно разблокирован.
  • Один пользователь может иметь несколько последовательностей операций блокировки и разблокировки на смешанной временной шкале.
  • идентификатор уникален

Например:

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 

Поэтому мне нужно получить SQL для извлечения всех операций блока с датой соответствующей операции разблокирования, если она существует. Например: block_ID, сумма, block_date, unblock_date. Итак, из данных примера мне нужно получить: Например:

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

Полагаю, для этого мне нужен оператор WITH, но я не могу понять, как правильно сопоставлять записи.

Любая помощь приветствуется.

Кстати, Postgres 9,4

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

Если ваши данные непротиворечивы, вы просто ищете минимальную дату разблокирования после даты блокировки. Вы можете получить это в подзапросе в вашем 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';

Или в предложении 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';

При боковом соединении можно даже получить целый ряд:

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';

Другой вариант получения единственной даты: 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';

Если вам нужен запрос, который прекрасно работает даже в случае несоответствий (особенно двух заблокированных или двух незаблокированных записей, следующих друг за другом), вам, возможно, придется выбирать данные последовательно, то есть в рекурсивном запросе или с помощью приложения.

0 голосов
/ 06 сентября 2018

Вы можете попробовать ниже способ

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
...