从 EF 为 PostGresql 生成的查询中的非最佳计数

Posted

技术标签:

【中文标题】从 EF 为 PostGresql 生成的查询中的非最佳计数【英文标题】:Non - optimal count in query generated from EF for PostGresql 【发布时间】:2017-09-07 09:40:40 【问题描述】:

我在尝试优化我的 ef 查询时遇到问题。我不知道即使在生成的 sql 中我可以改变什么,更不用说将它映射到实体语法。我将非常感谢任何优化提示。 EF中的查询:

var data = Context.Premise.Where(p => p.TransportManager != null);
var query = from premise in data
                        join psi in Context.PremiseSeriousInfringement on premise.Id equals psi.PremiseId
                        group psi by psi.Premise into bp
                        let types = from type in bp
                                    select type.SeriousInfringement.SeriousInfringementCode.SeriousInfringementType.Value
                        let premise = bp.Key
                        let NNInfringementCount = types.Count(t => t == "NN")
                        let BPNInfringementCount = types.Count(t => t == "BPN")
                        let PNInfringementCount = types.Count(t => t == "PN")
                        select new
                        
                            NNInfringementCount,
                            BPNInfringementCount,
                            PNInfringementCount,
                            premise.Id,
                            premise.Type,
                            premise.Status,
                            premise.CreationDate,
                            premise.BusinessCaseNumber,

                            FirstNames = premise.TransportManager.FirstNames,
                            FamilyName = premise.TransportManager.FamilyName,
                            CertificateNumber = premise.TransportManager.CertificateNumber,

                            Types = types
                        ;
query = query.OrderBy(filter.sortField + " " + filter.sortOrder)
            .Skip((filter.pageIndex - 1) * filter.pageSize)
            .Take(filter.pageSize);

[编辑我使它更具可读性]这是它生成的查询:

SELECT "Project8"."UndertakingId", "Project8"."TransportManagerId", "Project8"."CancelReasons", "Project8"."WarningDate", "Project8"."WarningDeliveryDate", "Project8"."FinishDate", "Project8"."InternalProceeding", "Project8"."AuthorityId", "Project8"."PenaltyImposed", "Project8"."PenaltyDescription", "Project8"."PenaltyType", "Project8"."isSendToEpuap", "Project8"."ModificationDate", "Project8"."CreatedByAppUserId", "Project8"."LastModifiedByAppUserId", "Project8"."Id1" AS "Id", "Project8"."Id" AS "Id1", "Project8"."C2" AS "C1", "Project8"."C3" AS "C2", "Project8"."C4" AS "C3", "Project8"."Type", "Project8"."Status", "Project8"."CreationDate", "Project8"."BusinessCaseNumber", "Project8"."FirstNames", "Project8"."FamilyName", "Project8"."CertificateNumber", "Project8"."C1" AS "C4", "Project8"."Value" FROM (
SELECT "Join14"."Id", "Join14"."UndertakingId", "Join14"."TransportManagerId", "Join14"."Type", "Join14"."Status", "Join14"."CancelReasons", "Join14"."WarningDate", "Join14"."WarningDeliveryDate", "Join14"."FinishDate", "Join14"."InternalProceeding", "Join14"."AuthorityId", "Join14"."PenaltyImposed", "Join14"."PenaltyDescription", "Join14"."BusinessCaseNumber", "Join14"."PenaltyType", "Join14"."isSendToEpuap", "Join14"."CreationDate", "Join14"."ModificationDate", "Join14"."CreatedByAppUserId", "Join14"."LastModifiedByAppUserId", "Join14"."Id_Alias12" AS "Id1", "Join14"."CertificateNumber", "Join14"."FirstNames", "Join14"."FamilyName", "Join18"."Value",  CASE  WHEN ("Join18"."SeriousInfringementId" IS NULL) THEN (CAST (NULL AS int4)) ELSE (1) END  AS "C1", "Join14"."C1" AS "C2", "Join14"."C2" AS "C3", "Join14"."C3" AS "C4" FROM (
SELECT "Project7"."Id", "Project7"."UndertakingId", "Project7"."TransportManagerId", "Project7"."Type", "Project7"."Status", "Project7"."CancelReasons", "Project7"."WarningDate", "Project7"."WarningDeliveryDate", "Project7"."FinishDate", "Project7"."InternalProceeding", "Project7"."AuthorityId", "Project7"."PenaltyImposed", "Project7"."PenaltyDescription", "Project7"."BusinessCaseNumber", "Project7"."PenaltyType", "Project7"."isSendToEpuap", "Project7"."CreationDate", "Project7"."ModificationDate", "Project7"."CreatedByAppUserId", "Project7"."LastModifiedByAppUserId", "Extent18"."Id" AS "Id_Alias12", "Extent18"."CertificateNumber", "Extent18"."FirstNames", "Extent18"."FamilyName", "Project7"."C1", "Project7"."C2", "Project7"."C3" FROM (
SELECT "Project5"."Id", "Project5"."UndertakingId", "Project5"."TransportManagerId", "Project5"."Type", "Project5"."Status", "Project5"."CancelReasons", "Project5"."WarningDate", "Project5"."WarningDeliveryDate", "Project5"."FinishDate", "Project5"."InternalProceeding", "Project5"."AuthorityId", "Project5"."PenaltyImposed", "Project5"."PenaltyDescription", "Project5"."BusinessCaseNumber", "Project5"."PenaltyType", "Project5"."isSendToEpuap", "Project5"."CreationDate", "Project5"."ModificationDate", "Project5"."CreatedByAppUserId", "Project5"."LastModifiedByAppUserId", "Project5"."C1", "Project5"."C2", (
SELECT CAST (count(1) AS int4) AS "A1" FROM (
SELECT "Join12"."SeriousInfringementId", "Join12"."Id_Alias10" AS "Id", "Join12"."SeriousInfringementCodeId", "Join12"."Id_Alias11" AS "Id1", "Join12"."SeriousInfringementTypeId", "Extent17"."Id" AS "Id2", "Extent17"."Value" FROM (
SELECT "Extent16"."SeriousInfringementTypeId", "Extent13"."Id", "Extent14"."SeriousInfringementId", "Extent15"."Id" AS "Id_Alias10", "Extent15"."SeriousInfringementCodeId", "Extent16"."Id" AS "Id_Alias11" FROM "dbo"."Premise" AS "Extent13" 
INNER JOIN "dbo"."PremiseSeriousInfringement" AS "Extent14" ON "Extent13"."Id" = "Extent14"."PremiseId" 
INNER JOIN "dbo"."SeriousInfringement" AS "Extent15" ON "Extent14"."SeriousInfringementId" = "Extent15"."Id" 
INNER JOIN "dbo"."SeriousInfringementCode" AS "Extent16" ON "Extent15"."SeriousInfringementCodeId" = "Extent16"."Id" WHERE "Extent13"."TransportManagerId" IS NOT NULL) AS "Join12" 
INNER JOIN "dbo"."DictionaryValue" AS "Extent17" ON "Join12"."SeriousInfringementTypeId" = "Extent17"."Id" WHERE ("Project5"."Id" = "Join12"."Id" OR TRUE = FALSE) AND E'PN' = "Extent17"."Value") AS "Project6") AS "C3" FROM (
SELECT "Project3"."Id", "Project3"."UndertakingId", "Project3"."TransportManagerId", "Project3"."Type", "Project3"."Status", "Project3"."CancelReasons", "Project3"."WarningDate", "Project3"."WarningDeliveryDate", "Project3"."FinishDate", "Project3"."InternalProceeding", "Project3"."AuthorityId", "Project3"."PenaltyImposed", "Project3"."PenaltyDescription", "Project3"."BusinessCaseNumber", "Project3"."PenaltyType", "Project3"."isSendToEpuap", "Project3"."CreationDate", "Project3"."ModificationDate", "Project3"."CreatedByAppUserId", "Project3"."LastModifiedByAppUserId", "Project3"."C1", (
SELECT CAST (count(1) AS int4) AS "A1" FROM (
SELECT "Join8"."SeriousInfringementId", "Join8"."Id_Alias7" AS "Id", "Join8"."SeriousInfringementCodeId", "Join8"."Id_Alias8" AS "Id1", "Join8"."SeriousInfringementTypeId", "Extent12"."Id" AS "Id2", "Extent12"."Value" FROM (
SELECT "Extent11"."SeriousInfringementTypeId", "Extent8"."Id", "Extent9"."SeriousInfringementId", "Extent10"."Id" AS "Id_Alias7", "Extent10"."SeriousInfringementCodeId", "Extent11"."Id" AS "Id_Alias8" FROM "dbo"."Premise" AS "Extent8" 
INNER JOIN "dbo"."PremiseSeriousInfringement" AS "Extent9" ON "Extent8"."Id" = "Extent9"."PremiseId" 
INNER JOIN "dbo"."SeriousInfringement" AS "Extent10" ON "Extent9"."SeriousInfringementId" = "Extent10"."Id" 
INNER JOIN "dbo"."SeriousInfringementCode" AS "Extent11" ON "Extent10"."SeriousInfringementCodeId" = "Extent11"."Id" WHERE "Extent8"."TransportManagerId" IS NOT NULL) AS "Join8" 
INNER JOIN "dbo"."DictionaryValue" AS "Extent12" ON "Join8"."SeriousInfringementTypeId" = "Extent12"."Id" WHERE ("Project3"."Id" = "Join8"."Id" OR TRUE = FALSE) AND E'BPN' = "Extent12"."Value") AS "Project4") AS "C2" FROM (
SELECT "Alias2"."Id", "Alias2"."UndertakingId", "Alias2"."TransportManagerId", "Alias2"."Type", "Alias2"."Status", "Alias2"."CancelReasons", "Alias2"."WarningDate", "Alias2"."WarningDeliveryDate", "Alias2"."FinishDate", "Alias2"."InternalProceeding", "Alias2"."AuthorityId", "Alias2"."PenaltyImposed", "Alias2"."PenaltyDescription", "Alias2"."BusinessCaseNumber", "Alias2"."PenaltyType", "Alias2"."isSendToEpuap", "Alias2"."CreationDate", "Alias2"."ModificationDate", "Alias2"."CreatedByAppUserId", "Alias2"."LastModifiedByAppUserId", (
SELECT CAST (count(1) AS int4) AS "A1" FROM (
SELECT "Join4"."SeriousInfringementId", "Join4"."Id_Alias4" AS "Id", "Join4"."SeriousInfringementCodeId", "Join4"."Id_Alias5" AS "Id1", "Join4"."SeriousInfringementTypeId", "Extent7"."Id" AS "Id2", "Extent7"."Value" FROM (
SELECT "Extent6"."SeriousInfringementTypeId", "Extent3"."Id", "Extent4"."SeriousInfringementId", "Extent5"."Id" AS "Id_Alias4", "Extent5"."SeriousInfringementCodeId", "Extent6"."Id" AS "Id_Alias5" FROM "dbo"."Premise" AS "Extent3" 
INNER JOIN "dbo"."PremiseSeriousInfringement" AS "Extent4" ON "Extent3"."Id" = "Extent4"."PremiseId" 
INNER JOIN "dbo"."SeriousInfringement" AS "Extent5" ON "Extent4"."SeriousInfringementId" = "Extent5"."Id" 
INNER JOIN "dbo"."SeriousInfringementCode" AS "Extent6" ON "Extent5"."SeriousInfringementCodeId" = "Extent6"."Id" WHERE "Extent3"."TransportManagerId" IS NOT NULL) AS "Join4" 
INNER JOIN "dbo"."DictionaryValue" AS "Extent7" ON "Join4"."SeriousInfringementTypeId" = "Extent7"."Id" WHERE ("Alias2"."Id" = "Join4"."Id" OR TRUE = FALSE) AND E'NN' = "Extent7"."Value") AS "Project2") AS "C1" FROM (
SELECT DISTINCT "Extent1"."Id", "Extent1"."UndertakingId", "Extent1"."TransportManagerId", "Extent1"."Type", "Extent1"."Status", "Extent1"."CancelReasons", "Extent1"."WarningDate", "Extent1"."WarningDeliveryDate", "Extent1"."FinishDate", "Extent1"."InternalProceeding", "Extent1"."AuthorityId", "Extent1"."PenaltyImposed", "Extent1"."PenaltyDescription", "Extent1"."BusinessCaseNumber", "Extent1"."PenaltyType", "Extent1"."isSendToEpuap", "Extent1"."CreationDate", "Extent1"."ModificationDate", "Extent1"."CreatedByAppUserId", "Extent1"."LastModifiedByAppUserId" FROM "dbo"."Premise" AS "Extent1" 
INNER JOIN "dbo"."PremiseSeriousInfringement" AS "Extent2" ON "Extent1"."Id" = "Extent2"."PremiseId" AND "Extent2"."PremiseId" = "Extent1"."Id" WHERE "Extent1"."TransportManagerId" IS NOT NULL) AS "Alias2") AS "Project3") AS "Project5") AS "Project7" 
LEFT OUTER JOIN "dbo"."TransportManager" AS "Extent18" ON "Project7"."TransportManagerId" = "Extent18"."Id" ORDER BY "Project7"."Type" ASC  OFFSET 0 LIMIT 25) AS "Join14" 
LEFT OUTER JOIN (
SELECT "Extent19"."Id", "Extent23"."Value", "Extent20"."SeriousInfringementId" FROM "dbo"."Premise" AS "Extent19" 
INNER JOIN "dbo"."PremiseSeriousInfringement" AS "Extent20" ON "Extent19"."Id" = "Extent20"."PremiseId" 
INNER JOIN "dbo"."SeriousInfringement" AS "Extent21" ON "Extent20"."SeriousInfringementId" = "Extent21"."Id" 
INNER JOIN "dbo"."SeriousInfringementCode" AS "Extent22" ON "Extent21"."SeriousInfringementCodeId" = "Extent22"."Id" 
INNER JOIN "dbo"."DictionaryValue" AS "Extent23" ON "Extent22"."SeriousInfringementTypeId" = "Extent23"."Id" WHERE "Extent19"."TransportManagerId" IS NOT NULL) AS "Join18" ON "Join14"."Id" = "Join18"."Id" OR TRUE = FALSE) AS "Project8" ORDER BY "Project8"."Type" ASC ,"Project8"."UndertakingId" ASC ,"Project8"."TransportManagerId" ASC ,"Project8"."CancelReasons" ASC ,"Project8"."WarningDate" ASC ,"Project8"."WarningDeliveryDate" ASC ,"Project8"."FinishDate" ASC ,"Project8"."InternalProceeding" ASC ,"Project8"."AuthorityId" ASC ,"Project8"."PenaltyImposed" ASC ,"Project8"."PenaltyDescription" ASC ,"Project8"."PenaltyType" ASC ,"Project8"."isSendToEpuap" ASC ,"Project8"."ModificationDate" ASC ,"Project8"."CreatedByAppUserId" ASC ,"Project8"."LastModifiedByAppUserId" ASC ,"Project8"."Id1" ASC ,"Project8"."Id" ASC ,"Project8"."Status" ASC ,"Project8"."CreationDate" ASC ,"Project8"."BusinessCaseNumber" ASC ,"Project8"."C1" ASC

