我正在做一个非常大的LinqToSql语句,它返回一个新对象。由于SQL方法的数量(主要是Sum和convert),SQL运行需要很长时间,因此加载网页需要很长时间(10-15秒)。虽然我可以使用AJAX或类似的CSS加载器。我首先想知道是否有一种简单的方法可以实现我想要从SQL数据库获得的东西。
我想:
Linq语句本身是一个很长的写入,但是当转换为SQL时,它充满了COALESCE和其他重要的SQL方法。
这是我的LINQ声明:
decimal _default = (decimal)0.0000;
var users = from bio in ctx.tbl_Bios.Where(bio => bio.SLXUID != null)
join opp in ctx.slx_Opportunities.Where(opp => opp.STATUS == "open") on bio.SLXUID equals opp.ACCOUNTMANAGERID into opps
select new UserStats{
Name = bio.FirstName + " " + bio.SurName,
EnquiryMoney = opps.Where(opp => opp.SALESCYCLE == "Enquiry").Sum(opp => (opp.ACTUALAMOUNT.HasValue && opp.ACTUALAMOUNT.Value != _default ? opp.ACTUALAMOUNT : opp.SALESPOTENTIAL.HasValue ? (decimal)opp.SALESPOTENTIAL.Value : _default)).GetValueOrDefault(_default),
EnquiryNum = opps.Where(opp => opp.SALESCYCLE == "Enquiry").Count(),
GoingAheadMoney = opps.Where(opp => opp.SALESCYCLE == "Going Ahead").Sum(opp => (opp.ACTUALAMOUNT.HasValue && opp.ACTUALAMOUNT.Value != _default ? opp.ACTUALAMOUNT : opp.SALESPOTENTIAL.HasValue ? (decimal)opp.SALESPOTENTIAL.Value : _default)).GetValueOrDefault(_default),
GoingAheadNum = opps.Where(opp => opp.SALESCYCLE == "Going Ahead").Count(),
GoodPotentialMoney = opps.Where(opp => opp.SALESCYCLE == "Good Potential").Sum(opp => (opp.ACTUALAMOUNT.HasValue && opp.ACTUALAMOUNT.Value != _default ? opp.ACTUALAMOUNT : opp.SALESPOTENTIAL.HasValue ? (decimal)opp.SALESPOTENTIAL.Value : _default)).GetValueOrDefault(_default),
GoodPotentialNum = opps.Where(opp => opp.SALESCYCLE == "Good Potential").Count(),
LeadMoney = opps.Where(opp => opp.SALESCYCLE == "Lead").Sum(opp => (opp.ACTUALAMOUNT.HasValue && opp.ACTUALAMOUNT.Value != _default ? opp.ACTUALAMOUNT : opp.SALESPOTENTIAL.HasValue ? (decimal)opp.SALESPOTENTIAL.Value : _default)).GetValueOrDefault(_default),
LeadNum = opps.Where(opp => opp.SALESCYCLE == "Lead").Count(),
PriceOnlyMoney = opps.Where(opp => opp.SALESCYCLE == "Price Only").Sum(opp => (opp.ACTUALAMOUNT.HasValue && opp.ACTUALAMOUNT.Value != _default ? opp.ACTUALAMOUNT : opp.SALESPOTENTIAL.HasValue ? (decimal)opp.SALESPOTENTIAL.Value : _default)).GetValueOrDefault(_default),
PriceOnlyNum = opps.Where(opp => opp.SALESCYCLE == "Price Only").Count(),
ProvisionalMoney = opps.Where(opp => opp.SALESCYCLE == "Provisional").Sum(opp => (opp.ACTUALAMOUNT.HasValue && opp.ACTUALAMOUNT.Value != _default ? opp.ACTUALAMOUNT : opp.SALESPOTENTIAL.HasValue ? (decimal)opp.SALESPOTENTIAL.Value : _default)).GetValueOrDefault(_default),
ProvisionalNum = opps.Where(opp => opp.SALESCYCLE == "Provisional").Count()
};
答案 0 :(得分:4)
您可以做多件事:
已过滤的索引:根据“机会”表中围绕值“打开”的记录细分,您可以在“打开”上创建过滤索引。如果你有大约相等数量的'open'和'closed'(或其他任何值),那么过滤后的索引会让你的TSQL只查看具有'open'的记录。过滤的索引仅存储符合谓词的数据;在这种情况下,您加入的任何内容都具有“开放”值。这样,它就不必扫描其他索引中可能已经打开的记录。
摘要/汇总表:创建一个包含您正在寻找的值的汇总表;在这种情况下,您正在寻找Sums和count - 为什么不创建一个只有一行具有这些计数的表?您可以使用存储过程/代理作业来使其保持最新。如果您的查询允许,您还可以尝试创建索引视图;我会进入下面的那个。对于汇总表;你基本上运行一个存储过程来计算这些字段并定期更新它们(比如每隔几分钟或每分钟一次,具体取决于负载)并将这些结果写入新表;这将是你的Rollup表。然后您的结果就像选择语句一样简单。这将是非常快的,代价是每隔几分钟计算这些总和的负荷。根据记录的数量,这可能会有问题。
索引视图:可能是解决此类问题的“正确”方式,depending on your constraints,以及我们谈论的行数(在我的情况下;我;追求它的情况下,有成千上万的行)。
你也可以为这些状态中的每一个创建一个过滤的索引(它有点滥用;但它会起作用),然后只是在它的求和/计数时,它只需要依赖与状态匹配的索引它正在寻找。
创建过滤后的索引:
CREATE NONCLUSTERED INDEX FI_OpenStatus_Opportunities
ON dbo.Opportunities (AccountManagerId, Status, ActualAmount)
WHERE status = 'OPEN';
GO
同样,您的金额和计数(每列一个):
CREATE NONCLUSTERED INDEX FI_SalesCycleEnquiry_Status_Opportunities
ON dbo.Opportunities (AccountManagerId, Status, SalesCycle, ActualAmount)
WHERE status = 'OPEN' and SalesCycle = 'Enquiry'
(等等)。
我不是说这是你最好的主意;但这是一个想法。它是否是一个好的取决于它在您的环境中的工作负载(我无法回答)。
您还可以创建包含此汇总信息的索引视图;这有点先进,取决于你。
要做到这一点:
CREATE VIEW [SalesCycle_Summary] WITH SCHEMABINDING AS
SELECT AccountManagerID, Status, SUM(ActualAmount) AS MONETARY
,COUNT_BIG(Status) as Counts
FROM [DBO].Opportunities
GROUP BY AccountManagerID, Status
GO
-- Create clustered index on the view; making it an indexed view
CREATE UNIQUE CLUSTERED INDEX IDX_SalesCycle_Summary ON [SalesCycle_Summary] (AccountManagerId);
然后(取决于您的设置)您可以直接加入该索引视图,也可以通过提示包含它(尝试前者)。
最后,如果这些都不起作用(索引视图周围有一些问题 - 我在大约6个月内没有使用它们,所以我不太记得那个让我感到困惑的具体问题),你总是可以创建CTE并完全放弃了Linq-To-SQL。
答案有点超出范围(因为我已经给出了两种方法,他们需要你进行大量的调查)。
调查这些行为:
从Linq-To-SQL语句(here's how you do that)获取生成的SQL。
打开SSMS并在查询窗口中打开以下内容:
SET STATISTICS IO ON
SET STATISTICS TIME ON
在继续之前修复索引的任何问题。如果您收到缺失指数警告;调查它们并解决它们,然后重新运行基准测试。
这些起始编号是您的基准。
SET NOCOUNT ON
,这样您就不会影响结果)这里没有“简单”的答案;答案取决于您的数据,您的数据使用情况,以及您可以对底层架构进行的更改。一旦在SSMS中运行它,您将看到它有多少是Linq-To-SQL开销,以及查询本身有多少。
答案 1 :(得分:1)
我在查询中先前将linq查询设置为本地,然后创建一个group by然后创建我的对象。由于返回的项目数量很少,我只能这样做,因此服务器可以轻松处理它们。其他任何遇到类似问题的人最好建议使用George Stocker的答案
我将查询更新为以下内容:
var allOpps = ctx.slx_Opportunities.Where(opp => opp.STATUS == "open").GroupBy(opp => opp.SALESCYCLE).ToList();
var users = ctx.tbl_Bios.Where(bio => bio.SLXUID != null).ToList().Select(bio => new UserStats
{
LeadNum = allOpps.Single(group => group.Key == "Lead").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Count(),
LeadMoney = allOpps.Single(group => group.Key == "Lead").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Sum(opp => opp.SALESPOTENTIAL.GetValueOrDefault(_default)),
GoingAheadNum = allOpps.Single(group => group.Key == "Going Ahead").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Count(),
GoingAheadMoney = allOpps.Single(group => group.Key == "Going Ahead").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Sum(opp => opp.SALESPOTENTIAL.GetValueOrDefault(_default)),
EnquiryNum = allOpps.Single(group => group.Key == "Enquiry").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Count(),
EnquiryMoney = allOpps.Single(group => group.Key == "Enquiry").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Sum(opp => opp.SALESPOTENTIAL.GetValueOrDefault(_default)),
GoodPotentialNum = allOpps.Single(group => group.Key == "Good Potential").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Count(),
GoodPotentialMoney = allOpps.Single(group => group.Key == "Good Potential").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Sum(opp => opp.SALESPOTENTIAL.GetValueOrDefault(_default)),
PriceOnlyNum = allOpps.Single(group => group.Key == "Price Only").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Count(),
PriceOnlyMoney = allOpps.Single(group => group.Key == "Price Only").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Sum(opp => opp.SALESPOTENTIAL.GetValueOrDefault(_default)),
ProvisionalNum = allOpps.Single(group => group.Key == "Provisional Booking").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Count(),
ProvisionalMoney = allOpps.Single(group => group.Key == "Provisional Booking").Where(opp => opp.ACCOUNTMANAGERID == bio.SLXUID).Sum(opp => opp.SALESPOTENTIAL.GetValueOrDefault(_default)),
Name = bio.FirstName + " " + bio.SurName
}).ToList();