I have Website(Id)
table, each record may have multiple CheckLog(FK WebsiteId)
entries associated. CheckLog
also has a compound index of [WebsiteId, CreatedTime]
. Website
has only around 20 records but overtime CheckLog
would grow, 3 millions entries at the time I have this problem. (See schema using EF Core at the end of the question).
A frequent query I have is to query the list of all Website
s, along with zero/one latest CheckLog
record:
return await this.ctx.Websites.AsNoTracking()
.Select(q => new WebsiteListItem()
{
Website = q,
LatestCheckLog = q.CheckLogs
.OrderByDescending(q => q.CreatedTime)
.FirstOrDefault(),
})
.ToListAsync();
I believe the [WebsiteId, CreatedTime]
index should help. However, the query takes around 11s to execute. Here's the translated query, along with EXPLAIN QUERY PLAN
:
SELECT "w"."Id", "t0"."Id", "t0"."CreatedTime", "t0"."WebsiteId"
FROM "Websites" AS "w"
LEFT JOIN (
SELECT "t"."Id", "t"."CreatedTime", "t"."WebsiteId"
FROM (
SELECT "c"."Id", "c"."CreatedTime", "c"."WebsiteId", ROW_NUMBER() OVER(PARTITION BY "c"."WebsiteId" ORDER BY "c"."CreatedTime" DESC) AS "row"
FROM "CheckLogs" AS "c"
) AS "t"
WHERE "t"."row" <= 1
) AS "t0" ON "w"."Id" = "t0"."WebsiteId"
MATERIALIZE 1
CO-ROUTINE 4
SCAN TABLE CheckLogs AS c USING INDEX IX_CheckLogs_WebsiteId_CreatedTime
USE TEMP B-TREE FOR RIGHT PART OF ORDER BY
SCAN SUBQUERY 4
SCAN TABLE Websites AS w
SEARCH SUBQUERY 1 AS t USING AUTOMATIC COVERING INDEX (WebsiteId=?)
Is this fixable with Index? If not, is there an efficient way to query it without creating N+1 queries? I tried to think of a way to do that with 2 queries but can't think of any better way to translate it the way EF Core does).
Also I believe this is a very common problem but I don't know what keyword I should use to find out solution for this kind of problem. I am okay with a general solution for this kind of problem (i.e. get the latest Product
of a list of Categories
). Thank you.
I use EF Core for DB Schema:
public class Website
{
public int Id { get; set; }
// Other properties
public ICollection<CheckLog> CheckLogs { get; set; }
}
[Index(nameof(CreatedTime))]
[Index(nameof(WebsiteId), nameof(CreatedTime))]
public class CheckLog
{
public int Id { get; set; }
public DateTime CreatedTime { get; set; }
public int WebsiteId { get; set; }
public Website Website { get; set; }
// Other properties
}