Efficiently Finding the Latest Row per Group |
글쓴이 : 모델광
날짜 : 2026-11-01 (일) 00:34
조회 : 2
|
|
The Following article triggered me to write this note: Efficiently Finding the Latest Row per Group – Sweet Data Story In that article, the author asked an AI assistant (Astra) to find the latest order for each shop. Several approaches were considered, but one proposal caught my eye. The idea was to retrieve the latest order for the shop with the highest shop_id, then jump directly to the latest order for the next shop in the index. At first glance, you may wonder what I am talking about. The following SQL should make the idea clearer: After retrieving the latest order for one shop, the next iteration searches for an order beloing to a smaller shop_id(WHERE x.shop_id < p.shop_id). PostgreSQL can reposition the index scan rather than reading all the intervening orders by using the clause ORDER BY ~ Limit 1. This is a way of implementing a Loose Index Scan using a recursive CTE. After reading Astra's proposal, I decided to apply the same idea to a SQL statement I had investigated in 2023. Here is the link to my previous note: I won't repeat the test-data generation code here because it is available in the previous note. 1. The original query: LATERAL JOIN In the previous note, I presented the following query to retrieve the latest status record for each piece of equipment: select t20.equ_num, t20.equ_nm, t20.equ_sts_nm The primary key of interface_test is defined on: The index is particularly useful for the query. PostgreSQL can perform a backward index scan for each equipment number and stop as soon as it finds the first matching entry. Here is the execution plan:
There are two important points to notice: 1. PostgreSQL uses a NL join, scanning the equipment table once. 2. For each equipment number, it performs a backward scan of the primary key index and stops after retrieving one tuple. The inner index scan runs 200 times, once for each equipment record. Each execution retrieves just one row. For this particular data set, the LATERAL strategy is already very efficient. 2. Applying Astra's recurive strategy Now let's apply the same idea to the interface_test table. Instead of driving the query from the equiqment master table, we start with the highest equipment number in the interface table. We then repeatedly retrieve the lates record for the next lower equiment number. Here is the rewritten query: with recursive w as ( -- the anchor member retrieves the latest interface record for the highest equimment number -- the recursive member The predicate
There are three points worth examining in the plan: First, PostgreSQL uses a recursive union to retrieve the lastest interface record for each equipment number. Second, the recursive CTE avoids scanning every interface record. There are 2 million rows in the interface_test table: - 200 equipment nubmers - 100 days of history per equipment - 100 interface records per day The recursive approach performs one initial lookup and then one lookup per equipment number. The execution plan reports 200 index searches in the recursive member. Third, PostgreSQL uses a hash join to retrieve the equipment information. 3. Comparing the two approaches The two strategis are remarkably close in performance. The LATERAL approach is slightly faster and accesses three fewer shared buffers (803 vs. 806). In this test, there is no performance reason to replace the original query with the recursive CTE. However, the recursive CTE approach is worth keeping in mind. Conclusion When retrieving the lates record for each group, the choice of access strategy can make a difference. A LATERAL join is an excellent option when a relatively small driving table is available. It allows PostgreSQL to perform a targeted index lookup for each group. A recursive CTE offers another option. By repeatedly moving to the next group in a composite index, it can implement a Loose Index Scan and avoid processing every historical record. Neither approach is universally superior. The right choice depends on the number of groups, the distribution of data, the availability of the driving table, and the indexes available. The important point is not simply to retrieve the lates row for each group, but to make PostgreSQL reach that row with as little unnecessary work as possible. |









