所以我试图创建一个自动提取过去12个月数据的视图(从上个月末开始。)
当我使用如下的where子句运行它时:
WHERE Visit_Date between 'Dec 1 2012' and 'Dec 1 2013'
它将在~1分钟内运行。
我有一些计算会自动创建这些日期。但是当我在where子句中使用它们时,查询在15分钟后仍在运行。
WHERE Visit_Date between DATEADD(mm,-12,DATEADD(mm,DATEDIFF(mm,12,GETDATE()),0))
and Dateadd(dd,-1,DATEADD(mm,DATEDIFF(mm,12,GETDATE()),0))
查询正在一个包含50多万条记录的表上运行。我相信这是一种更有效的方法。我猜测正在发生的事情是它通过每行的Getdate()计算,这显然不是理想的。
有什么建议吗? 请记住,我正在创建一个视图,我通常不会编写存储过程或动态SQL。
答案 0 :(得分:2)
我认为@Andriy可能是正确的(I've also blogged about it),这是由于基数估计错误(估计时日期相反)。您可以在KB #2481274,Connect #630583中查看更多详情,@ Andriy指出的问题:
Query runs slow with date expression, but fast with string literal
对于每月只更改一次的内容,我认为您可以考虑创建一个工作,在每月月初改变视图,将日期范围硬编码到视图中。这可能不是启用trace flag 4199的可怕替代方案,这可能是也可能不是永久修复,如果您全局启用它可能会或可能不会导致其他问题(而不是仅仅运行此问题的会话)查询 - 再次无法保证它总能使这个快速)。这是我正在考虑的那种过程:
CREATE PROCEDURE dbo.AlterThatView
AS
BEGIN
SET NOCOUNT ON;
DECLARE @end DATE = DATEADD(DAY, 1-DAY(GETDATE()), GETDATE());
DECLARE @start DATE = DATEADD(MONTH, -12, @end);
DECLARE @sql NVARCHAR(MAX) = N'ALTER VIEW dbo.ViewName
AS
SELECT ...
WHERE Visit_Date >= ''' + CONVERT(CHAR(8), @start, 112) + '''
AND Visit_Date < ''' + CONVERT(CHAR(8), @end, 112) + ''';';
EXEC sp_executesql @sql;
END
GO
只需创建一个在每个月的每个月1日午夜后运行的作业,然后调用此过程。您可能希望让工作也从视图中运行SELECT
。
请don't use BETWEEN for date range queries和stop using lazy shorthand for dateparts。
答案 1 :(得分:0)
因为您可以在&lt;中返回行在50M +行的表中1分钟,我猜你在Visit_Date列上有一个索引。在第一个查询中,SQL查询计划生成器对索引进行搜索,因为它大致了解将返回多少行,因为它知道日期边界。然后,它确定索引上的索引搜索是最佳行动计划。
在你的第二个查询中,它不知道或者不准确地知道可以返回多少行,所以它可能决定进行索引或表扫描而不是搜索。
您可以考虑的一个选项是在查询中使用索引提示。如果这不是生产代码,而且可能只是偶尔执行的即席查询,则索引提示是安全的。问题是如果索引被删除或名称更改,查询将失败。所以记住这一点。
要记住的其他事项是,如果您提供索引提示,SQL Server将使用该索引。如果开始日期和结束日期之间的时间跨度使得表的大部分返回,则搜索可能不如扫描有效(这就是SQL Server有时会选择扫描的原因)。
此处您最好的朋友正在分析生成的估算查询计划。你可以在SSMS中得到这个。我将尝试一些方法,直到您可以对查询执行索引搜索(而不是扫描)。