ISNULL () yerine yalnızca değişmez değerleri kullanan bir WHERE yan tümcesinde değiştirmenin farklı yolları nelerdir?


55

Bunun ne hakkında olmadığı:

Bu, kullanıcı girişini kabul eden veya değişkenleri kullanan tüm soruları yakalama ile ilgili bir soru değildir .

Bu, kesinlikle bir maddeye göre karşılaştırma yapmak için kanarya değeri olan değerleri değiştirmek ISNULL()için WHEREyan tümce maddede kullanılan sorgular NULLve bu sorguları SQL Server'da SARGable olacak şekilde yeniden yazmak için farklı yöntemlerle ilgilidir .

Neden orada oturmuyorsun?

Örnek sorgumuz, SQL Server 2016'daki Yığın Taşması veritabanının yerel bir kopyasına karşı ve NULLyaşları veya yaşları <18 olan kullanıcıları arıyor .

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

Sorgu planı, oldukça düşünceli, kümelenmemiş bir dizinin Taranmasını gösterir.

Fındık

Tarama operatörü (her kokuşmuş satırın tamamını okuduğumuzu gösteriyor) (SQL Server'ın daha yeni sürümlerinde gerçek yürütme planı XML'ye yapılan eklemeler sayesinde).

Fındık

Genel olarak, 9157 okur ve CPU süresinin yaklaşık yarısını kullanırız:

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 485 ms,  elapsed time = 483 ms.

Soru: Daha verimli ve hatta SARGable yapmak için bu sorguyu yeniden yazma yolları nelerdir?

Başka önerilerde bulunmaktan çekinmeyin. Benim cevap mutlaka sanmıyorum cevap ve daha iyi olabilir alternatifleri ile gelip orada kadar akıllı insanlar var.

Kendi bilgisayarınızda oynamak istiyorsanız , SO veritabanını indirmek için buraya gidin .

Teşekkürler!

Yanıtlar:


57

Cevap bölümü

Bunu farklı T-SQL yapıları kullanarak yeniden yazmak için çeşitli yollar vardır. Avantaj ve dezavantajlara bakacağız ve aşağıda genel bir karşılaştırma yapacağız.

İlk önce : kullanmaOR

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

Kullanmak OR, bize ihtiyacımız olan satır sayısını tam olarak okuyan daha verimli bir Arama planı verir, ancak teknik dünyanın a whole mess of malarkeysorgu planına ne dediğini ekler .

Fındık

Ayrıca, Seek'ın iki kez yürütüldüğünü ve bunun grafiksel operatörden gerçekten daha açık olması gerektiğini de unutmayın:

Fındık

Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 469 ms,  elapsed time = 473 ms.

İkincisi : UNION ALL Sorgulamamızla türetilmiş tabloları kullanmak da bu şekilde yeniden yazılabilir.

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Bu, daha az malarkey ve aynı şekilde endeksin kaç defa arandığı (aranan?) Konusunda daha belirgin bir dürüstlük derecesiyle aynı plan türünü verir.

Fındık

ORSorgu ile aynı miktarda okur (8233) , ancak 100ms CPU zaman aşımına uğradı.

CPU time = 313 ms,  elapsed time = 315 ms.

Bununla birlikte, burada gerçekten dikkatli olmalısınız, çünkü bu plan paralel gitmeye çalışırsa, iki ayrı COUNTişlem serileştirilecektir, çünkü her biri bir küresel skaler küme olarak kabul edilir. Bayrak 8649'u kullanarak paralel bir planı zorlarsak, sorun ortaya çıkar.

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);

Fındık

Sorgumuzu biraz değiştirerek bu önlenebilir.

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Şimdi bir Seek gerçekleştiren her iki düğüm de birleştirme operatörüne ulaşana kadar tamamen paraleldir.

Fındık

Neye mal olursa olsun, tamamen paralel versiyonun iyi bir faydası var. Yaklaşık 100 okuma maliyeti ve yaklaşık 90 ms ilave CPU zamanı pahasına, geçen süre 93 ms'ye düşer.

Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 500 ms,  elapsed time = 93 ms.

