지금까지 캐스트는 다루기 쉽지만 좋은 생각입니까?


47

SQL Server 2008에서 날짜 데이터 유형이 추가되었습니다.

캐스팅 datetime에 열 것은 date입니다 스 SARGable 과에 인덱스를 사용할 수있는 datetime열입니다.

select *
from T
where cast(DateTimeCol as date) = '20130101';

다른 옵션은 범위를 대신 사용하는 것입니다.

select *
from T
where DateTimeCol >= '20130101' and
      DateTimeCol < '20130102'

이 쿼리들이 똑같이 좋습니까?


4
실행 계획은 무엇을 말합니까?
a_horse_with_no_name

3
LINQ2SQL where cast(date_column as date) = 'value'과 C #이 표시되면 SQL을 생성한다는 사실을 알 수 없습니다 where obj.date_column.Date == date_variable.
GSerg

6
훌륭한 Connect 항목입니다. :)
Rob Farley

1
Connect 사이트는 위키피디아에서 Sargable과 함께 제거되었습니다.
Ivanzinho

답변:


59

현재까지 캐스팅의 sargability 뒤에있는 메커니즘을 dynamic seek 라고 합니다.

SQL Server는 내부 함수 GetRangeThroughConvert를 호출 하여 범위의 시작과 끝을 얻습니다.

놀랍게도 이것은 리터럴 값과 같은 범위 가 아닙니다 .

페이지 당 행과 하루 1440 행으로 테이블 작성

CREATE TABLE T
  (
     DateTimeCol DATETIME PRIMARY KEY,
     Filler      CHAR(8000) DEFAULT 'X'
  );

WITH Nums(Num)
     AS (SELECT number
         FROM   spt_values
         WHERE  type = 'P'
                AND number BETWEEN 1 AND 1440),
     Dates(Date)
     AS (SELECT {d '2012-12-30'} UNION ALL
         SELECT {d '2012-12-31'} UNION ALL
         SELECT {d '2013-01-01'} UNION ALL
         SELECT {d '2013-01-02'} UNION ALL
         SELECT {d '2013-01-03'})
INSERT INTO T
            (DateTimeCol)
SELECT DISTINCT DATEADD(MINUTE, Num, Date)
FROM   Nums,
       Dates 

그런 다음 실행

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT *
FROM   T
WHERE  DateTimeCol >= '20130101'
       AND DateTimeCol < '20130102'

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'; 

첫 번째 쿼리는 1443읽기를 수행하고 두 번째 쿼리는 2883전체 추가 날짜를 읽은 다음 잔여 술어에 대해 버립니다.

계획은 탐색 술어가

Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]), 
               End: DateTimeCol < Scalar Operator([Expr1007])

따라서 대신 모든 행을 >= '20130101' ... < '20130102'읽고 > '20121231' ... < '20130102'버립니다 2012-12-31.

이에 의존하는 또 다른 단점은 카디널리티 추정이 기존 범위 쿼리보다 정확하지 않을 수 있다는 것입니다. 이것은 수정 된 버전의 SQL Fiddle 에서 볼 수 있습니다 .

테이블의 100 개 행이 모두 술어와 일치합니다 (날짜 시간이 같은 날 1 분 간격으로).

두 번째 (범위) 쿼리는 100이 일치하고 클러스터형 인덱스 스캔을 사용할 것으로 정확하게 추정합니다. CAST( AS DATE)쿼리는 잘못 하나의 행이 일치 할 것으로 추정 및 키 조회와 계획을 생성합니다.

통계는 완전히 무시되지 않습니다. 테이블의 모든 행이 동일 datetime하고 술어와 일치하는 경우 (예 : 20130101 00:00:00또는 20130101 01:00:00) 계획에 31.6228 행이있는 클러스터 된 인덱스 스캔이 표시됩니다.

100 ^ 0.75 = 31.6228

따라서이 경우 추정치는 여기 공식에서 파생 된 것으로 보입니다 .

테이블의 모든 행이 동일 datetime하고 술어와 일치하지 않으면 (예 :) 20130102 01:00:00예상 행 수 1 및 계획이있는 계획으로 돌아갑니다.

테이블에 둘 이상의 DISTINCT값이있는 경우 예상 행은 쿼리가 정확하게 찾은 것과 같은 것으로 보입니다 20130101 00:00:00.

통계 히스토그램에 단계가있는 2013-01-01 00:00:00.000경우 추정치는 EQ_ROWS(즉, 해당 날짜의 다른 시간은 고려하지 않음)을 기준으로합니다. 그렇지 않으면 단계가 없으면 AVG_RANGE_ROWS주변 단계에서를 사용하는 것처럼 보입니다 .

으로 datetime많은 시스템에서 약 3MS의 정밀도를 가지고이 거의 실제 중복 값이 될 것이며,이 숫자는 1이됩니다.


1
안녕하세요 마틴, 당신 TL;DR은 몇 가지 글 머리 기호 로 부품 을 추가 할 수 있습니까?
TT.

6
@TT. 요점은 그것이 좋은 생각이 아니라고 생각합니다. 치트 시트가 필요한 방법을 왜 사용 하시겠습니까?
Aaron Bertrand

10

나는 이것이 Martin의 오랜 Great Answer®을 가지고 있음을 알고 있지만 최신 버전의 SQL Server에서 동작에 몇 가지 변경 사항을 추가하고 싶었습니다. 이것은 2008R2까지만 테스트 된 것으로 보입니다.

카디널리티 추정 시간 여행을 가능하게하는 새로운 USE 힌트 를 통해 상황이 언제 바뀌는 지 확인할 수 있습니다.

SQL Fiddle에서와 동일한 설정을 사용합니다.

CREATE TABLE T ( ID INT IDENTITY PRIMARY KEY, DateTimeCol DATETIME, Filler CHAR(8000) NULL );

CREATE INDEX IX_T_DateTimeCol ON T ( DateTimeCol );


WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
     E02(N) AS (SELECT 1 FROM E00 a, E00 b),
     E04(N) AS (SELECT 1 FROM E02 a, E02 b),
     E08(N) AS (SELECT 1 FROM E04 a, E04 b),
     Num(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY E08.N) FROM E08)
INSERT INTO T(DateTimeCol)
SELECT TOP 100 DATEADD(MINUTE, Num.N, '20130101')
FROM Num;

다음과 같이 다른 수준을 테스트 할 수 있습니다.

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_100' ));
GO

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_110' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_120' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_130' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140' ));
GO 

이 모든 계획은 여기에 있습니다 . Compat 레벨 100과 110은 모두 주요 조회 계획을 제공하지만 Compat 레벨 120부터 100 행 추정치와 동일한 스캔 계획을 시작합니다. 이것은 compat 레벨 140까지 적용됩니다.

견과류

견과류

견과류

>= '20130101', < '20130102'계획에 대한 카디널리티 추정 은 100으로 유지되며 이는 예상 된 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.