Apa cara berbeda untuk mengganti ISNULL () dalam klausa WHERE yang hanya menggunakan nilai literal?

55

Tentang apa ini:

Ini bukan pertanyaan tentang catch-all queries yang menerima input pengguna atau menggunakan variabel.

Ini hanya tentang kueri ISNULL()yang digunakan dalam WHEREklausa untuk mengganti NULLnilai dengan nilai kenari untuk dibandingkan dengan predikat, dan berbagai cara untuk menulis ulang kueri yang akan SARGable di SQL Server.

Mengapa kamu tidak memiliki kursi di sana?

Contoh kueri kami menentang salinan lokal dari database Stack Overflow pada SQL Server 2016, dan mencari pengguna dengan NULLusia, atau usia <18.

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

Rencana kueri menunjukkan Pindaian indeks nonclustered yang cukup bijaksana.

Gila

Operator pemindaian menunjukkan (terima kasih kepada penambahan pada XML rencana pelaksanaan aktual dalam versi terbaru dari SQL Server) yang kami baca setiap baris stinkin '.

Gila

Secara keseluruhan, kami membaca 9157 dan menggunakan sekitar setengah detik waktu CPU:

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.

Pertanyaannya: Apa cara untuk menulis ulang kueri ini agar lebih efisien, dan mungkin bahkan SARGable?

Jangan ragu untuk menawarkan saran lain. Saya tidak berpikir jawaban saya adalah tentu yang jawabannya, dan ada cukup banyak orang pintar di luar sana untuk datang dengan alternatif yang mungkin lebih baik.

Jika Anda ingin bermain di komputer Anda sendiri, pergilah ke sini untuk mengunduh database SO .

Terima kasih!

Erik Darling
sumber

Jawaban:

57

Bagian jawaban

Ada berbagai cara untuk menulis ulang ini menggunakan konstruksi T-SQL yang berbeda. Kami akan melihat pro dan kontra dan melakukan perbandingan secara keseluruhan di bawah ini.

Pertama : MenggunakanOR

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

Menggunakan ORmemberi kita rencana pencarian yang lebih efisien, yang membaca jumlah baris yang tepat yang kita butuhkan, namun menambahkan apa yang dunia teknis sebut a whole mess of malarkeydengan rencana kueri.

Gila

Perhatikan juga bahwa Seek dieksekusi dua kali di sini, yang seharusnya lebih jelas dari operator grafis:

Gila

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.

Kedua : Menggunakan tabel turunan dengan UNION ALL kueri kami juga dapat ditulis ulang seperti ini

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

Ini menghasilkan jenis rencana yang sama, dengan malarkey jauh lebih sedikit, dan tingkat kejujuran yang lebih jelas tentang berapa kali indeks dicari (dicari?) Menjadi.

Gila

Ia melakukan jumlah pembacaan yang sama (8233) dengan ORkueri, tetapi mencukur waktu istirahat CPU sekitar 100 ms.

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

Namun, Anda harus benar - benar berhati - hati di sini, karena jika rencana ini mencoba berjalan paralel, dua COUNToperasi terpisah akan diserialisasi, karena masing-masing dianggap sebagai agregat skalar global. Jika kita memaksakan rencana paralel menggunakan Trace Flag 8649, masalahnya menjadi jelas.

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

Gila

Ini dapat dihindari dengan sedikit mengubah kueri kami.

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

Sekarang kedua node melakukan Seek sepenuhnya diparalelkan sampai kita menekan operator gabungan.

Gila

Untuk apa nilainya, versi paralel sepenuhnya memiliki beberapa manfaat baik. Dengan biaya sekitar 100 lebih banyak bacaan, dan sekitar 90ms waktu CPU tambahan, waktu yang telah berlalu menyusut menjadi 93ms.

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.

Bagaimana dengan CROSS BERLAKU? Tidak ada jawaban yang lengkap tanpa keajaiban CROSS APPLY!

Sayangnya, kami mengalami lebih banyak masalah dengan 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);

Rencana ini mengerikan. Ini adalah jenis rencana yang Anda miliki ketika Anda muncul terakhir untuk Hari St. Patrick. Meskipun paralel dengan baik, untuk beberapa alasan pemindaian PK / CX. Ew. Paket tersebut memiliki biaya 2.198 dolar permintaan.

Gila

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.

Yang merupakan pilihan aneh, karena jika kita memaksanya untuk menggunakan indeks nonclustered, biaya turun agak signifikan menjadi 1798 dolar permintaan.

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

