【问题标题】:Limit results from multiple individual tables in a single LINQ-to-Entities query. Resultant T-SQL is wrong在单个 LINQ-to-Entities 查询中限制来自多个单独表的结果。结果 T-SQL 是错误的
【发布时间】:2015-01-05 18:36:58
【问题描述】:

我需要用一个查询来查询多个表,并且我需要单独限制每个表的结果。

一个例子...

我有一个 ContentItem、Retailer 和 Product 表。

ContentItem 有一个 Type (int) 字段,对应于“零售商”和“产品”等内容类型的枚举。我正在为每个子查询使用此字段过滤 ContentItem。

ContentItem 有一个 Id (pkey) 字段。

Retailer 和 Product 有一个 Id (pkey) 字段。 Id 也是 ContentItem.Id 的 FK。

我可以使用 LEFT JOIN 查询从所有三个表中进行选择。然后,我可以限制返回的总行数,比如说总共 6 行。

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

我已经可以使用 SQL 完成此任务,但我很难让 LINQ-to-Entities “做正确的事情”。


这是我的 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();

此查询返回实质上“嵌套”我的 UNION ALL 语句的 SQL,并且服务器返回数据库中的所有行。

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 不支持子查询中每个表的偏移量,因此子查询包装。

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


我想在这里添加来自@radar 的 SQL,所有这些都很好并且格式化了。它至少看起来是避免子查询的优雅解决方案,并且仍然完成偏移/获取。

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 &gt;= 0 AND x.rn &lt;= 6,第二组结果为WHERE x.rn &gt;= 6 AND x.rn &lt;= 12,第三组结果为WHERE x.rn &gt;= 12 AND x.rn &lt;= 18,以此类推。

【问题讨论】:

  • 在您的查询中,每个表都由类型 ID 指示。为什么不使用 linq 过滤掉类型。例如,类型 = 0 代表零售商,类型 = 1 代表品牌
  • @NKD 是的 - 我会将其添加到我的表结构注释中。但是,我已经按类型过滤了。你能澄清一下你的意思吗?
  • 抱歉,我刚回到办公桌前。我只是想说您是否可以尝试以下简单的方法:var queryRetailers = contentItemModel .Where(m => m.Type == ContentTypeEnum.Retailer) .OrderByDescending(o => o.mtime).Take(6)。列表();我不确定你在用跳过等做什么,因为你没有在你的问题中提到它。
  • 啊。 Skip/Take 方法是我正在使用的偏移/限制方法。
  • 那么每个单独的 linq(queryRetailers、queryBrands 等)是否至少为您返回了正确的结果?我试图了解它失败的部分;单个 linq 查询或连接它们时。

标签: c# sql-server linq entity-framework tsql


【解决方案1】:

由于你在寻找更简单的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

【讨论】:

  • 这可能有效。 SQL 至少看起来不错,所以现在我需要将它翻译成 LINQ。我只需要在 Type 和 mtime 之间添加一个索引来避免排序,否则查询总共使用了 1 次索引扫描并且没有表扫描。一旦我解决了 LINQ,我会报告回来。感谢您的领导。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-24
    • 2013-08-10
    • 1970-01-01
    相关资源
    最近更新 更多