在单个 LINQ-to-Entities 查询中限制来自多个单独表的结果。结果 T-SQL 是错误的

Limit results from multiple individual tables in a single LINQ-to-Entities query. Resultant T-SQL is wrong

我需要用一个查询查询多个 table,我需要分别限制每个 table 的结果。

一个例子...

我有一个 ContentItem、Retailer 和 Product table。

ContentItem 有一个 Type (int) 字段,它对应于 "Retailer" 和 "Product." 等内容类型的枚举,我正在为每个子子查询使用此字段过滤 ContentItem。

ContentItem 有一个 Id (pkey) 字段。

零售商和产品有一个 Id (pkey) 字段。 Id 也是 ContentItem.Id.

的 FK

我可以使用 LEFT JOIN 查询从所有三个 table 中 select。从那里,我可以限制返回的总行数,假设总共 6 行。

我想做的是分别限制从 Retailer 和 Product 返回的行数。这样,我总共将有 12 行(最多):6 行来自零售商,6 行来自产品。

我已经可以使用 SQL 完成此操作,但我很难将 LINQ-to-Entities 转换为 "do the right thing."


这是我的 SQL

SELECT * From
    (
            (SELECT * FROM (SELECT * FROM [dbo].[ContentItem] WHERE Type = 0 ORDER BY ContentItem.mtime OFFSET 0 ROWS FETCH NEXT 6 ROWS ONLY) Retailers)
        UNION ALL
            (SELECT * FROM (SELECT * FROM [dbo].[ContentItem] WHERE Type = 1 ORDER BY ContentItem.mtime OFFSET 0 ROWS FETCH NEXT 6 ROWS ONLY) Brands)
        UNION ALL
            (SELECT * FROM (SELECT * FROM [dbo].[ContentItem] WHERE Type = 2 ORDER BY ContentItem.mtime OFFSET 0 ROWS FETCH NEXT 6 ROWS ONLY) Products)
        UNION ALL
            (SELECT * FROM (SELECT * FROM [dbo].[ContentItem] WHERE Type = 3 ORDER BY ContentItem.mtime OFFSET 0 ROWS FETCH NEXT 6 ROWS ONLY) Certifications)
        UNION ALL
            (SELECT * FROM (SELECT * FROM [dbo].[ContentItem] WHERE Type = 4 ORDER BY ContentItem.mtime OFFSET 0 ROWS FETCH NEXT 6 ROWS ONLY) Claims)
    ) as ContentItem

    LEFT JOIN [dbo].[Retailer] ON (Retailer.Id = ContentItem.Id)
    LEFT JOIN [dbo].[Brand] ON (Brand.Id = ContentItem.Id)
    LEFT JOIN [dbo].[Product] ON (Product.Id = ContentItem.Id)
    LEFT JOIN [dbo].[Certification] ON (Certification.Id = ContentItem.Id)
    LEFT JOIN [dbo].[Claim] ON (Claim.Id = ContentItem.Id);

这是我的多次 LINQ 查询迭代之一(未返回所需结果)。

var queryRetailers = contentItemModel
    .Where(contentItem => contentItem.Type == ContentTypeEnum.Retailer)
    .OrderByDescending(o => o.mtime).Skip(skip).Take(take).Select(o => new { Id = o.Id });
var queryBrands = contentItemModel
    .Where(contentItem => contentItem.Type == ContentTypeEnum.Brand)
    .OrderByDescending(o => o.mtime).Skip(skip).Take(take).Select(o => new { Id = o.Id });
var queryProducts = contentItemModel
    .Where(contentItem => contentItem.Type == ContentTypeEnum.Product)
    .OrderByDescending(o => o.mtime).Skip(skip).Take(take).Select(o => new { Id = o.Id });
var queryCertifications = contentItemModel
    .Where(contentItem => contentItem.Type == ContentTypeEnum.Certification)
    .OrderByDescending(o => o.mtime).Skip(skip).Take(take).Select(o => new { Id = o.Id });
