Сравнение строк в одной и той же таблице в PostgreSQL на основе обновлений одного поля

#sql #postgresql

Вопрос:

Таким образом, это своего рода сложный SQL-запрос, и я хочу сделать это оптимальным способом.

Я хочу получить список клиентов в базе данных, которые снимают все деньги, которые у них есть в банке, и прекращают пользоваться банком, а также время, когда прекращается использование банка, также важно для меня. Что я имею в виду, говоря «прекратить пользоваться банком», так это то, что они не вносили деньги после того, как сняли их. Таким образом, депозит по-прежнему равен 0.

Вот что я сделал до сих пор;

Таблица клиентов является;

    customer_id deposit  ts 
    101        44        2021-09-30 10:12:19 00
    101        0         2021-09-30 09:12:19 00
    101        65        2021-09-29 09:12:19 00  
    100        0         2021-10-31 14:12:19 00  
    100        0         2021-10-30 13:12:19 00  
    100        0         2021-09-30 10:12:19 00  
    100        21        2021-09-29 10:12:19 00
    104        54        2021-09-27 10:12:19 00
 

итак, здесь мы видим, что customer_id:101 в какой-то момент снимает все свои деньги(65 — > 0), но затем возвращает деньги обратно.(0 — > > 44), поэтому он все еще использует банк.
Но до сих пор customer_id:100, похоже, перестал использовать банк. в 2021-09-30 10:12:19 00 он выводит деньги, и когда мы смотрим на последнюю строку, принадлежащую клиенту:100, она по-прежнему равна 0.

Поэтому в результате я хочу получить только идентификатор пользователя:100;

    customer_id deposit      ts                     pre_deposit
      100        0         2021-09-30 10:12:19 00  21
 

Итак, то, что я сделал до сих пор, можно увидеть здесь;
http://sqlfiddle.com/#!15/4aef7/1/0

 select distinct on (customer_id) *
from 
(
 select customer_id, deposit, ts, 
        lead(deposit) over w as pre_deposit, 
        lead(ts) over w as pre_ts 
 from t
 window w as (partition by customer_id order by ts desc)
) t 
where pre_deposit != 0 AND deposit = 0 
order by customer_id, ts desc;
 

но это также возвращает идентификатор пользователя:101, потому что в какой-то момент депозит стал 0.
но единственная строка, которую я хочу получить, — это customer_id:100.

Так как же это можно сделать?

Ответ №1:

Ваш запрос близок, но ваши where критерии все еще позволяют на более позднюю дату указывать ненулевую сумму депозита, поскольку он сравнивает данные только из 2 начальных строк. Чтобы понять, что происходит, давайте просто взглянем на часть вашего запроса для клиента 101.

  select row_number() over()        -- added for discussion
      , customer_id, deposit, ts  
      , lead(deposit) over w as pre_deposit 
      , lead(ts) over w as pre_ts 
   from t
  where customer_id = 101
 window w as (partition by customer_id order by ts desc);
 

Этот запрос выдает следующее (и это временное значение, сгенерированное вашим полным запросом)::

  ---- ------------- --------- ------------------------------- ------------- ------------------------------- 
| rn | customer_id | deposit |              ts               | pre_deposit |            pre_ts             |
 ---- ------------- --------- ------------------------------- ------------- ------------------------------- 
|  1 |         101 |      44 | 2021-09-30 05:12:19.000 -0500 |           0 | 2021-09-30 04:12:19.000 -0500 |
|  2 |         101 |       0 | 2021-09-30 04:12:19.000 -0500 |          65 | 2021-09-29 04:12:19.000 -0500 |
|  3 |         101 |      65 | 2021-09-29 04:12:19.000 -0500 |             |                               |
 ---- ------------- --------- ------------------------------- ------------- ------------------------------- 
 

Теперь посмотрите на rn2. Он содержит значения из самого себя, а предварительные значения из rn3. Rn2-это строка, которая в конечном итоге выбирается. Обратите внимание, что он четко соответствует вашим where критериям. Проблема заключается в существовании строки 1. Поскольку rn1 есть, есть причина исключить. Это исключение отсутствует. Это исключение необходимо добавить в where пункт. Нужно убедиться, что такой строки не существует. (См. демонстрацию)

 with test as 
     ( select t.* 
            , lag(deposit) over w as pre_deposit 
            , lag(ts) over w as pre_ts 
         from t
       window w as (partition by customer_id order by ts)
     ) 
select distinct on (customer_id) *
  from test t1
 where t1.deposit = 0  
   and not exists ( select null 
                      from test t2
                     where t2.customer_id = t1.customer_id
                       and t2.deposit > 0 
                       and t2.ts > t1.ts
                   ) ; 
 

ПРИМЕЧАНИЕ: Я запустил демо-версию в Postgres 9.3, такую же, как у вас. Я предполагаю, что это версия, которую вы используете. Однако эта версия попала в конец списка в ноябре 2018 года. Я бы настоятельно рекомендовал вам обновить версию, если вы действительно используете 9.3.