从数据库端的组中获取最后 2 行

Get last 2 rows from group on database side

我正在尝试提高 Postgre 的 linq 查询性能SQL。有两个表(Parcles、ParcelStates)具有关系 1:n。我需要为每个包裹获取最后 2 个 ParcelStates。看起来很简单,我有以下代码:

IQueryable<Parcel> parcels = _dbContext.Parcels
   .OrderByDescending(x => x.Id)
   .Take(100);

然后获取状态:

var states = await parcels
    .GroupJoin(_dbContext.ParcelStates, ps => ps.Id, p => p.ParcelId, (ps, p) => new { ps, p })
    .SelectMany(x => x.p.DefaultIfEmpty().OrderByDescending(y => y.Id).Take(2), (x,c) => c)
    .ToListAsync();

它returns我180状态,还可以。但是存在性能问题,因为它生成不执行 SQL 查询:

SELECT *
FROM (
    SELECT *
    FROM parcels AS x
    WHERE x.isdeleted = FALSE
    ORDER BY c DESC, c0 DESC
    LIMIT @__p_1 OFFSET @__p_0
) AS t
LEFT JOIN parcelstates AS p ON t.id = p.parcelid
ORDER BY t.c DESC, t.c0 DESC, t.id

它从数据库中获取所有状态,而我只需要 2 个。

如何更改 LINQ 以在数据库端过滤结果?

我在日志中发现: LINQ 表达式 'Take(2)' 无法翻译,将被计算

我们可以使用一个 foreach 循环,它将转化为几个非常快速的 SQL 查找(应该在 < 1 秒内执行)。不理想,但我仍然建议编写存储过程来获取此数据,而不是依赖 LINQ to SQL,它并不总是生成最佳查询:

// Store a list of parcel states
var parcelStates = new List<ParcelState>();

// Read top 100 parcels from the database
var parcels = dbContext.Parcels
                       .OrderBy(p => p.Id)
                       .Take(100);

// For each parcel, use SQL to lookup the 2 most recent parcel states
foreach (var p in parcels)
{
    var ps = dbContext.ParcelStates
                                .Where(ps => ps.ParcelId == p.Id)
                                .OrderByDescending(ps => ps.Id)
                                .Take(2);
    parcelStates.AddRange(ps);
}

// Now we have all parcel states for those parcels
Console.WriteLine($"Found {parcelStates.Count} parcel states for {parcels.Count} parcels");

如果将 SelectMany 表达式插入 GroupJoin,它会转换为 SQL 吗?

var states = await parcels
    .GroupJoin(_dbContext.ParcelStates, ps => ps.Id, p => p.ParcelId,
               (ps, p) => p.DefaultIfEmpty().OrderByDescending(y => y.Id).Take(2))
    .ToListAsync();