Использование индексов при сравнении даты и времени - PullRequest
1 голос
/ 15 марта 2019

У меня есть две таблицы, каждая из которых содержит миллионы строк данных.

tbl_one:
purchasedtm DATETIME,
userid      INT,
totalcost   INT

tbl_two:
id          BIGINT,
eventdtm    DATETIME,
anothercol  INT

Первая таблица имеет кластерный индекс по первым двум столбцам: CLUSTERED INDEX tbl_one_idx ON(purchasedtm, userid)

Второй имеет первичный ключ в столбце идентификатора, а также некластеризованный индекс в столбце eventdtm.

Я хочу выполнить запрос, который ищет строки, в которых purchasedtm и eventdtm находятся в один и тот же день.

Первоначально я написал свой запрос как:

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 голосов
/ 22 марта 2019

Литтл.поздно после комментирования, но ...

Как обсуждалось в комментариях, такой код, как CAST(DateTimeColumn AS date), на самом деле SARGable.Роб Фарли опубликовал статью о некоторых функциональных возможностях SARGable и non-SARGable здесь , однако в любом случае я расскажу несколько вещей.

Во-первых, применение функции к столбцу приведет кобычно делает ваш запрос не SARGable, особенно если он меняет порядок значений или порядок их значений не имеет смысла.Возьмем что-то вроде:

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

Порядок значений в столбце здесь совершенно бесполезен, так как мы фокусируемся на символах справа.К сожалению, как Роб также обсуждает:

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

Это также не SARGable.Однако как насчет следующего?

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

Это так, поскольку логика не применяется к столбцу и порядок не изменяется.Если значение wehre '%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

Изменение типа данных (кроме * 1027).*) редко будет держать запрос SARGable.CONVERT(date,varchardate,112) не будет SARGable, даже если порядок столбцов не изменился.Однако преобразование decimal в int привело к тому же результату, что и преобразование datetime в date, и сохранило SARGability:

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

Надеюсь, это даст вам достаточно для продолжения, но Пелас спросит, хотите ли вы, чтобы я добавил что-нибудь еще.

0 голосов
/ 15 марта 2019

Можно создать постоянный вычисляемый столбец в обеих таблицах, который содержит только часть даты:

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 может не использовать индекс.

Индексированный столбец даты приведет к сравнению на равенство и без преобразования.

...