Peki ya CROSS APPLY? Hiçbir sihir olmadan cevap tamamlanamaz CROSS APPLY!

Ne yazık ki, biz daha fazla sorunla karşılaşıyoruz COUNT.

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Bu plan korkunç. Bu, Aziz Patrick Günü'ne en son geldiğiniz zaman planladığınız plan. Güzel paralel olmasına rağmen, bazı nedenlerden dolayı PK / CX'i tarıyor. Ew. Planın 2198 sorgu parası var.

Fındık

Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 29532 ms,  elapsed time = 5828 ms.

Bu garip bir seçimdir, çünkü kümelenmemiş dizini kullanmaya zorlarsak, maliyet önemli ölçüde 1798 sorgu parasına düşer.

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Hey, istiyor! Oradan kontrol et. Ayrıca, sihirle, CROSS APPLYçoğunlukla tamamen paralel bir plana sahip olmak için saçma bir şey yapmamıza gerek olmadığını unutmayın .

Fındık

Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 27625 ms,  elapsed time = 4909 ms.

Çapraz başvuru, COUNToradaki eşyalar olmadan daha iyi sonuç verir .

SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY 
(
    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Plan iyi görünüyor, ancak okur ve CPU bir gelişme değil.

Fındık

Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 4844 ms,  elapsed time = 863 ms.

Haçı tekrar yazmak, türetilmiş bir birleşim olmak için geçerlidir, aynı şeyde aynıdır. Sorgu planını ve istatistik bilgilerini yeniden göndermeyeceğim - gerçekten değişmediler.

SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN 
(
    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x ON x.Id = u.Id;

İlişkisel Cebir : Kapsamlı olmak ve Joe Celko'nun hayallerime dokunmasını engellemek için en azından garip ilişkisel şeyler denemeliyiz. İşte hiçbir şey yok!

Bir girişim INTERSECT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   INTERSECT
                   SELECT u.Age WHERE u.Age IS NOT NULL );

Fındık

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 1094 ms,  elapsed time = 1090 ms.

Ve işte bir girişim EXCEPT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   EXCEPT
                   SELECT u.Age WHERE u.Age IS NULL);

Fındık

Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 2126 ms,  elapsed time = 376 ms.

Bunları yazmanın başka yolları da olabilir, ama bunu belki de kullanan EXCEPTve INTERSECTbenden daha sık kullanan insanlara bırakacağım .

Eğer gerçekten sadece bir sayı gerekiyorsa kullandığım COUNTsteno biraz olarak benim sorgularda (okuyun: Ben daha karmaşık senaryolar bazen ile gelip çok tembelim). Sadece bir sayıma ihtiyacınız varsa, CASEhemen hemen aynı şeyi yapmak için bir ifade kullanabilirsiniz .

SELECT SUM(CASE WHEN u.Age < 18 THEN 1
                WHEN u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

Her ikisi de aynı plana sahip ve aynı CPU'ya ve okuma özelliklerine sahip.

Fındık

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 719 ms,  elapsed time = 719 ms.

Kazanan? Testlerimde, türetilmiş bir tablonun üzerinde SUM olan zorunlu paralel plan en iyi sonucu verdi. Ve evet, bu sorguların birçoğu her iki öngörüyü de hesaba katan birkaç filtre uygulanmış dizin ekleyerek yardımcı olabilirdi, ancak bazı deneyleri başkalarına bırakmak istedim.

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Teşekkürler!


1
NOT EXISTS ( INTERSECT / EXCEPT )Sorgular olmadan çalışabilir INTERSECT / EXCEPTpay: WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );Başka bir şekilde - kullanır EXCEPT: SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ; (kullanıcı kimliği PK ya da herhangi bir özel olmayan boş kolonu (ler)) elde edilmektedir.
ypercubeᵀᴹ

Bu test edildi mi? SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;Üzgünüm, test ettiğiniz milyon sürümü kaçırdıysam!
ypercubeᵀᴹ

@ ypercubeᵀᴹ bunun planı . Biraz farklı, ama UNION ALLplanlarla benzer özelliklere sahip (360ms CPU, 11k okur).
Erik Darling

