Bugüne kadar oyunculara sarhoş ama iyi bir fikir mi?


47

SQL Server 2008'de tarih veri türü eklendi.

Bir datetimesütunun yayınlanması date, parçalanabilir ve datetimesütunda bir dizin kullanabilir .

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

Sahip olduğunuz diğer seçenek, bunun yerine bir aralık kullanmaktır.

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

Bu sorgular eşit derecede iyi mi, yoksa biri diğerine mi tercih edilmeli?


4
İcra planı ne diyor?
a_horse_with_no_name

3
LINQ2SQL'in where cast(date_column as date) = 'value'C # ile benzer şekilde sunulduğunda SQL oluşturduğunu farketmeye yardımcı olamam where obj.date_column.Date == date_variable.
GSerg

6
Bu mükemmel bir Connect ürünü. :)
Rob Farley,

1
Connect sitesi Wikipedia'da Sargable'ın yanısıra kaldırıldı
Ivanzinho

Yanıtlar:


59

Bugüne kadar dökümün sertliğinin arkasındaki mekanizmaya dinamik arama denir .

SQL Server GetRangeThroughConvert, aralığın başlangıcını ve sonunu almak için dahili bir işlevi çağırır .

Biraz da şaşırtıcı bir şekilde, bu sizin gerçek değerlerinizle aynı aralıkta değil .

Sayfa başına satır ve günde 1440 satır içeren bir tablo oluşturma

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 

Sonra çalışan

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'; 

İlk sorgu 1443okudu ve ikincisi 2883bütün bir ek gün okuyor, ardından artık bir yükleme karşı atıyor.

Plan arama belirleyicisinin olduğunu gösterir

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

Bunun yerine >= '20130101' ... < '20130102'okur > '20121231' ... < '20130102'sonra tüm 2012-12-31satırları atar .

Buna güvenmenin bir başka dezavantajı, kardinalite tahminlerinin geleneksel aralık sorgusu kadar doğru olmamasıdır. Bu, SQL Fiddle’ınızın değiştirilmiş bir sürümünde görülebilir .

Tablodaki 100 satırın tümü artık yüklemeyle eşleşiyor (aynı gün içinde 1 dakika arayla tarihlenen tarihler).

İkinci (aralık) sorgu, 100'ün eşleşeceğini ve kümelenmiş bir dizin taraması kullanacağını doğru olarak tahmin eder. CAST( AS DATE)Sorgu yanlış tek satır maç olacağını tahmin eder ve anahtar aramalarını bir plan üretir.

İstatistikler tamamen göz ardı edilmiyor. Tablodaki tüm satırlar aynıysa datetimeve yükleme (örneğin 20130101 00:00:00veya 20130101 01:00:00) eşleşiyorsa , plan, tahmini 31.6228 satır içeren kümelenmiş bir dizin taramasını gösterir.

100 ^ 0.75 = 31.6228

Öyleyse, bu durumda tahmin, buradaki formülden elde edilmiştir .

Tablodaki tüm satırlar aynıysa datetimeve yükleme (örn. 20130102 01:00:00) Uymuyorsa, o zaman 1 tahmini satır sayısına ve aramaları içeren plana geri döner.

Tablonun birden fazla DISTINCTdeğere sahip olduğu durumlar için , tahmin edilen satırlar, sorgu tam olarak aranıyormuş gibi görünüyor 20130101 00:00:00.

İstatistik histogramının bir adımı 2013-01-01 00:00:00.000olması durumunda, tahmin esas alınacaktır EQ_ROWS(yani o tarihteki diğer zamanları hesaba katmamak). Aksi takdirde, hiçbir adım yoksa, AVG_RANGE_ROWSçevresindeki basamaklardan kullanıyormuş gibi görünür .

Gibi datetimebirçok sistemlerde yaklaşık 3ms bir hassasiyete sahiptir orada çok az gerçek yinelenen değerleri olacak ve bu sayı 1 olacaktır.


1
Merhaba Martin, TL;DRfarklı durumlarda birkaç mermi noktası olan bir parça ekleyebilir misiniz , bu durumda, şu ana kadarki durum iyi bir fikir midir, değil mi?
TT.

6
@TT. Bence asıl mesele bunun iyi bir fikir olmadığı. Neden bir kopya kağıdı gerektiren bir yöntem kullanmak istiyorsunuz?
Aaron Bertrand

10

Bunun Martin'den uzun zamandır devam eden bir Great Answer® olduğunu biliyorum, ancak davranıştaki bazı değişiklikleri SQL Server'ın daha yeni sürümlerinde eklemek istedim. Bu sadece 2008R2'ye kadar test edilmiş gibi görünüyor.

Kardinalite tahmini zaman yolculuğunu mümkün kılan yeni KULLANIM İPUÇLARI ile, işlerin ne zaman değiştiğini görebiliriz.

SQL Fiddle ile aynı ayarları kullanmak.

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;

Bunun gibi farklı seviyeleri test edebiliriz:

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 

Bunların hepsi için planlar burada . Uyum seviyeleri 100 ve 110 her ikisi de ana arama planını verir, ancak uygunluk seviyesi 120 ile başlayarak, aynı tarama planını 100 sıra tahminiyle almaya başlarız. Bu, 140 seviyesine kadar geçerlidir.

FINDIK

FINDIK

FINDIK

>= '20130101', < '20130102'Planlar için kardinalite tahmini , beklenen 100'ünde kalmaya devam ediyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.