Hei, cari! Lihat kamu di sana. Juga perhatikan bahwa dengan keajaiban CROSS APPLY, kita tidak perlu melakukan apa pun yang konyol untuk memiliki rencana yang sepenuhnya paralel.

Gila

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.

Silang berlaku tidak berakhir lebih baik tanpa COUNTbarang - barang di sana.

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

Rencananya terlihat bagus, tetapi pembacaan dan CPU bukan perbaikan.

Gila

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.

Menulis ulang tanda silang berlaku untuk hasil gabungan turunan dalam segala hal yang persis sama. Saya tidak akan memposting ulang paket kueri dan info statistik - mereka benar-benar tidak berubah.

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;

Aljabar Relasional : Agar teliti, dan untuk mencegah Joe Celko menghantui mimpiku, setidaknya kita perlu mencoba beberapa hal relasional yang aneh. Ini dia!

Upaya dengan 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 );

Gila

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.

Dan inilah upaya dengan 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);

Gila

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.

Mungkin ada cara lain untuk menulis ini, tetapi saya akan menyerahkannya kepada orang-orang yang mungkin menggunakan EXCEPTdan INTERSECTlebih sering daripada saya.

Jika Anda benar-benar hanya perlu hitungan yang saya gunakan COUNTdalam pertanyaan saya sebagai sedikit singkatan (baca: Saya terlalu malas untuk datang dengan skenario yang lebih terlibat kadang-kadang). Jika Anda hanya perlu menghitung, Anda dapat menggunakan CASEekspresi untuk melakukan hal yang sama.

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

Keduanya mendapatkan paket yang sama dan memiliki karakteristik CPU dan membaca yang sama.

Gila

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.

Pemenang? Dalam pengujian saya, rencana paralel yang dipaksakan dengan SUM atas tabel turunan menunjukkan kinerja terbaik. Dan ya, banyak dari pertanyaan ini dapat dibantu dengan menambahkan beberapa indeks yang disaring untuk menjelaskan kedua predikat, tetapi saya ingin meninggalkan beberapa eksperimen kepada yang lain.

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

Terima kasih!

Erik Darling
sumber
1
The NOT EXISTS ( INTERSECT / EXCEPT )query dapat bekerja tanpa INTERSECT / EXCEPTbagian: WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );Cara lain - yang menggunakan EXCEPT: SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ; (di mana UserID adalah PK atau tidak kolom nol unik (s)).
ypercubeᵀᴹ
Apakah ini diuji? 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) ;Maaf jika saya ketinggalan dalam jutaan versi yang telah Anda uji!
ypercubeᵀᴹ
@ ypercubeᵀᴹ inilah rencananya . Ini sedikit berbeda, tetapi memiliki karakteristik yang mirip dengan UNION ALLrencana (360ms CPU, 11k dibaca).
Erik Darling
Hei Erik, baru saja berkeliaran di dunia sql dan muncul untuk mengatakan "kolom terkomputasi" hanya untuk mengganggu Anda. <3
crucible
17

Saya bukan game untuk mengembalikan database 110 GB hanya untuk satu tabel jadi saya membuat data saya sendiri . Distribusi usia harus cocok dengan apa yang ada di Stack Overflow tetapi jelas tabel itu sendiri tidak akan cocok. Saya tidak berpikir bahwa itu terlalu banyak masalah karena permintaan akan mencapai indeks. Saya sedang menguji pada komputer 4 CPU dengan SQL Server 2016 SP1. Satu hal yang perlu diperhatikan adalah bahwa untuk kueri yang menyelesaikan ini dengan cepat, penting untuk tidak menyertakan rencana eksekusi yang sebenarnya. Itu bisa memperlambat segalanya sedikit.

Saya mulai dengan menelusuri beberapa solusi dalam jawaban Erik yang luar biasa. Untuk yang ini:

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

Saya mendapat hasil berikut dari sys.dm_exec_sessions selama 10 percobaan (kueri secara alami berjalan paralel untuk saya):

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

Permintaan yang bekerja lebih baik untuk Erik sebenarnya berkinerja lebih buruk di mesin saya:

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

Hasil dari 10 percobaan:

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

