«Интерполировать предыдущее значение» той же таблицы в SQL - PullRequest
0 голосов
/ 21 марта 2020

Я столкнулся с проблемой, когда мне нужно сделать что-то, что работает как интерполяция предыдущего значения в соединении, но не из таблицы, к которой я присоединяюсь, а из исходной таблицы. Позвольте мне объяснить:

Table A:

Item Package Start_date Finish_date
 X   12345   2020-01-01 2020-02-01
 X   6789    2020-02-02 2020-03-02


Table B

Item   Date     
 X   2020-01-15 
 X   2020-02-15
 X   2020-03-15

Я хочу знать, в каком пакете из таблицы A был мой товар на дату из таблицы B. Итак, я делаю:

select Item, Date, Package 
from B
left join A on (B.Item = A.Item and B.Date between StartDate and FinishDate)

и получаю:

Item  Date      Package
X   2020-01-15  12345
X   2020-02-15  6789
X   2020-03-15  NULL

но вместо нуля я бы хотел видеть последнее непустое значение Package, событие, если дата выходит за пределы диапазона дат (здесь это будет 6789).

Кто-нибудь знает, как это сделать?

Ответы [ 2 ]

1 голос
/ 22 марта 2020

Несмотря на то, что вы можете удобно использовать левое соединение с INTERPOLATE PREVIOUS VALUE, здесь могут быть созвездия данных, в которых вам нужен точный комплексный предикат объединения с объединенным экви- и предикатом BETWEEN, который вы используете.

Если вам это нужно (я не могу придумать случаи, когда вы бы не справились, поэтому помогите мне), это была бы простая OLAP, Window, функция, доступная в Vertica: LAST_VALUE(<_expression_> IGNORE NULLS), которая возвращает последнее ненулевое значение в окне OLAP.

Но я сомневаюсь, что вам это нужно, поэтому я добавляю оба решения здесь ниже.

Повторение вашего ввода в начальном предложении WITH, включающем два выражения Common Table, при вашем объединении это должно выглядеть например:

WITH
a(item,package,start_date,finish_date) as (
          SELECT 'X',12345,DATE '2020-01-01',DATE '2020-02-01'
UNION ALL SELECT 'X',6789,DATE '2020-02-02',DATE '2020-03-02'
)
,
b(item,date) AS (
          SELECT 'X',DATE '2020-01-15'
UNION ALL SELECT 'X',DATE '2020-02-15'
UNION ALL SELECT 'X',DATE '2020-03-15'
)
SELECT
  b.item
, b.date
, LAST_VALUE(a.package IGNORE NULLS) OVER(w) AS package
FROM b
LEFT JOIN a 
 ON a.item=b.item
AND b.date BETWEEN start_date AND finish_date
WINDOW w AS(PARTITION BY b.item ORDER BY b.date)
ORDER BY 2;
-- out  item |    date    | package 
-- out ------+------------+---------
-- out  X    | 2020-01-15 |   12345
-- out  X    | 2020-02-15 |    6789
-- out  X    | 2020-03-15 |    6789

Предикат левого соединения INTERPOLATE PREVIOUS VALUE работает, однако, и в этом созвездии данных, как вы можете видеть здесь ниже.

WITH
a(item,package,start_date,finish_date) as (
          SELECT 'X',12345,DATE '2020-01-01',DATE '2020-02-01'
UNION ALL SELECT 'X',6789,DATE '2020-02-02',DATE '2020-03-02'
)
,
b(item,date) AS (
          SELECT 'X',DATE '2020-01-15'
UNION ALL SELECT 'X',DATE '2020-02-15'
UNION ALL SELECT 'X',DATE '2020-03-15'
)
SELECT
  b.item
, b.date
, a.package
FROM b
LEFT JOIN a 
 ON a.item=b.item
AND b.date INTERPOLATE PREVIOUS VALUE start_date
ORDER BY 2;
-- out  item |    date    | package 
-- out ------+------------+---------
-- out  X    | 2020-01-15 |   12345
-- out  X    | 2020-02-15 |    6789
-- out  X    | 2020-03-15 |    6789

Стоит попробовать быстрее: предикат объединения диапазона или предикат ИНТЕРПОЛАТНОГО ПРЕДЫДУЩЕГО ЗНАЧЕНИЯ.

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

Можете ли вы попробовать оба и сказать нам, какой из них был быстрее?

0 голосов
/ 21 марта 2020

Используйте два join с. Один для совпадения и один для значения по умолчанию:

select b.Item, b.Date, 
       coalesce(a.Package, adef.Package) as Package 
from B left join
     A
     on B.Item = A.Item and
        B.Date between A.StartDate and A.FinishDate left join
     (select a.*,
             row_number() over (partition by item order by StartDate desc) as seqnum
      from a
     ) adef
     on adef.item = B.item and
        adef.seqnum = 1 and
        a.item is null;

РЕДАКТИРОВАТЬ:

Вы могли бы фактически объединить это в один join:

select b.Item, b.Date, a.Package
from B left join
     (select a.*,
             row_number() over (partition by item order by StartDate desc) as seqnum
      from a
     ) a
     on adef.item = B.item and
        B.Date >= A.StartDate and
        (B.Date <= A.FinishDate or
         (B.Date > A.FinishDate and seqnum = 1)
        );
...