比较日期时间时使用索引

时间:2019-03-15 18:50:53

标签: sql-server datetime indexing casting sql-server-2016

我有两个表,两个表都包含数百万行数据。

tbl_one:
purchasedtm DATETIME,
userid      INT,
totalcost   INT

tbl_two:
id          BIGINT,
eventdtm    DATETIME,
anothercol  INT

第一个表的前两列具有聚集索引:CLUSTERED INDEX tbl_one_idx ON(purchasedtm, userid)

第二个在其ID列上有一个主键,在eventdtm列上也有一个非聚集索引。

我想运行一个查询,以查找purchasedtmeventdtm在同一天的行。

我最初将查询写为:

WHERE CAST(tbl_one.purchasedtm AS DATE) = CAST(tbl_two.eventdtm AS DATE)

但这不会使用两个索引中的任何一个。

后来,我将查询更改为此:

WHERE tbl_one.purchasedtm >= CAST(tbl_two.eventdtm AS DATE)
AND tbl_one.purchasedtm < DATEADD(DAY, 1, CAST(tbl_two.eventdtm AS DATE))

这样,因为比较的一侧只包装在一个函数中,所以另一侧仍然可以使用其索引。是吗?

我还有其他一些问题:

  • 我也可以用其他方式编写查询,即保持tbl_two.eventdtm不变并将tbl_one.purchasedtm包裹在CAST()中。这会改变性能吗?
  • 如果上一个问题的答案是肯定的,是因为eventdtm有自己的专用索引,而查找purcahsedtm只会是部分索引匹配吗?
  • 在确定两个选择中哪个更好时,我还可以考虑其他因素吗? (例如,如果tbl_one中有几百万行,而tbl_two中有数十亿行,那会影响我应该CAST哪一列,我不应该哪一列?)
  • 一般来说,如果您比较两个都被索引的列,那么与仅对其中一个索引的相似方案相比,我们是否可以获得任何性能?
  • 最后,我可以不使用CAST来执行原始任务吗?

注意:我没有创建或修改索引,添加列等的功能。

2 个答案:

答案 0 :(得分:0)

可以在两个仅包含日期部分的表中创建一个持久化的计算列:

purchasedt AS CAST(purchasedtm AS DATE)
eventdt    AS CAST(eventdtm    AS DATE)

并在其上创建索引。

关于原始查询:SQL Server 可以对此进行翻译:

WHERE CAST(tbl_one.purchasedtm AS DATE) = CAST(tbl_two.eventdtm AS DATE)

类似于以下内容:

WHERE tbl_one.purchasedtm BETWEEN -- first ms of tbl_two.eventdtm
                              AND -- last ms of tbl_two.eventdtm

但是在您的情况下(i)必须在tbl_two内为数百万行计算该值(ii)在循环内必须执行范围扫描。 SQL Server可能不使用索引。

建立索引的日期列将导致相等比较且不进行转换。

答案 1 :(得分:0)

小。评论后很晚,但是...

如评论中所述,诸如CAST(DateTimeColumn AS date)之类的代码实际上是可保存的。罗伯·法利(Rob Farley)发表了一些有关SARGable和Non-SARGable功能here的文章,但是,我还是要讲一些事情。

首先,将函数应用于列通常会使您的查询不可SARG,尤其是如果它更改了值的顺序或它们的顺序是没有意义的。采取类似的东西:

SELECT *
FROM TABLE
WHERE RIGHT(COLUMN,5) = 'value';

在此列中值的顺序完全无济于事,因为我们关注的是右侧字符。不幸的是,正如Rob还讨论的那样:

SELECT *
FROM TABLE
WHERE LEFT(COLUMN,5) = 'value';

这也是非SARG。但是,接下来呢?

SELECT *
FROM TABLE
WHERE Column LIKE 'value%';

这是因为未将逻辑应用于列且顺序未更改。如果值为'%value%',那么该值也是非SARGable的。

在应用添加(或减去)您要查找的内容的逻辑时,您始终希望将其应用于文字值(或函数,例如GETDATE()`)。例如,这些表达式之一是SARGable,另一个不是:

Column + 1  = @Variable --non-SARGable
Column = @Variable - 1 --SARGable

同样适用于DATEADD

@DateVariable BETWEEN DateColumn AND DATEADD(DAY, 30,DateColumn) --non-SARGable
DateColumn BETWEEN DATEADD(DAY, -30, @DateVariable) AND @DateVariable --SARGable

很少更改数据类型(而不是date)会使查询保持可保存状态。 CONVERT(date,varchardate,112)将不会被保存,即使该列的顺序没有变化。但是,将decimal转换为int的结果与将datetime转换为date的结果相同,并且保持了可保存性:

CREATE TABLE testtab (n decimal(2,1) PRIMARY KEY CLUSTERED);
INSERT INTO testtab
VALUES(0.1),
      (0.3),
      (1.1),
      (1.7),
      (2.4);
GO

SELECT n
FROM testtab
WHERE CONVERT(int,n) = 2;
GO    

DROP TABLE testtab;

enter image description here

希望这能给您足够的帮助,但是请让我问您是否要我进一步添加任何内容。