Saya tidak dapat segera menjelaskan mengapa itu seburuk itu, tetapi tidak jelas mengapa kami ingin memaksa hampir setiap operator dalam rencana kueri untuk paralel. Dalam paket awal kami memiliki zona seri yang menemukan semua baris AGE < 18. Hanya ada beberapa ribu baris. Di mesin saya, saya mendapatkan 9 pembacaan logis untuk bagian dari permintaan dan 9 ms dari waktu CPU yang dilaporkan dan waktu yang berlalu. Ada juga zona serial untuk agregat global untuk baris dengan AGE IS NULLtetapi yang hanya memproses satu baris per DOP. Di mesin saya ini hanya empat baris.

Hasil saya adalah bahwa yang paling penting adalah mengoptimalkan bagian dari kueri yang menemukan baris dengan NULLuntuk Agekarena ada jutaan baris itu. Saya tidak dapat membuat indeks dengan halaman yang lebih sedikit yang mencakup data daripada halaman yang dikompres pada kolom. Saya berasumsi bahwa ada ukuran indeks minimum per baris atau banyak ruang indeks tidak dapat dihindari dengan trik yang saya coba. Jadi jika kita terjebak dengan jumlah pembacaan logis yang sama untuk mendapatkan data maka satu-satunya cara untuk membuatnya lebih cepat adalah dengan membuat kueri lebih paralel, tetapi ini perlu dilakukan dengan cara yang berbeda dari kueri Erik yang menggunakan TF 8649. Dalam kueri di atas kami memiliki rasio 3,62 untuk waktu CPU dengan waktu yang berlalu yang cukup bagus. Idealnya adalah rasio 4.0 pada mesin saya.

Salah satu bidang peningkatan yang mungkin adalah untuk membagi pekerjaan lebih merata di antara utas. Pada tangkapan layar di bawah ini, kita dapat melihat bahwa salah satu CPU saya memutuskan untuk beristirahat sejenak:

benang malas

Pemindaian indeks adalah salah satu dari sedikit operator yang dapat diimplementasikan secara paralel dan kami tidak dapat melakukan apa pun tentang bagaimana baris didistribusikan ke utas. Ada elemen peluang untuk itu juga, tetapi cukup konsisten saya melihat satu utas yang masih berjalan. Salah satu cara untuk mengatasi ini adalah dengan melakukan paralelisme dengan cara yang sulit: pada bagian dalam dari loop bersarang bergabung. Apa pun di bagian dalam loop bersarang akan diimplementasikan secara serial tetapi banyak utas serial dapat berjalan secara bersamaan. Selama kita mendapatkan metode distribusi paralel yang menguntungkan (seperti round robin), kita dapat mengontrol dengan tepat berapa banyak baris yang dikirim ke setiap utas.

Saya menjalankan kueri dengan DOP 4 jadi saya harus membagi NULLbaris dalam tabel menjadi empat ember secara merata . Salah satu cara untuk melakukan ini adalah dengan membuat sekelompok indeks pada kolom yang dihitung:

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

Saya tidak yakin mengapa empat indeks terpisah sedikit lebih cepat dari satu indeks, tetapi itulah yang saya temukan dalam pengujian saya.

Untuk mendapatkan paket loop bersarang paralel, saya akan menggunakan flag jejak tidak berdokumen 8649 . Saya juga akan menulis kode sedikit aneh untuk mendorong pengoptimal agar tidak memproses lebih banyak baris daripada yang diperlukan. Di bawah ini adalah salah satu implementasi yang tampaknya berfungsi dengan baik:

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

Hasil dari sepuluh percobaan:

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

Dengan kueri itu, kami memiliki rasio waktu CPU dengan waktu berlalu 3,85! Kami mencukur 17 ms dari runtime dan hanya butuh 4 kolom dan indeks yang dihitung untuk melakukannya! Setiap utas memproses sangat dekat dengan jumlah baris yang sama secara keseluruhan karena setiap indeks memiliki sangat dekat dengan jumlah baris yang sama dan setiap utas hanya memindai satu indeks:

pekerjaan dibagi dengan baik

Pada catatan akhir, kita juga dapat menekan tombol mudah dan menambahkan CCI yang tidak tercakup ke dalam Agekolom:

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

Kueri berikut selesai dalam 3 ms pada mesin saya:

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

Itu akan sulit dikalahkan.

Joe Obbish
sumber
7

Meskipun saya tidak memiliki salinan lokal dari database Stack Overflow, saya dapat mencoba beberapa pertanyaan. Pikir saya adalah untuk mendapatkan hitungan pengguna dari tampilan katalog sistem (sebagai lawan langsung mendapatkan hitungan baris dari tabel yang mendasarinya). Kemudian dapatkan hitungan baris yang sesuai (atau mungkin tidak) sesuai dengan kriteria Erik, dan lakukan beberapa matematika sederhana.