var queryClaims = contentItemModel
    .Where(contentItem => contentItem.Type == ContentTypeEnum.Claim)
    .OrderByDescending(o => o.mtime).Skip(skip).Take(take).Select(o => new { Id = o.Id });

var query = from contentItem in
    queryRetailers
    .Concat(queryBrands)
    .Concat(queryProducts)
    .Concat(queryCertifications)
    .Concat(queryClaims)

join item in context.Retailer on contentItem.Id equals item.Id into retailerGroup
    from retailer in retailerGroup.DefaultIfEmpty(null)

join item in context.Brand on contentItem.Id equals item.Id into brandGroup
    from brand in brandGroup.DefaultIfEmpty(null)

join item in context.Product on contentItem.Id equals item.Id into productGroup
    from product in productGroup.DefaultIfEmpty(null)

join item in context.Certification on contentItem.Id equals item.Id into certificationGroup
    from certification in certificationGroup.DefaultIfEmpty(null)

join item in context.Claim on contentItem.Id equals item.Id into claimGroup
    from claim in claimGroup.DefaultIfEmpty(null)

select new
{
    contentItem,
    retailer,
    brand,
    product,
    certification,
    claim
};

var results = query.ToList();

这个查询 returns SQL 本质上是 "nests" 我的 UNION ALL 语句,以及服务器 returns 来自数据库的所有行。