Hey Erik, sadece sql dünyasında dolaşıyordu ve seni rahatsız etmek için "hesaplanmış sütun" demek için fırladı. <3
pota,

17

110 GB veri tabanını tek bir masa için geri yüklemek için oyun değildi, bu yüzden kendi verilerimi yarattım . Yaş dağılımları, Yığın Taşması'ndakilerle eşleşmelidir, ancak açıkça tablonun kendisi uyuşmaz. Bunun çok fazla bir sorun olduğunu sanmıyorum çünkü sorgular yine de endeksleri etkileyecektir. SQL Server 2016 SP1 ile 4 CPU'lu bir bilgisayarda test ediyorum. Unutulmaması gereken bir şey, bu işlemi hızla bitiren sorgular için gerçek uygulama planının dahil edilmemesi önemlidir. Bu işleri biraz yavaşlatabilir.

Erik'in mükemmel cevabındaki bazı çözümlerden geçerek başladım. Bunun için:

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

10'dan fazla denemeden sys.dm_exec_sessions'dan şu sonuçları aldım (sorgu doğal olarak benim için paralel gitti):

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

Erik için daha iyi çalışan sorgu makinemde daha da kötüleşti:

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

10 denemenin sonuçları:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

Neden bu kadar kötü olduğunu hemen anlayamıyorum, ancak sorgu planındaki hemen hemen her operatörü neden paralel gitmeye zorlamak istediğimiz belli değil. Orijinal planda tüm satırları bulan seri bir bölgemiz var AGE < 18. Sadece birkaç bin satır var. Makinemde, sorgunun bu kısmı için 9 mantıksal okuma ve bildirilen CPU zamanı ve geçen zamanın 9 ms'si alıyorum. Genel toplama için satırlar için bir seri bölge de var AGE IS NULLancak bu DOP başına sadece bir satır işliyor. Makinemde bu sadece dört satır.

Benim geleneğim, sorgunun satırları NULLfor ile bulduğu kısmı optimize etmenin en önemli Agenedeni , çünkü bu satırların milyonlarca tanesi var. Verileri kapsayan, sayfadaki basit bir sıkıştırılmış sayfadan daha az sayfa içeren bir dizin oluşturamadım. Her satır için asgari bir indeks büyüklüğü olduğunu ya da denediğim püf noktalarıyla indeks alanının çoğundan kaçınılamayacağını varsayıyorum. Dolayısıyla, verileri elde etmek için aynı sayıda mantıksal okuma ile sıkışırsak, daha hızlı hale getirmenin tek yolu sorguyu daha paralel hale getirmektir, ancak bunun Erik'in TF'yi kullanan sorgusundan farklı bir şekilde yapılması gerekir. 8649. Yukarıdaki sorguda CPU zamanı için geçen zamanın oldukça iyi olduğu 3.62 oranına sahibiz. İdeal, makinemde 4.0 oranı olurdu.

Mümkün olan bir iyileştirme alanı, işi iş parçacığı arasında daha eşit bir şekilde bölmektir. Aşağıdaki ekran görüntüsünde CPU'larımdan birinin biraz ara vermeye karar verdiğini görüyoruz:

tembel iplik

Dizin tarama, paralel olarak uygulanabilecek birkaç operatörden biridir ve satırların iş parçacıklarına nasıl dağıtıldığı hakkında hiçbir şey yapamayız. Bunun için de bir şans unsuru var ama tutarlı bir şekilde çalışan bir ip gördüm. Bunun üstesinden gelmek için bir yol paralellik zor yoldan yapmaktır: iç içe bir döngü birleşiminin iç kısmında. Yuvalanmış bir döngünün iç kısmındaki her şey seri şekilde uygulanır, ancak birçok seri iş parçacığı aynı anda çalışabilir. Olumlu bir paralel dağıtım yöntemi (yuvarlak robin gibi) elde ettiğimiz sürece, her bir dişe kaç tane satır gönderildiğini tam olarak kontrol edebiliriz.

