我有一个测试,随着时间的推移会有很多记录。我正在使用 Postgresql 来存储这些测试和记录,并且我希望能够搜索具有特定时间范围内的记录的测试。
一个测试有很多记录,每条记录都有一个
collectionTime
值,该值存储为 DateTime
。当我搜索所有测试时,我使用 EF Core 和 Linq 使用如下查询:
var tests = Table.Include(t => t.RecordData)
.AsSplitQuery().AsNoTracking()
.Select(r => new Test
{
Id = r.Id,
// ...additional test info here...
FirstReported = r.RecordData.OrderBy(rd => rd.CollectionDate).FirstOrDefault().CollectionDate,
LastReported = r.RecordData.OrderByDescending(rd => rd.CollectionDate).FirstOrDefault().CollectionDate,
NumberOfRecords = r.RecordData.Count,
});
此时,我开始执行不同的过滤和排序。这会根据用户应用的过滤器修改结果查询。如果用户只是获取所有记录,然后对它们进行分页并返回而不进行过滤,那么查询就没有问题。但是,当我添加逻辑来检查是否提供了特定时间段时,我的查询始终超时。
if (providedStartDate is not null)
{
var startDateUTC = providedStartDate.ToUniversalTime().Date;
tests = tests.Where(x => x.NumberOfRecords > 0 && x.LastReported >= startDateUTC);
}
我有一个几乎相同的条件来检查
FirstReported
收集时间是否在providedEndDate
之后,因为像这样过滤掉两侧在逻辑上会产生我想要的测试。
对结果进行分页后,我将它们发送到列表中:
return await tests.ToListAsync(token);
我同样想计算用于分页目的的记录数,并且在过滤日期范围内的测试时
CountAsync
也会停止。
对于查询和逻辑为何此查询执行如此差/超时有任何建议或直接关注吗?我认为这是相当常见的事情,但我似乎找不到任何解决此类优化搜索的具体帖子。谢谢。
排除附加信息后,为
StartDate
生成的原始 SQL 如下。请注意,我有一个查询过滤器,仅获取活动记录和测试,但忽略查询过滤器也不会以任何显着的方式影响性能:
SELECT
t."Id",
(SELECT t2."CollectionDate"
FROM public."RecordData" AS t2
WHERE t2."IsActive" = TRUE AND t."Id" = t2."TestId"
ORDER BY t2."CollectionDate"
LIMIT 1) AS "FirstReported",
(SELECT t3."CollectionDate"
FROM public."RecordData" AS t3
WHERE t3."IsActive" = TRUE AND t."Id" = t3."TestId"
ORDER BY t3."CollectionDate" DESC
LIMIT 1) AS "LastReported",
(SELECT count(*)::int
FROM public."RecordData" AS t4
WHERE t4."IsActive" = TRUE AND t."Id" = t4."TestId") AS "NumberOfRecords"
FROM
public."Tests" AS t
WHERE
t."IsActive" = TRUE
AND (SELECT count(*)::int
FROM public."RecordData" AS t0
WHERE t0."IsActive" = TRUE AND t."Id" = t0."TestId") > 0
AND (SELECT t1."CollectionDate"
FROM public."RecordData" AS t1
WHERE t1."IsActive" = TRUE AND t."Id" = t1."TestId"
ORDER BY t1."CollectionDate" DESC
LIMIT 1) >= @__startDateUTC_0
LIMIT @__p_2 OFFSET @__p_1
看起来 EF Core LINQ 翻译不好,请尝试以下方法
var tests =
from t in Table
from first in r.RecordData
.OrderBy(rd => rd.CollectionDate)
.Take(1).DefautIfEmpty()
from last in r.RecordData
.OrderByDescending(rd => rd.CollectionDate)
.Take(1).DefautIfEmpty()
select new Test
{
Id = r.Id,
...additional test info here...
FirstReported = first.CollectionDate,
LastReported = last.CollectionDate,
NumberOfRecords = r.RecordData.Count(),
};