这是 PgAdmin III 提出的计划:

[编辑] 我的朋友想出了这样的 sql:

select premise."Id", premise."Type", premise."CreationDate", premise."Status", premise."BusinessCaseNumber", tm."FirstNames", tm."FamilyName",tm."CertificateNumber",premiseSum.NNCount,  premiseSum.BPNCount, premiseSum.PNCount from
(
    select distinct 
        premiseGroup."PremiseId", 

        sum(premiseGroup.NNCount) OVER (partition  BY premiseGroup."PremiseId") NNCount, 
        sum(premiseGroup.BPNCount) OVER (partition  BY premiseGroup."PremiseId") BPNCount,
        sum(premiseGroup.PNCount) OVER (partition  BY premiseGroup."PremiseId") PNCount
    from
    (
        select psi."PremiseId", 
            (select sum(count(sit."Value")) OVER (ORDER BY psi."PremiseId") where "Value" = 'NN') NNCount, 
            (select sum(count(sit."Value")) OVER (ORDER BY psi."PremiseId") where "Value" = 'PN') PNCount, 
            (select sum(count(sit."Value")) OVER (ORDER BY psi."PremiseId") where "Value" = 'BPN') BPNCount
        from "dbo"."Premise" premise 
        inner join "dbo"."PremiseSeriousInfringement" psi on psi."PremiseId" = premise."Id"
        inner join "dbo"."SeriousInfringement" si on psi."SeriousInfringementId" = si."Id"
        inner join "dbo"."SeriousInfringementCode" sic on si."SeriousInfringementCodeId" = sic."Id"
        inner join "dbo"."DictionaryValue" sit on sic."SeriousInfringementTypeId" = sit."Id"
        group by psi."PremiseId", sit."Value"
    ) premiseGroup
) premiseSum
join "dbo"."Premise" premise on premise."Id" = premiseSum."PremiseId"
join "dbo"."TransportManager" tm on tm."Id" = premise."TransportManagerId"

它非常快并且有很好的查询计划,但我们不知道如何在 LINQ 和 EF 中编写它。

【问题讨论】:

【参考方案1】:

无法在 PostgreSQL 上进行测试,但根据我的经验,EF 通常会为 Count 的谓词版本创建低效的翻译。

我通常做的是将谓词 Count 构造 (Count(condition) 替换为等效的条件 Sum 构造 (Sum(condition ? 1 : 0)) 转换为更好的 SQL(至少对于 Sql Server),所以值得尝试:

let NNInfringementCount = types.Sum(t => t == "NN" ? 1 : 0)
let BPNInfringementCount = types.Sum(t => t == "BPN" ? 1 : 0)
let PNInfringementCount = types.Sum(t => t == "PN" ? 1 : 0)

【讨论】:

速度更快,但变化不大,EF还是创建了3个嵌套循环的子查询。

以上是关于从 EF 为 PostGresql 生成的查询中的非最佳计数的主要内容,如果未能解决你的问题,请参考以下文章

从 PostgreSQL 中的行生成系列

为啥 EF 为简单查询生成子查询?

首先是EF6 postgresql数据库,无法生成模型

EF框架操作postgresql,实现WKT类型坐标的插入,查询,以及判断是否相交

如何从 CSV 为 PostgreSQL 副本生成模式

如何使用 PredicateBuilder、EF Core 5 和 Postgresql 10+ 执行不区分大小写和重音的 LIKE(子字符串)查询?