Sorguları DOP 4 ile çalıştırıyorum, bu yüzden NULLtablodaki satırları dört kovaya eşit şekilde bölmem gerekiyor . Bunu yapmanın bir yolu, hesaplanan sütunlarda bir demet dizin oluşturmaktır:

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

Dört ayrı dizinin neden bir dizinden biraz daha hızlı olduğundan emin değilim, ancak testimde bulduğum şey bu.

Paralel olarak iç içe geçmiş bir döngü planı elde etmek için belgelenmemiş izleme bayrağı 8649'u kullanacağım . Ayrıca, optimize ediciyi gereğinden fazla satır işlememesini teşvik etmek için kodu biraz garip bir şekilde yazacağım. Aşağıda iyi sonuç veren görünen bir uygulama bulunmaktadır:

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

On denemenin sonuçları:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

Bu sorguda 3.85 oranındaki bir CPU / geçen zaman oranına sahibiz! Çalışma süresinden 17 ms traş olduk ve bunu yapmak için yalnızca 4 hesaplanmış sütun ve dizin aldı! Her iş parçacığı aynı sayıda satıra çok yakın işlemektedir, çünkü her bir dizin aynı sayıda satıra çok yakındır ve her iş parçacığı yalnızca bir dizini tarar:

iyi bölünmüş iş

Son bir notta, kolay düğmeye basabilir ve Agesütuna kümelenmemiş bir CCI ekleyebiliriz :

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

Aşağıdaki sorgu makinemde 3 ms ile bitiyor:

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

Bunu yenmek zor olacak.


7

Yığın Taşması veritabanının yerel bir kopyasına sahip olmama rağmen, birkaç sorgu deneyebildim. Benim düşüncem, bir sistem katalog görünümünden bir dizi kullanıcıyı (doğrudan alttaki tablodan bir dizi satırı elde etmenin aksine) elde etmekti. Öyleyse Erik'in ölçütlerine uyan (belki de uymayan) bir dizi satır alın ve basit bir matematik yapın.

Sorguları test etmek için Yığın Değişimi Veri Gezgini'ni (birlikte SET STATISTICS TIME ON;ve SET STATISTICS IO ON;) birlikte kullandım . Referans için, bazı sorgular ve CPU / IO istatistikleri:

QUERY 1

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

SQL Server Yürütme Süreleri: CPU süresi = 0 ms, geçen süre = 0 ms. (1 satır döndürüldü)

Tablo 'Kullanıcılar'. Tarama sayısı 17, mantıksal 201567 okur, fiziksel 0 okur, ileri okuma 2740, lob mantık 0 okur, lob fiziksel ok 0, okurken lob 0 okur.

SQL Server Yürütme Süreleri: CPU süresi = 1829 ms, geçen süre = 296 ms.

QUERY 2

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

SQL Server Yürütme Süreleri: CPU süresi = 0 ms, geçen süre = 0 ms. (1 satır döndürüldü)

Tablo 'Kullanıcılar'. Tarama sayısı 17, mantıksal 201567 okur, fiziksel 0 okur, ileri okuma 0, lob mantık 0 okur, fiziksel ok 0, okurken lo 0 okur.

SQL Server Yürütme Süreleri: CPU süresi = 2500 ms, geçen süre = 147 ms.

QUERY 3

--Erik's derived tables/UNION ALL query.
SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

SQL Server Yürütme Süreleri: CPU süresi = 0 ms, geçen süre = 0 ms. (1 satır döndürüldü)

Tablo 'Kullanıcılar'. Tarama sayımı 34, mantıksal 403134, fiziksel 0 okur, ileri okuma 0, lobi mantık 0 okur, lobi fiziksel okur 0, lobi okundu 0 okur.

SQL Server Yürütme Süreleri: CPU süresi = 3156 ms, geçen süre = 215 ms.

1. Deneme

Bu, Erik'in burada listelediğim tüm sorgulardan daha yavaştı ... en azından geçen zaman açısından.

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SQL Server Yürütme Süreleri: CPU süresi = 0 ms, geçen süre = 0 ms. (1 satır döndürüldü)

