Неожиданное поведение оконной функции first_value - PullRequest
0 голосов
/ 16 октября 2019

У меня есть 2 столбца - номер заказа, значение. Конструктор значений таблицы:

(1, null)
,(2, 5)
,(3, null)
,(4, null)
,(5, 2)
,(6, 1)

Мне нужно получить

(1, 5) -- i.e. first nonnull Value if I go from current row and order by OrderNo
,(2, 5)
,(3, 2) -- i.e. first nonnull Value if I go from current row and order by OrderNo
,(4, 2) -- analogous
,(5, 2)
,(6, 1)

Это запрос, который, я думаю, должен работать.

;with SourceTable as (
    select *
        from (values
            (1, null)
            ,(2, 5)
            ,(3, null)
            ,(4, null)
            ,(5, 2)
            ,(6, 1)
        ) as T(OrderNo, Value)
)
select
       *
       ,first_value(Value) over (
           order by
               case when Value is not null then 0 else 1 end
               , OrderNo
           rows between current row and unbounded following
       ) as X
   from SourceTable
order by OrderNo

Проблема в том, что он возвращаетточно такой же набор результатов как SourceTable. Я не понимаю почему. Например, если первая строка обработана (OrderNo = 1), я ожидаю, что столбец X вернет 5, потому что frame должен включать все строки (текущая строка и несвязанные последующие), и он упорядочивается по Value - сначала не по нулям, а затем по OrderNo. Таким образом, первая строка в кадре должна быть OrderNo = 2. Очевидно, что это не так, но я не понимаю, почему.

Очень признателен, если кто-то объяснит, как устроен первый кадр. Мне нужно это для SQL Server, а также Postgresql.

Большое спасибо

Ответы [ 2 ]

2 голосов
/ 16 октября 2019

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

with SourceTable as (
      select *
      from (values (1, null),
                   (2, 5),
                   (3, null),
                   (4, null),
                   (5, 2),
                   (6, 1)
           ) T(OrderNo, Value)
)
select st.*,
       (array_remove(array_agg(value) over (order by orderno rows between current row and unbounded following), null))[1] as x
from SourceTable st
order by OrderNo;

Здесь - это скрипта db <>.

Или с использованием бокового соединения:

select st.*, st2.value
from SourceTable st left join lateral
     (select st2.*
      from SourceTable st2
      where st2.value is not null and st2.orderno >= st.orderno
      order by st2.orderno asc
      limit 1
     ) st2
     on 1=1
order by OrderNo;

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

1 голос
/ 16 октября 2019

Довольно легко понять, почему first_value не работает, если вы упорядочите результаты по case when Value is not null then 0 else 1 end, orderno

 orderno | value | x
---------+-------+---
       2 |     5 | 5
       5 |     2 | 2
       6 |     1 | 1
       1 |       |
       3 |       |
       4 |       |
(6 rows)

Для orderno = 1 в кадре нет ничего, что было бы ненулевым.

Вместо этого мы можем упорядочить заказы по группам, используя count в качестве оконной функции в подзапросе. Затем мы используем max как оконную функцию над этой группой (это произвольно, min будет работать так же хорошо), чтобы получить одно ненулевое значение в этой группе:

with SourceTable as (
    select *
        from (values
            (1, null)
            ,(2, 5)
            ,(3, null)
            ,(4, null)
            ,(5, 2)
            ,(6, 1)
        ) as T(OrderNo, Value)
)
select orderno, order_group, max(value) OVER (PARTITION BY order_group) FROM (

    SELECT *,
       count(value) OVER (ORDER BY orderno DESC) as order_group
   from SourceTable
   ) as sub
order by orderno;
 orderno | order_group | max
---------+-------------+-----
       1 |           3 |   5
       2 |           3 |   5
       3 |           2 |   2
       4 |           2 |   2
       5 |           2 |   2
       6 |           1 |   1
(6 rows)
...