Saya menggunakan Stack Exchange Data Explorer (Bersama SET STATISTICS TIME ON;dan SET STATISTICS IO ON;) untuk menguji kueri. Untuk referensi, berikut adalah beberapa pertanyaan dan statistik CPU / IO:

QUERY 1

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

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 0 ms. (1 baris dikembalikan)

Tabel 'Pengguna'. Pindai hitungan 17, pembacaan logis 201567, pembacaan fisik 0, pembacaan baca depan 2740, pembacaan logis lob 0, pembacaan fisik lob 0, pembacaan pembacaan lob 0.

Waktu Eksekusi SQL Server: Waktu CPU = 1829 ms, waktu yang berlalu = 296 ms.

QUERY 2

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

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 0 ms. (1 baris dikembalikan)

Tabel 'Pengguna'. Pindai hitungan 17, bacaan logis 201567, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 2500 ms, waktu yang berlalu = 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);

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 0 ms. (1 baris dikembalikan)

Tabel 'Pengguna'. Pindai hitungan 34, bacaan logis 403134, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 3156 ms, waktu yang berlalu = 215 ms.

Percobaan 1

Ini lebih lambat dari semua pertanyaan Erik yang saya daftarkan di sini ... setidaknya dalam hal waktu yang telah berlalu.

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

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 0 ms. (1 baris dikembalikan)

Tabel 'Meja Kerja'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0. Tabel 'sysrowsets'. Pindai hitungan 2, bacaan logis 10, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0. Tabel 'sysschobjs'. Pindai hitungan 1, bacaan logis 4, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0. Tabel 'Pengguna'. Pindai hitungan 1, bacaan logis 201567, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 593 ms, waktu yang berlalu = 598 ms.

Percobaan ke-2

Di sini saya memilih variabel untuk menyimpan jumlah total pengguna (bukan sub-kueri). Jumlah pindaian meningkat dari 1 menjadi 17 dibandingkan dengan upaya pertama. Bacaan logis tetap sama. Namun, waktu yang berlalu menurun drastis.

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

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 0 ms. Tabel 'Meja Kerja'. Pindai hitungan 0, bacaan logis 0, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0. Tabel 'sysrowsets'. Pindai hitungan 2, bacaan logis 10, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bac baca baca depan 0. Tabel 'sysschobjs'. Pindai hitungan 1, bacaan logis 4, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 0 ms, waktu yang berlalu = 1 ms. (1 baris dikembalikan)

Tabel 'Pengguna'. Pindai hitungan 17, bacaan logis 201567, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.

Waktu Eksekusi SQL Server: Waktu CPU = 1471 ms, waktu yang berlalu = 98 ms.

Catatan Lain: DBCC TRACEON tidak diizinkan di Stack Exchange Data Explorer, seperti disebutkan di bawah ini:

Pengguna 'STACKEXCHANGE \ svc_sede' tidak memiliki izin untuk menjalankan DBCC TRACEON.

Dave Mason
sumber
1
Mereka mungkin tidak memiliki indeks yang sama dengan yang saya lakukan, karenanya perbedaannya. Dan siapa yang tahu? Mungkin server rumah saya menggunakan perangkat keras yang lebih baik;) Jawaban yang bagus!
Erik Darling
Anda harus menggunakan kueri berikut untuk upaya pertama Anda (akan jauh lebih cepat, karena rids banyak sys.objects-overhead): 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
NB: ketahuilah bahwa In-Memory-indexes (NONCLUSTERED HASH) tidak memiliki indeks id = 0/1 seperti yang akan terjadi pada heap / clustered index)
Thomas Franz
1

Gunakan variabel?

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;

Per komentar dapat melewati variabel

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);
paparazzo
sumber
3
Juga:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ
3
Mungkin ingin mencobanya sambil memeriksa CPU & IO. Petunjuk: sama dengan salah satu jawaban Erik.
Brent Ozar
0

Menggunakan dengan baik 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.

Ini adalah sesuatu yang baru saja muncul di pikiran saya. Hanya menjalankan ini di https://data.stackexchange.com

Tapi tidak seefisien @blitz_erik

Biju jose
sumber
0

Solusi sepele adalah menghitung hitung (*) - hitung (usia> = 18):

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

Atau:

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

Hasil di sini

Salman A
sumber