SELECT 
    [Distinct4].[C1] AS [C1], 
    [Distinct4].[C2] AS [C2], 
    [Extent6].[Id] AS [Id], 
    [Extent6].[RowVersion] AS [RowVersion], 
    [Extent6].[ctime] AS [ctime], 
    [Extent6].[mtime] AS [mtime], 
    [Extent7].[Id] AS [Id1], 
    [Extent7].[Recommended] AS [Recommended], 
    [Extent7].[RowVersion] AS [RowVersion1], 
    [Extent7].[ctime] AS [ctime1], 
    [Extent7].[mtime] AS [mtime1], 
    [Extent8].[Id] AS [Id2], 
    [Extent8].[OverrideGrade] AS [OverrideGrade], 
    [Extent8].[PlantBased] AS [PlantBased], 
    [Extent8].[Recommended] AS [Recommended1], 
    [Extent8].[RowVersion] AS [RowVersion2], 
    [Extent8].[ctime] AS [ctime2], 
    [Extent8].[mtime] AS [mtime2], 
    [Extent8].[Brand_Id] AS [Brand_Id], 
    [Extent8].[Grade_Name] AS [Grade_Name], 
    [Extent8].[Grade_Value] AS [Grade_Value], 
    [Extent9].[Id] AS [Id3], 
    [Extent9].[RowVersion] AS [RowVersion3], 
    [Extent9].[ctime] AS [ctime3], 
    [Extent9].[mtime] AS [mtime3], 
    [Extent9].[Grade_Name] AS [Grade_Name1], 
    [Extent9].[Grade_Value] AS [Grade_Value1], 
    [Extent10].[Id] AS [Id4], 
    [Extent10].[RowVersion] AS [RowVersion4], 
    [Extent10].[ctime] AS [ctime4], 
    [Extent10].[mtime] AS [mtime4], 
    [Extent10].[Grade_Name] AS [Grade_Name2], 
    [Extent10].[Grade_Value] AS [Grade_Value2]
    FROM       (SELECT DISTINCT 
        [UnionAll4].[C1] AS [C1], 
        [UnionAll4].[C2] AS [C2]
        FROM  (SELECT 
            [Distinct3].[C1] AS [C1], 
            [Distinct3].[C2] AS [C2]
            FROM ( SELECT DISTINCT 
                [UnionAll3].[C1] AS [C1], 
                [UnionAll3].[C2] AS [C2]
                FROM  (SELECT 
                    [Distinct2].[C1] AS [C1], 
                    [Distinct2].[C2] AS [C2]
                    FROM ( SELECT DISTINCT 
                        [UnionAll2].[C1] AS [C1], 
                        [UnionAll2].[C2] AS [C2]
                        FROM  (SELECT 
                            [Distinct1].[C1] AS [C1], 
                            [Distinct1].[C2] AS [C2]
                            FROM ( SELECT DISTINCT 
                                [UnionAll1].[C1] AS [C1], 
                                [UnionAll1].[Id] AS [C2]
                                FROM  (SELECT TOP (1000) 
                                    [Project1].[C1] AS [C1], 
                                    [Project1].[Id] AS [Id]
                                    FROM ( SELECT [Project1].[Id] AS [Id], [Project1].[mtime] AS [mtime], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[mtime] DESC) AS [row_number]
                                        FROM ( SELECT 
                                            [Extent1].[Id] AS [Id], 
                                            [Extent1].[mtime] AS [mtime], 
                                            1 AS [C1]
                                            FROM [dbo].[ContentItem] AS [Extent1]
                                            WHERE 0 =  CAST( [Extent1].[Type] AS int)
                                        )  AS [Project1]
                                    )  AS [Project1]
                                    WHERE [Project1].[row_number] > 0
                                    ORDER BY [Project1].[mtime] DESC
                                UNION ALL
                                    SELECT TOP (1000) 
                                    [Project3].[C1] AS [C1], 
                                    [Project3].[Id] AS [Id]
                                    FROM ( SELECT [Project3].[Id] AS [Id], [Project3].[mtime] AS [mtime], [Project3].[C1] AS [C1], row_number() OVER (ORDER BY [Project3].[mtime] DESC) AS [row_number]
                                        FROM ( SELECT 
                                            [Extent2].[Id] AS [Id], 
                                            [Extent2].[mtime] AS [mtime], 
                                            1 AS [C1]
                                            FROM [dbo].[ContentItem] AS [Extent2]
                                            WHERE 1 =  CAST( [Extent2].[Type] AS int)
                                        )  AS [Project3]
                                    )  AS [Project3]
                                    WHERE [Project3].[row_number] > 0
                                    ORDER BY [Project3].[mtime] DESC) AS [UnionAll1]
                            )  AS [Distinct1]
                        UNION ALL
                            SELECT TOP (1000) 
                            [Project7].[C1] AS [C1], 
                            [Project7].[Id] AS [Id]
                            FROM ( SELECT [Project7].[Id] AS [Id], [Project7].[mtime] AS [mtime], [Project7].[C1] AS [C1], row_number() OVER (ORDER BY [Project7].[mtime] DESC) AS [row_number]
                                FROM ( SELECT 
                                    [Extent3].[Id] AS [Id], 
                                    [Extent3].[mtime] AS [mtime], 
                                    1 AS [C1]
                                    FROM [dbo].[ContentItem] AS [Extent3]
                                    WHERE 2 =  CAST( [Extent3].[Type] AS int)
                                )  AS [Project7]
                            )  AS [Project7]
                            WHERE [Project7].[row_number] > 0
                            ORDER BY [Project7].[mtime] DESC) AS [UnionAll2]
                    )  AS [Distinct2]
                UNION ALL
                    SELECT TOP (1000) 
                    [Project11].[C1] AS [C1], 
                    [Project11].[Id] AS [Id]
                    FROM ( SELECT [Project11].[Id] AS [Id], [Project11].[mtime] AS [mtime], [Project11].[C1] AS [C1], row_number() OVER (ORDER BY [Project11].[mtime] DESC) AS [row_number]
                        FROM ( SELECT 
                            [Extent4].[Id] AS [Id], 
                            [Extent4].[mtime] AS [mtime], 
                            1 AS [C1]
                            FROM [dbo].[ContentItem] AS [Extent4]
                            WHERE 3 =  CAST( [Extent4].[Type] AS int)
                        )  AS [Project11]
                    )  AS [Project11]
                    WHERE [Project11].[row_number] > 0
                    ORDER BY [Project11].[mtime] DESC) AS [UnionAll3]
            )  AS [Distinct3]
        UNION ALL
            SELECT TOP (1000) 
            [Project15].[C1] AS [C1], 
            [Project15].[Id] AS [Id]
            FROM ( SELECT [Project15].[Id] AS [Id], [Project15].[mtime] AS [mtime], [Project15].[C1] AS [C1], row_number() OVER (ORDER BY [Project15].[mtime] DESC) AS [row_number]
                FROM ( SELECT 
                    [Extent5].[Id] AS [Id], 
                    [Extent5].[mtime] AS [mtime], 
                    1 AS [C1]
                    FROM [dbo].[ContentItem] AS [Extent5]
                    WHERE 4 =  CAST( [Extent5].[Type] AS int)
                )  AS [Project15]
            )  AS [Project15]
            WHERE [Project15].[row_number] > 0
            ORDER BY [Project15].[mtime] DESC) AS [UnionAll4] ) AS [Distinct4]
    LEFT OUTER JOIN [dbo].[Retailer] AS [Extent6] ON [Distinct4].[C2] = [Extent6].[Id]
    LEFT OUTER JOIN [dbo].[Brand] AS [Extent7] ON [Distinct4].[C2] = [Extent7].[Id]
    LEFT OUTER JOIN [dbo].[Product] AS [Extent8] ON [Distinct4].[C2] = [Extent8].[Id]
    LEFT OUTER JOIN [dbo].[Certification] AS [Extent9] ON [Distinct4].[C2] = [Extent9].[Id]
    LEFT OUTER JOIN [dbo].[Claim] AS [Extent10] ON [Distinct4].[C2] = [Extent10].[Id]

