SQL Самостоятельное присоединение для сравнения данных по дням - PullRequest
0 голосов
/ 06 августа 2020

Я хочу сравнить товар по дням. Цель состоит в том, чтобы получить разницу между днем ​​1 и днем ​​2, днем ​​2 и днем ​​3 и т.д. и т. Д. 2, а затем день 10)

Товар представлен несколькими атрибутами, но для отображения проблемы я использовал 1 поле

Ожидаемый результат

Product  Action   EventTime
X1       Added    T1
X2       Added    T1
X2       Removed  T2
X3       Added    T2
X1       Removed  T10
X3       Removed  T10
X4       Added    T10

Моя идея - присвоить этой записи номер строки и выполнить полное внешнее соединение, чтобы найти разницу, но я не могу получить правильный результат.

Мой мыслительный процесс - Давайте ранжируем по времени события.

Product  EventTime  RNK
X1       T1         1
X2       T1         1
X1       T2         2
X3       T2         2
X4       T10        3

если мы сделаем

select 
  * 
from 
    dataset d1 
full join 
    dataset d2
        on d1.product = d2.product
        and d1.RNK = d2.RNK - 1
where
    d1.product is null or d2.product is null

Это не даст мне правильного результата. но если я сначала очищу данные, чтобы получилось

Product  EventTime  RNK
--------------------- X1       T1         1 (cross out)
----------------------X2       T1         1
X1       T2         2
X3       T2         2
X4       T10        3 

Product  EventTime  RNK
X1       T1         1
X2       T1         1
X1       T2         2
X3       T2         2
-------------------- X4       T10        3  (cross out)

И мы сделаем полное соединение с указанным выше набором данных. Я получу правильный результат, но производительность низкая. в основном я удалил первый ранг и последний ранг.

Есть идеи, как получить разницу между 2 сетами по дневной последовательности?

Ответы [ 2 ]

3 голосов
/ 06 августа 2020

Хмммм. . . это похоже на проблему промежутков и островов. Вы можете получить периоды времени для каждого продукта, используя:

select product, min(time), max(time)
from (select t.*,
             row_number() over (order by time) as seqnum,
             row_number() over (partition by product order by time) as seqnum_p
      from t
     ) t
group by product, (seqnum_p - seqnum);

Получить время удаления немного сложнее. . . вам нужно использовать lead() и некоторую причудливую агрегацию:

select product, min(time), max(time),
       max(next_time) keep (dense_rank first over order by time desc) as next_time
from (select t.*,
             row_number() over (order by time) as seqnum,
             row_number() over (partition by product order by time) as seqnum_p,
             min(time) over (order by time range between '1' second following and unbounded following) as next_time
      from t
     ) t
group by product, (seqnum_p - seqnum);

Этого может быть достаточно для того, что вы хотите. Но вы можете отменить поворот:

with cte as (
      select product, min(time) as min_time, 
             max(next_time) keep (dense_rank first over order by time desc) as next_time
      from (select t.*,
                   row_number() over (order by time) as seqnum,
                   row_number() over (partition by product order by time) as seqnum_p,
                   min(time) over (order by time range between '1' second following and unbounded following) as next_time
            from t
           ) t
      group by product, (seqnum_p - seqnum)
     )
select product, 'Added', min_time
from cte
union all
select product 'Removed', next_time
from cte;
0 голосов
/ 06 августа 2020

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

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

with event_table (product, event_time) as 
( SELECT 'X1',  trunc(sysdate)+1 FROM DUAL UNION ALL
  SELECT 'X2',  trunc(sysdate)+1 FROM DUAL UNION ALL 
  SELECT 'X1',  trunc(sysdate)+2 FROM DUAL UNION ALL  
  SELECT 'X3',  trunc(sysdate)+2 FROM DUAL UNION ALL  
  SELECT 'X4',  trunc(sysdate)+10 FROM DUAL ),
  -- solution begins here
  -- start by getting a distinct list of event times
  distinct_times as ( SELECT DISTINCT event_time FROM event_table ),
  -- Next, do a partitioned right join to ensure that every product is represented at every event time.  If the row is sparse data that was added by the right join, et.event_time will be null.
  -- We use the lag() function to see what the product looked like at the last event and
  -- compare with the current event.
  -- NULL -> NULL ==> no change
  -- NOT NULL -> NOT NULL ==> no change
  -- NULL -> NOT NULL ==> added
  -- NOT NULL -> NULL ==> removed
  sparse_data_filled as (
select dt.event_time, et.product,
case when lag(et.event_time ) over ( partition by et.product order by dt.event_time ) is null then
          -- product wasn't present during last event
          case when et.event_time is null then
            -- product still is not present
            null  -- no change
          else
            -- product is present now and was not before
            'Added'
          end
    else
      -- product was present during last event
      case when et.event_time is null then
        -- product is no longer present
          'Removed'
       else
         -- product is still present
         null   -- no change
      end
    end message
from event_table et partition by (product) 
right join distinct_times dt on et.event_time = dt.event_time )
SELECT * from sparse_data_filled
-- filter out the non-changes
where message is not null
order by event_time, product
;
+------------+---------+---------+
| EVENT_TIME | PRODUCT | MESSAGE |
+------------+---------+---------+
| 07-AUG-20  | X1      | Added   |
| 07-AUG-20  | X2      | Added   |
| 08-AUG-20  | X2      | Removed |
| 08-AUG-20  | X3      | Added   |
| 16-AUG-20  | X1      | Removed |
| 16-AUG-20  | X3      | Removed |
| 16-AUG-20  | X4      | Added   |
+------------+---------+---------+

Более компактная версия, предназначенная только для решения (без тестовых данных):

WITH 
  distinct_times as ( SELECT DISTINCT event_time FROM event_table ),
  changes as (
select dt.event_time, et.product,
case nvl2(et.event_time,1,0) - nvl2(lag(et.event_time ) over ( partition by et.product order by dt.event_time ),1,0)
       when +1 then 'Added'
       when -1 then 'Removed'
    end message
from event_table et partition by (product) 
right join distinct_times dt on et.event_time = dt.event_time )
SELECT * from changes
where message is not null
order by event_time, product
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...