Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
324 views
in Technique[技术] by (71.8m points)

postgresql - Selecting the last day price by using LAG() function

I have a user transaction table which is having a userid, date, itemid, price and prev_day_price column. Sample is:

userid date itemid price prev_day_price
1 2020-12-26 archicad 1400.0
1 2020-12-26 archicad 1400.0
1 2020-12-24 archicad 1200.0
1 2020-12-23 archicad 1240.0
1 2020-12-23 archicad 1240.0
1 2020-12-21 archicad 1100.0
question from:https://stackoverflow.com/questions/65916540/selecting-the-last-day-price-by-using-lag-function

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Why doesn't your solution work properly?

lag(), as well as every other window function, work on the window you are defining. In your case you define a partitioned window, a group, so to speak. The lag() function is executed only within this group, not over the groups. So, it returns the previous values within the partition. E.g. for the 2020-12-26 it return NULL for the first record (as there is no previous record before the first one) and the value for the first record in the second one. But this happens separately within every date group. This explains your result.


Solution for Postgres 11+ :

demo:db<>fiddle

SELECT
    *,
    first_value(price) OVER (
        ORDER BY itemid, userid, mydate 
        GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW
    ) AS prev_day_price 
FROM userlog

You have to define another window, on which you can act. Instead of windowing all dates separately, it is a great idea to bundle all records from the current and the previous date group. This is exactly what the GROUPS window was made for. Withing this group you can order your records by date and take the first value. This is exactly what you expect.


Solution for Postgres versions <11 :

(Neither GROUPS windows nor PRECEDING != UNBOUNDED are supported)

step-by-step demo:db<>fiddle

SELECT
    u.itemid,
    u.mydate,
    u.userid,
    s.price                                          -- 5
FROM userlog u
JOIN (                                               -- 4
    SELECT
        itemid, mydate, userid,
        COALESCE(                                    -- 3
             lag(price) OVER (PARTITION BY itemid, userid ORDER BY mydate),  -- 2
             price
        ) as price
    FROM (
        SELECT
            itemid, mydate, userid,
            MAX(price) as price                      -- 1
        FROM userlog
        GROUP BY itemid, mydate, userid
    ) s
) s USING (itemid, mydate, userid)
  1. Reduce all groups to one single records, e.g. with group and aggregate. Another opportunity could be using SELECT DISTINCT ON (itemid, mydate, userid)
  2. Shift the previous price within each group (well, only the itemid/userid groups, the mydate column must be only used for ordering!) using the lag() function
  3. Because the first record has no previous one, the current will be taken. This is what the COALESCE() function does.
  4. Join this result to your original table and...
  5. ... return the "lagged" price from it.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...