วันที่ส่งเป็นเรื่องง่าย แต่เป็นความคิดที่ดีใช่ไหม


47

ใน SQL Server 2008 มีการเพิ่มประเภทข้อมูลวันที่

การคัดเลือกdatetimeคอลัมน์เพื่อให้dateสามารถขายได้และสามารถใช้ดัชนีใน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 สร้าง SQL where cast(date_column as date) = 'value'เมื่อนำเสนอด้วย C # where obj.date_column.Date == date_variableคล้ายกับ
GSerg

6
นั่นเป็นรายการเชื่อมต่อที่ยอดเยี่ยม :)
Rob Farley

1
ไซต์ Connect ถูกลบและ Sargable ใน Wikipedia
Ivanzinho

คำตอบ:


59

กลไกที่อยู่เบื้องหลัง sargability หล่อวันที่เรียกว่าแบบไดนามิกแสวงหา

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แถวทั้งหมด

ข้อเสียอีกประการหนึ่งของการใช้งานก็คือการประมาณค่า cardinality อาจไม่แม่นยำเท่ากับแบบสอบถามแบบดั้งเดิม สิ่งนี้สามารถเห็นได้ในเวอร์ชันที่แก้ไขของ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 และแผนการที่มีการค้นหา

สำหรับกรณีที่ตารางมีมากกว่าหนึ่งมูลค่าประมาณแถวที่ดูเหมือนว่าจะเป็นเช่นเดียวกับถ้าแบบสอบถามกำลังมองหาว่าDISTINCT20130101 00:00:00

หากฮิสโตแกรมสถิติเกิดขึ้นมีขั้นตอนในตอน2013-01-01 00:00:00.000นั้นการประมาณจะขึ้นอยู่กับEQ_ROWS(เช่นไม่คำนึงถึงเวลาอื่นในวันนั้น) มิฉะนั้นหากไม่มีขั้นตอนใด ๆ ดูเหมือนว่าจะใช้AVG_RANGE_ROWSจากขั้นตอนโดยรอบ

เนื่องจากdatetimeมีความแม่นยำประมาณ 3 มิลลิวินาทีในหลาย ๆ ระบบจะมีค่าซ้ำกันน้อยมากและจำนวนนี้จะเป็น 1


1
สวัสดีมาร์ตินคุณสามารถเพิ่มTL;DRส่วนที่มีสัญลักษณ์แสดงหัวข้อย่อยไม่กี่กรณีที่มีกรณีแตกต่างกันได้หรือไม่ไม่ว่าในกรณีนี้นักแสดงถึงความคิดที่ดีหรือไม่?
TT

6
@TT ฉันคิดว่าประเด็นคือมันไม่ใช่ความคิดที่ดี ทำไมคุณถึงต้องการใช้วิธีการที่ต้องการแผ่นชีท?
Aaron Bertrand

10

ฉันรู้ว่านี่เป็นคำตอบที่ยอดเยี่ยมอันยาวนานจากมาร์ติน แต่ฉันต้องการที่จะเพิ่มการเปลี่ยนแปลงพฤติกรรมบางอย่างที่นี่ใน SQL Server เวอร์ชันใหม่กว่า ดูเหมือนว่าจะผ่านการทดสอบจนถึงปี 2008 แล้วเท่านั้น

ด้วยคำแนะนำการใช้งานใหม่ที่ทำให้การประเมินเวลาของการเดินทางมีความเป็นไปได้เราจะเห็นว่าเมื่อสิ่งต่าง ๆ เปลี่ยนไป

ใช้การตั้งค่าเดียวกันกับใน 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 

แผนการทั้งหมดของเหล่านี้จะอยู่ที่นี่ ระดับความเข้ากันได้ 100 และ 110 ทั้งคู่ให้แผนการค้นหาคีย์ แต่เริ่มต้นด้วยระดับความเข้ากันได้ 120 เราเริ่มได้รับแผนการสแกนเดียวกันโดยประมาณ 100 แถว นี่เป็นจริงถึงระดับที่เข้ากันได้ 140

ถั่ว

ถั่ว

ถั่ว

การประเมินความ>= '20130101', < '20130102'เป็นหัวใจของแผนยังคงอยู่ที่ 100 ซึ่งคาดว่าจะได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.