'Çalışma Masası' tablosu. Tarama sayısı 0, mantıksal 0, fiziksel okuma 0, ileri okuma 0, lob mantıksal 0, lob fiziksel okuma 0, lob okuma-öncesi 0 okur. Tablo 'sysrowsets'. Tarama sayısı 2, mantıksal 10 okur, fiziksel 0 okur, ileri okuma 0, lojik mantık 0 okur, fiziksel ok 0, okurum lo 0 okur. Tarama sayısı 1, mantıksal 4 okur, fiziksel 0 okur, ileri doğru okuma 0, lob mantık 0 okur, fiziksel ok 0, okundu lob 0 okur. Tablo 'Kullanıcılar'. Tarama sayısı 1, mantıksal 201567 okur, fiziksel 0 okur, ileri okuma 0, lob mantık 0 okur, fiziksel ok 0, okurken lob 0 okur.

SQL Server Yürütme Süreleri: CPU süresi = 593 ms, geçen süre = 598 ms.

2. Deneme

Burada bir değişkeni toplam kullanıcı sayısını (alt sorgu yerine) saklamak için seçtim. Tarama sayısı, ilk denemeye kıyasla 1'den 17'ye yükseldi. Mantıksal okuma aynı kaldı. Ancak, geçen zaman önemli ölçüde azaldı.

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

SQL Server Yürütme Süreleri: CPU süresi = 0 ms, geçen süre = 0 ms. 'Çalışma Masası' tablosu. Tarama sayısı 0, mantıksal 0, fiziksel okuma 0, ileri okuma 0, lob mantıksal 0, lob fiziksel okuma 0, lob okuma-öncesi 0 okur. Tablo 'sysrowsets'. Tarama sayısı 2, mantıksal 10 okur, fiziksel 0 okur, ileri okuma 0, lojik mantık 0 okur, fiziksel ok 0, okurum lo 0 okur. Tarama sayısı 1, mantıksal 4 okur, fiziksel 0 okur, ileri okuma 0, lob mantık 0 okur, fiziksel ok 0, okundu lob 0 okur.

SQL Server Yürütme Süreleri: CPU süresi = 0 ms, geçen süre = 1 ms. (1 satır döndürüldü)

Tablo 'Kullanıcılar'. Tarama sayısı 17, mantıksal 201567 okur, fiziksel 0 okur, ileri okuma 0, lob mantık 0 okur, fiziksel ok 0, okurken lo 0 okur.

SQL Server Yürütme Süresi: CPU süresi = 1471 ms, geçen süre = 98 ms.

Diğer Notlar: DBCC TRACEON'a, aşağıda belirtilen Stack Exchange Data Explorer'da izin verilmez:

'STACKEXCHANGE \ svc_sede' kullanıcısı DBCC TRACEON'u çalıştırma iznine sahip değil.


1
Muhtemelen benimle aynı endekslere sahip değillerdir, dolayısıyla farklılıklar. Ve kim bilir? Belki de ev sunucum daha iyi bir donanımdadır;) Harika cevap olsa!
Erik Darling,

ilk denemeniz için aşağıdaki sorguyu kullanmanız gerekirdi (sys.objects-overhead'in çoğunu kullandığı için çok daha hızlı olacak): SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
Thomas Franz

Not: Bellek İçi indekslerin (NONCLUSTERED HASH) ortak bir yığın / kümelenmiş indeksin sahip olacağı gibi bir indeks kimliği = 0/1 olmadığının farkında olun)
Thomas Franz

1

Değişkenleri kullan?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

Yorum başına değişkenleri atlayabilirsiniz

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
Ayrıca:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
CPU ve IO kontrol ederken bunu denemek isteyebilirsiniz. İpucu: Erik'in cevaplarından biriyle aynı.
Brent Ozar

0

İyi kullanarak SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

Bu sadece aklıma gelen bir şeydir. Sadece https://data.stackexchange.com

Ancak @ blitz_erik kadar verimli değil


0

Önemsiz bir çözüm sayımı (*) - sayımı (yaş> = 18) hesaplamaktır:

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

Veya:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

Sonuçlar burada

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.