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 OR
memberi 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 malarkey
dengan rencana kueri.
Perhatikan juga bahwa Seek dieksekusi dua kali di sini, yang seharusnya lebih jelas dari operator grafis:
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.
Ia melakukan jumlah pembacaan yang sama (8233) dengan OR
kueri, 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 COUNT
operasi 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);
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.
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.
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.
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 COUNT
barang - 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.
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 );
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);
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 EXCEPT
dan INTERSECT
lebih sering daripada saya.
Jika Anda benar-benar hanya perlu hitungan yang
saya gunakan COUNT
dalam 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 CASE
ekspresi 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.
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!
NOT EXISTS ( INTERSECT / EXCEPT )
query dapat bekerja tanpaINTERSECT / EXCEPT
bagian:WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
Cara lain - yang menggunakanEXCEPT
: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)).