递归SQL查询以加速非索引查询

时间:2010-10-12 16:31:05

标签: sql sql-server tsql sql-server-2008 recursion

这个问题在很大程度上是出于好奇心,因为我确实有一个有效的查询(它只需要比我想要的时间长一点)。

我有一张包含400万行的表格。此表的唯一索引是自动增量BigInt ID。查询正在其中一列中查找不同的值,但仅返回1天。不幸的是,评估的ReportDate列不是DateTime类型,甚至不是BigInt,而是格式为YYYYMMDD的char(8)。所以查询有点慢。

  SELECT Category 
    FROM Reports 
   where ReportDate = CONVERT(VARCHAR(8), GETDATE(), 112) 
GROUP BY Category

请注意,上述声明中的日期转换只是将其转换为YYYYMMDD格式进行比较。

我想知道是否有办法根据我知道我感兴趣的唯一数据位于表的“底部”的事实来优化此查询。我正在考虑某种递归的SELECT函数,它逐渐增加了一个可用于最终查询的临时表。

例如,在psuedo-sql:

N = 128
TemporaryTable = SELECT TOP {N} * 
                   FROM Reports 
               ORDER BY ID DESC 

/* Once we hit a date < Today, we can stop */
if(TemporaryTable does not contain ReportDate < Today) 
  N = N**2
  Repeat Select

/* We now have a smallish table to do our query */
  SELECT Category 
    FROM TemproaryTable 
   where ReportDate = CONVERT(VARCHAR(8), GETDATE(), 112) 
GROUP BY Category

这有意义吗?有可能吗?

这是在MS SQL Server 2008上。

5 个答案:

答案 0 :(得分:4)

我可能会建议您不需要转换以YYYYMMDD格式存储为char数据的Date;这种格式本身就是可以排序的。我会改为将您的日期转换为该格式的输出。

此外,您编写转换的方式是,它会为每个单独的行转换当前的DateTime,因此即使为整个查询存储该值也可以加快速度...但我认为只需将您要搜索的日期转换为该格式的char即可。

我还建议你得到你需要创建的索引......当然......这不是你问的问题:P

答案 1 :(得分:3)

为什么不创建您需要的索引?

create index idx_Reports_ReportDate 
    on Reports(ReportDate, Category)

答案 2 :(得分:3)

不,这没有意义。优化此查询的唯一方法是为其设置覆盖索引:

CREATE INDEX ndxReportDateCategory ON Reports (ReportDate, Category);

更新

考虑到您无法修改架构的注释,您应该修改架构。如果你仍然不能,那么答案仍然适用:解决方案是有一个索引。

最后,为了更直接地回答你的问题,如果你在ID和ReportData之间有很强的相关性:你寻找的ID是ReportDate比你所追求的日期小的最大ID:

SELECT MAX(Id) 
FROM Reports
WHERE ReportDate < 'YYYYMMDD';

这将对ID索引执行反向扫描,并停止在所需日期之前的第一个ID(即扫描整个表)。然后,您可以根据此找到的最大ID来过滤您的报告。

答案 3 :(得分:1)

我想你会发现关于SARGability的讨论,在Rob Farley的博客上,你的帖子主题非常有趣。

http://blogs.lobsterpot.com.au/2010/01/22/sargable-functions-in-sql-server/

一种有趣的替代方法,不需要您修改现有的列数据类型,就是利用计算列。

alter table REPORTS
add castAsDate as CAST(ReportDate as date)

create index rf_so2 on REPORTS(castAsDate) include (ReportDate)

答案 4 :(得分:0)

我偶尔用来进入日志表的一种查询模式是用子查询来限制:

DECLARE @ReportDate varchar(8)
SET @ReportDate = Convert(varchar(8), GetDate(), 112)

SELECT *
FROM
(
SELECT top 20000 *
FROM Reports
ORDER BY ID desc
) sub
WHERE sub.ReportDate = @ReportDate

20k / 4M =读取表的0.5%。


这是一个循环解决方案。注意:可能希望在临时表中将ID主键和Reportdate编入索引。

DECLARE @ReportDate varchar(8)
SET @ReportDate = Convert(varchar(8), GetDate(), 112)

DECLARE @CurrentDate varchar(8), MinKey bigint


SELECT top 2000 * INTO #MyTable
FROM Reports ORDER BY ID desc

SELECT @CurrentDate = MIN(ReportDate), @MinKey = MIN(ID)
FROM #MyTable

WHILE @ReportDate <= @CurrentDate
BEGIN

  SELECT top 2000 * INTO #MyTable
  FROM Reports WHERE ID < @MinKey ORDER BY ID desc

  SELECT @CurrentDate = MIN(ReportDate), @MinKey = MIN(ID)
  FROM #MyTable

END

SELECT * FROM #MyTable
WHERE ReportDate = @ReportDate


DROP TABLE #MyTable