所以我的总体问题是:

1) 是否可以执行更简单的 SQL 查询来获得相同的结果?我知道 T-SQL 不支持子查询中每个 table 的偏移量,因此子查询包装。

2) 如果没有,我在 LINQ 查询中做错了什么?这甚至可以用 LINQ 实现吗?


我想在这里添加来自@radar 的 SQL 一切都很好并且格式化。它至少看起来是避免子子查询的优雅解决方案,并且仍然完成了 offset/fetch.

SELECT *
    FROM (SELECT
        [ContentItem].*,
        row_number() OVER ( PARTITION BY Type ORDER BY ContentItem.mtime ) as rn
        FROM [dbo].[ContentItem]
        LEFT JOIN [dbo].[Retailer] ON (Retailer.Id = ContentItem.Id)
        LEFT JOIN [dbo].[Brand] ON (Brand.Id = ContentItem.Id)
        LEFT JOIN [dbo].[Product] ON (Product.Id = ContentItem.Id)
        LEFT JOIN [dbo].[Certification] ON (Certification.Id = ContentItem.Id)
        LEFT JOIN [dbo].[Claim] ON (Claim.Id = ContentItem.Id)
    ) as x
WHERE x.rn >= a AND x.rn <= b;

a 是阈值下限(偏移量),b 是阈值上限(fetch-ish)。唯一的问题是 b 现在等于 fetch + a 而不是 fetch。第一组结果为 WHERE x.rn >= 0 AND x.rn <= 6,第二组为 WHERE x.rn >= 6 AND x.rn <= 12,第三组为 WHERE x.rn >= 12 AND x.rn <= 18,依此类推。

因为你看起来更简单SQL,你可以使用row_number解析函数,这样会更快

您需要尝试查看,因为有很多 left joins 并且这些表中还需要存在适当的索引。

select * 
from (
select *, row_number() over ( partition by Type order by ContentItem.mtime ) as rn
from [dbo].[ContentItem]
LEFT JOIN [dbo].[Retailer] ON (Retailer.Id = ContentItem.Id)
LEFT JOIN [dbo].[Brand] ON (Brand.Id = ContentItem.Id)
LEFT JOIN [dbo].[Product] ON (Product.Id = ContentItem.Id)
LEFT JOIN [dbo].[Certification] ON (Certification.Id = ContentItem.Id)
LEFT JOIN [dbo].[Claim] ON (Claim.Id = ContentItem.Id);
)
where rn <= 6

嗯,看来我是个白痴。那个 TOP(1000) 电话本应该告诉我的。我假设我的 take 变量设置为 6,但实际上它设置为 1000。结果我的巨型 LINQ 查询按预期工作,但嵌套的 UNION ALL 语句让我失望。

不过,我将进一步调查@radar 的回答。很难与更好的性能争论。