简体   繁体   中英

PostgreSQL previous and next group value

The problem is the following:

Suppose, I have a table of such view (it is a sub-sample of the table I'm working with):

| col1 | col2 |
|------|------|
|   1  |  a2  |
|   1  |  b2  |
|   2  |  c2  |
|   2  |  d2  |
|   2  |  e2  |
|   1  |  f2  |
|   1  |  g2  |
|   3  |  h2  |
|   1  |  j2  |

I need to add two new columns

  • prev containing the previous value in col1 not equal to the current
  • next containing the next value in col1 not equal to the current

If there is no previous value, prev should contain the current col1 's value as well as next should contain the current value if no next values exist.

Result should have the following form:

| col1 | col2 | prev | next |
|------|------|------|------|
|   1  |  a2  |   1  |   2  |
|   1  |  b2  |   1  |   2  |
|   2  |  c2  |   1  |   1  |
|   2  |  d2  |   1  |   1  |
|   2  |  e2  |   1  |   1  |
|   1  |  f2  |   2  |   3  |
|   1  |  g2  |   2  |   3  |
|   3  |  h2  |   1  |   1  |
|   1  |  j2  |   3  |   1  |

I will be grateful any help.

If I assume that you have an id column that specifies the ordering, then this is possible. I'm just not sure this is easily expressed using window functions.

You can use correlated subqueries:

select t.*,
       (select t2.col1
        from t t2
        where t2.id < t.id and t2.col1 <> t.col1
        order by t2.id desc
        fetch first 1 row only
       ) as prev_col1,
       (select t2.col1
        from t t2
        where t2.id > t.id and t2.col1 <> t.col1
        order by t2.id asc
        fetch first 1 row only
       ) as prev_col2
from t;

You can add the coalece() for missing previous and next values. That is not the interesting part of the problem.

You can try this using combination of window functions lead , lag , first_value , last_value and sum .

select
    t.col1, t.col2, n,
    coalesce(first_value(y) over (partition by x order by col2), col1) prev_val,
    coalesce(last_value(y2) over (partition by x order by col2 
        rows between current row and unbounded following), col1) next_val
from (
    select
        t.*,
        case when col1 <> lag(col1) over (order by col2) then lag(col1) over (order by col2) end y,
        case when col1 <> lead(col1) over (order by col2) then lead(col1) over (order by col2) end y2,
        sum(n) over (order by col2) x
    from (
        select
            t.*,
            case when col1 <> lag(col1) over (order by col2) then 1 else 0 end n
        from t
    ) t
) t;

It finds the lead/lag per group of rows.

WITH cte AS (
SELECT row_number() over() rowid, *
  FROM unnest(array[1,1,2,2,2,1,1,3,1], array['a2','b2','c2','d2','e2','f2','g2','h2','j2']) t(col1,col2)
)
SELECT t.col1,
       t.col2,
       COALESCE(prev.col1,t.col1) prev,
       COALESCE("next".col1,t.col1) "next"
  FROM cte t
    LEFT JOIN LATERAL (SELECT prev.col1
                         FROM cte prev
                         WHERE prev.rowid < t.rowid
                           AND prev.col1 != t.col1
                         ORDER BY prev.rowid DESC
                         LIMIT 1
                      ) prev ON True
   LEFT JOIN LATERAL (SELECT "next".col1
                         FROM cte "next"
                         WHERE "next".rowid > t.rowid
                           AND "next".col1 != t.col1
                         ORDER BY "next".rowid ASC
                         LIMIT 1
                      ) "next" ON True

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM