【问题标题】:Optimizing SQL Server / C# queries and custom paging优化 SQL Server / C# 查询和自定义分页
【发布时间】:2018-09-10 20:28:11
【问题描述】:

我有一个如下所示的数据库表:

[Id] [int] IDENTITY(1,1) NOT NULL,
[TransactionID] [bigint] NOT NULL,
[Title] [nvarchar](120) NOT NULL,
[QuantitySoldTransaction] [int] NOT NULL,
[TransactionPrice] [float] NOT NULL,
[ItemID] [bigint] NOT NULL,
[TransactionDate] [datetime] NULL,
[StateOrProvince] [nvarchar](400) NULL,
[CountryName] [nvarchar](400) NULL,
[TwoCountryCode] [nvarchar](200) NULL,
[UserStoreId] [int] NOT NULL,

我必须通过这些参数对这些交易进行分组:

  • ItemID => 商品销售额
  • CountryName => 各国销售额
  • 交易日期 => 销售日期
  • 交易日期 => 一天中的小时销售额
  • 交易日期 => 星期几的销售额

我有几件事我不完全理解我应该如何实现它。

例如,我显示的每个项目的销售额数据在不同的视图中完全分开,并且每个国家、小时和一周中的每一天的销售额也显示在不同的视图中。

我想在 Web 应用程序中引入自定义分页,这样我就可以避免一次从服务器中提取所有数据,从而减少服务器上的负载。

每次我拉出所有记录时,我都必须做一个 group by 语句来获取所需的数据。

例如:

ViewBag.Transactions = allStoreItems.GroupBy(x => x.ItemID)
  .Select(pr => new TransactionsTabResults
  {
      Title = pr.Select(x=>x.Title).FirstOrDefault(),
      ItemID = pr.Key.ToString(),
      AveragePrice =  Math.Round(pr.Average(y => y.TransactionPrice), 2),
      TotalSoldItems = pr.Sum(x => x.QuantitySoldTransaction),
      TotalRevenuePerItem = Math.Round(Convert.ToDecimal(pr.Sum(x => x.TransactionPrice * x.QuantitySoldTransaction)), 2)

  }).OrderByDescending(x => x.TotalRevenuePerItem).ToList();

这些将是每个项目单独的销售额(每个项目 ID 的销售额)。这里可怕的是我一次提取所有数据,然后将其注入到用户的 DOM 中,这对最终用户和服务器来说性能都很糟糕。

我认为唯一的解决方案是自定义分页,这应该可以减少服务器上的负载。

所以我的问题是:如果我创建一个可以进行自定义分页的存储过程,但每次用户发出请求时仍会按分组进行,我猜这仍然具有糟糕的性能,因为分组的成本很高对吧?

所以我的想法是在我的以下之间创建一个表:

UserStores => New Table Here <= StoreTransactions

新表将包含每个参数的分组数据:

  1. 按一般日期(每月 30 天)、按项目 ID、按日期(按 24 小时)和再次按日期(按一周中的 7 天)..

所以这里的另一个问题是:多对多表是否会降低 SQL 查询的成本,从而使我能够更顺畅地对记录进行分页并显示我想要的内容?

【问题讨论】:

  • 有人吗? =)
  • 不确定它如何适合您的应用程序逻辑,但您是否考虑过创建索引视图,其中包含按您需要获取数据的级别分组的数据?

标签: c# asp.net sql-server stored-procedures group-by


【解决方案1】:

我假设给定的表类似于您问题中的 StoreTransactions 表。老实说,我不认为你建议的中间有一张桌子会有效。

我要做的是,假设您在 IDENTITY (Id) 列之上有一个唯一的聚集索引,为您需要的查询添加额外的覆盖非聚集索引。也就是说,如果索引开销不是问题。最重要的是,我会为您可能需要的每个“分组”编写一个单独的存储过程,并使该存储过程的输入参数包括 @pageSize@pageNumber。然后,根据您使用的 SQL Server 版本:

  • 如果您的 SQL Server 是 2012 或更高版本,请使用 OFFSET - FETCH 子句 (documentation)
  • 如果您的 SQL Server 是 2008R2 或更早版本,请在其中使用 CTEROW_NUMBER() 窗口函数来模拟分页 like so

