LINQ for “Outer Apply” / “Left Outer Join” 有点像 PostgreSQL 的查询

LINQ for "Outer Apply" / "Left Outer Join" kinda Query for PostgreSQL

提问人:James 提问时间:11/13/2023 最后编辑:James 更新时间:11/14/2023 访问量:94

问:

我的 linq 查询有问题,它执行得非常慢。当我调试并查看翻译后的查询时,我看到了问题所在。我的 LinQ 并没有真正实现我想要的查询。

这是我现在拥有的 LinQ:

from doc in context.document
from lasthistory in context.documenthistory
    .Where(x => x.documentid == doc.id)
    .OrderByDescending(x => x.actiondatetime)
    .Take(1)
    .DefaultIfEmpty()
where lasthistory.actiondatetime >= periodFrom
    && lasthistory.actiondatetime < periodTo.AddDays(1)
select new
{
    id = doc.id,
    lastactionby = lasthistory.actionby,
    lastactiondatetime = lasthistory.actiondatetime
}

下面是这个 LINQ 的翻译查询

SELECT d.id, t0.actionby AS lastactionby, t0.actiondatetime AS lastactiondatetime
FROM dbo.document AS d
LEFT JOIN (
    SELECT t.actionby, t.actiondatetime, t.documentid
    FROM (
        SELECT d0.actionby, d0.actiondatetime, d0.documentid, ROW_NUMBER() OVER(PARTITION BY d0.documentid ORDER BY d0.actiondatetime DESC) AS row
        FROM dbo.documenthistory AS d0
    ) AS t
    WHERE t.row <= 1
) AS t0 ON d.id = t0.documentid
WHERE (t0.actiondatetime >= @__periodFrom_1) AND (t0.actiondatetime < @__AddDays_2)

它首先尝试选择documenthistory中的所有数据,这就是我的查询执行速度非常慢的原因。

我现在已经将 LinQ 更新为如下所示:

from doc in context.document
let lasthistory = context.documenthistory
    .Where(x => x.documentid == doc.id)
    .OrderByDescending(x => x.actiondatetime)
    .FirstOrDefault()
where lasthistory.actiondatetime >= periodFrom
    && lasthistory.actiondatetime < periodTo.AddDays(1)
select new
{
    id = doc.id,
    lastactionby = lasthistory.actionby,
    lastactiondatetime = lasthistory.actiondatetime
}

它的性能比第一个更好,但是翻译后的查询有点“丑陋”,子查询太多了

SELECT d.id, 
    (
        SELECT d2.actionby 
        FROM dbo.documenthistory AS d2 
        WHERE d2.documentid = d.id 
        ORDER BY d2.actiondatetime DESC 
        LIMIT 1
    ) AS lastactionby, 
    (
        SELECT d3.actiondatetime 
        FROM dbo.documenthistory AS d3 
        WHERE d3.documentid = d.id 
        ORDER BY d3.actiondatetime DESC 
        LIMIT 1
    ) AS lastactiondatetime
FROM dbo.document AS d
WHERE ((SELECT d0.actiondatetime FROM dbo.documenthistory AS d0 WHERE d0.documentid = d.id ORDER BY d0.actiondatetime DESC LIMIT 1) >= @__periodFrom_1)
    AND ((SELECT d1.actiondatetime FROM dbo.documenthistory AS d1 WHERE d1.documentid = d.id ORDER BY d1.actiondatetime DESC LIMIT 1) < @__AddDays_2)

我想要的查询是这样的:

SELECT d.id, t.actionby AS lastactionby, t.actiondatetime AS lastactiondatetime
FROM dbo.document AS d
LEFT JOIN LATERAL (
    SELECT d0.actionby, d0.actiondatetime
    FROM dbo.documenthistory AS d0
    WHERE d0.documentid = d.id
    ORDER BY d0.actiondatetime DESC
    FETCH FIRST 1 ROW ONLY
) t ON true
WHERE (t.actiondatetime >= @__periodFrom_1) AND (t.actiondatetime < @__AddDays_2)

有没有办法在 Linq 中做到这一点?或者也许 Linq 的结果与此查询类似?

提前致谢!

C# PostgreSQL LINQ EF-CORE-6.0

评论

0赞 Bagus Tesa 11/13/2023
也许最好从头开始重建查询?
0赞 James 11/13/2023
@BagusTesa那么,你的意思是我应该先进行查询,然后再将其转换为 linq?
0赞 Bagus Tesa 11/13/2023
这是一种方法。您也可以直接使用 LINQ。我不太确定查询的实际外观,因为没有示例数据、预期输出和我必须遵守的规则。首先,我对为什么该部件没有直接链接以及缺乏导航属性的使用一无所知。wheredocunent_history
0赞 Alander 11/13/2023
这应该会有所帮助 stackoverflow.com/questions/3014362/c-sharp-outer-apply-in-linq
2赞 Bagus Tesa 11/13/2023
tbh,使用 ORM 通常会放弃查询的一些功能。它们不能做很多原始SQL可以做的事情,它们提供了便利性而不是性能。

答:

1赞 Svyatoslav Danyliv 11/14/2023 #1

我想到的一种解决方法是使用 .在这种情况下,使用 Window 函数构建有点困难。 移除,根据您需要的过滤器DistinctINNER JOINDefaultIfEmpty()CROSS APPLY

var query =
    from doc in context.document
    from lasthistory in context.documenthistory
        .Where(x => x.documentid == doc.id)
        .OrderByDescending(x => x.actiondatetime)
        .Take(1)
        .Select(x => new { x.actionby, x.actiondatetime })
        .Distinct()
    where lasthistory.actiondatetime >= periodFrom
        && lasthistory.actiondatetime < periodTo.AddDays(1)
    select new
    {
        id = doc.id,
        lastactionby = lasthistory.actionby,
        lastactiondatetime = lasthistory.actiondatetime
    };

从另一端,您可以限制记录的扫描范围

var documents = context.document
    .Where(doc = context.documenthistory.Any(h => h.documentid == doc.id 
        && h.actiondatetime >= periodFrom
        && h.actiondatetime < periodTo.AddDays(1))
    );

var query =
    from doc in documents
    from lasthistory in context.documenthistory
        .Where(x => x.documentid == doc.id)
        .OrderByDescending(x => x.actiondatetime)
        .Take(1)
    where lasthistory.actiondatetime >= periodFrom
        && lasthistory.actiondatetime < periodTo.AddDays(1)
    select new
    {
        id = doc.id,
        lastactionby = lasthistory.actionby,
        lastactiondatetime = lasthistory.actiondatetime
    };

评论

0赞 James 11/16/2023
谢谢!linq with 是我需要的那个,它的性能比使用 .此外,查询下方的查询,它将首先选择所有数据。.Distinct()let.Distinct()documenthistory