我的建议是,对于项目分组和 SQL Server 2012 或更新版本,例如:

-- setting up my environment
CREATE TABLE dbo.StoreTransactions (
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TransactionID] [bigint] NOT NULL,
    [Title] [nvarchar](120) NOT NULL,
    [QuantitySoldTransaction] [int] NOT NULL,
    [TransactionPrice] [float] NOT NULL,
    [ItemID] [bigint] NOT NULL,
    [TransactionDate] [datetime] NULL,
    [StateOrProvince] [nvarchar](400) NULL,
    [CountryName] [nvarchar](400) NULL,
    [TwoCountryCode] [nvarchar](200) NULL,
    [UserStoreId] [int] NOT NULL,
    CONSTRAINT PK_StoreTransactions PRIMARY KEY(Id) -- this makes a unique clustered index on the Id column
);
GO

-- making a covering index for the calculations
CREATE NONCLUSTERED INDEX IX_StoreTransactions_Title_TransactionPrice_QuantitySoldTransaction
    ON dbo.StoreTransactions(Title, TransactionPrice, QuantitySoldTransaction);
GO
-- making an index for the ItemID example
CREATE NONCLUSTERED INDEX IX_StoreTransactions_ItemID
    ON dbo.StoreTransactions(ItemID);
GO

-- stored procedure grouped by ItemID
CREATE PROCEDURE dbo.ReturnPaginatedDataByItemID
    -- set your defaults
    @pageNumber int = 1,
    @pageSize int = 10
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
          MIN(t.Title) AS Title -- this is based on your FirstOrDefault() function
        , t.ItemID
        , AVG(t.TransactionPrice) AS AveragePrice
        , SUM(t.QuantitySoldTransaction) AS TotalSoldItems
        , SUM(t.TransactionPrice*t.QuantitySoldTransaction) AS TotalRevenuePerItem
    FROM dbo.StoreTransactions t
    GROUP BY t.ItemID
    ORDER BY TotalRevenuePerItem DESC   -- order is required for the evaluator to be able to know which rows from pages to take
    OFFSET @pageSize * (@pageNumber - 1) ROWS FETCH NEXT @pageSize ROWS ONLY;
END;

您还可以在存储过程中使用一个附加参数,该参数接受您想要分组的属性的名称,然后您有两个使用它的选项:

  • 根据该参数制作IF - ELSE 语句并执行适当的SELECT's
  • 将写入的选择转换为nvarchar(max),然后动态插入要选择和分组的内容,而不是我示例中的ItemID;最后,您只需使用sp_executesql 执行该动态查询

注意:确保不要忘记为其他列创建覆盖索引,就像我为 ItemID 制作的示例一样。

【讨论】:

  • 嘿 MK_ 感谢您的回复! :) 这就像一个魅力,但是......我显示的按日期分组的数据我需要一次显示全部,所以我猜我这里唯一的解决方案是将它分组并插入某处,这样我就不用一次性全部拉出来了?
  • @User987 - 你能详细说明一下吗?我完全不明白...您的意思是,当您想按日期显示数据(TransactionDate?)时,您需要按 ItemID 以及 CountryName 等分组显示它 - 一次?
  • 所以基本上,当我按交易日期对数据进行分组时,我需要一次将其全部提取出来......没有分页没有任何东西......只是我在表格中分组的原始表示要在图表中显示的日期...我认为最好的方法是将其简单地存储在包含此分组数据的表中?
  • 您可以尝试按照我的建议添加索引然后进行查询吗?如果在那之后不需要太多时间,如果你问我,硬件上的一些压力值得拥有实时数据。如果不是,并且实时数据不是那么重要,那么您可以使用其他一些表来存储数据并创建一个 SQL Server 代理作业,该作业将在特定时间间隔刷新它。只需确保在该刷新过程中您首先在临时表等中计算,然后才在事务中删除旧数据并插入新计算的数据。
  • 我会先尝试使用索引,看看会发生什么 :D 如果这样需要太多时间,那么我将求助于添加额外的表
猜你喜欢
  • 2018-09-05
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 2021-06-03
  • 2014-11-04
  • 2017-04-05
  • 2013-10-04
相关资源
最近更新 更多