ส่วนคำตอบ
มีหลายวิธีในการเขียนสิ่งนี้โดยใช้โครงสร้าง T-SQL ที่แตกต่างกัน เราจะดูข้อดีข้อเสียและทำการเปรียบเทียบโดยรวมด้านล่าง
ก่อนขึ้น : การใช้OR
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;
การใช้OR
ทำให้เรามีแผนค้นหาที่มีประสิทธิภาพมากขึ้นซึ่งจะอ่านจำนวนแถวที่แน่นอนที่เราต้องการอย่างไรก็ตามมันจะเพิ่มสิ่งที่โลกทางเทคนิคเรียกa whole mess of malarkey
ไปยังแผนแบบสอบถาม
โปรดทราบด้วยว่าการค้นหาดำเนินการสองครั้งที่นี่ซึ่งควรเห็นได้ชัดเจนกว่าจากผู้ให้บริการกราฟิก:

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.
ลำดับที่สอง : การใช้ตารางที่ได้รับด้วยUNION ALL
แบบสอบถามของเราสามารถเขียนใหม่ได้เช่นนี้
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);
แผนนี้ให้ผลในรูปแบบเดียวกันโดยมีโรคมาลาเรียน้อยกว่าและมีระดับความซื่อสัตย์ที่ชัดเจนมากขึ้นเกี่ยวกับดัชนีที่ถูกแสวงหา (ค้นหา?) กี่ครั้ง

มันจะทำการอ่าน (8233) จำนวนเท่ากันกับOR
เคียวรี แต่ประหยัดเวลาของ CPU ประมาณ 100 มิลลิวินาที
CPU time = 313 ms, elapsed time = 315 ms.
แต่คุณจะต้องมีจริงๆระวังนี่เพราะถ้าแผนนี้พยายามที่จะไปขนานสองแยกCOUNT
การดำเนินงานจะได้รับการต่อเนื่องเพราะพวกเขากำลังแต่ละถือว่าเป็นสเกลารวมทั่วโลก หากเราบังคับใช้แผนขนานโดยใช้การติดตามสถานะ 8649 ปัญหาจะชัดเจน
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);

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

สำหรับสิ่งที่มันคุ้มค่ารุ่นขนานอย่างเต็มที่มีประโยชน์ที่ดีบางอย่าง ด้วยค่าใช้จ่ายในการอ่านอีกประมาณ 100 ครั้งและเวลา CPU เพิ่มเติมประมาณ 90ms เวลาที่ผ่านไปจะลดลงเป็น 93 มิลลิวินาที
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.
CROSS ใช้กับอะไรได้บ้าง
ไม่มีคำตอบที่สมบูรณ์หากไม่ได้ใช้เวทย์มนตร์ของCROSS APPLY
!
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);
แผนนี้แย่มาก นี่คือแผนที่คุณจะได้เมื่อคุณปรากฏตัวครั้งสุดท้ายในวันเซนต์แพทริก แม้ว่าจะขนานกันอย่างดีด้วยเหตุผลบางอย่างมันสแกน PK / CX Ew แผนมีต้นทุน 2198 เหรียญตัวค้นหา

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.
ซึ่งเป็นตัวเลือกที่แปลกเพราะถ้าเราบังคับให้ใช้ดัชนีที่ไม่เป็นคลัสเตอร์ค่าใช้จ่ายจะลดลงค่อนข้างมากถึง 1,798 เหรียญค้นหา
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);
เฮ้ค้นหา! ตรวจสอบคุณที่นั่น นอกจากนี้โปรดทราบว่าด้วยความมหัศจรรย์ของCROSS APPLY
เราไม่จำเป็นต้องทำสิ่งที่โง่เขลาเพื่อให้ได้แผนคู่ขนานที่ครบถ้วน

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.
การใช้ครอสจะจบลงได้ดีขึ้นโดยที่ไม่มีของCOUNT
อยู่ในนั้น
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);
แผนดูดี แต่การอ่านและ CPU ไม่ได้ปรับปรุง

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.
ใช้การเขียนซ้ำเพื่อให้ได้ผลลัพธ์การเข้าร่วมที่เป็นแบบเดียวกันทุกอย่าง ฉันจะไม่โพสต์แผนคิวรีและข้อมูลสถิติอีกครั้งเพราะพวกเขาไม่ได้เปลี่ยนแปลงเลย
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;
พีชคณิตเชิงสัมพันธ์ : เพื่อให้ละเอียดและเพื่อป้องกันไม่ให้โจ Celko หลอกหลอนความฝันของเราอย่างน้อยเราต้องลองสิ่งที่เกี่ยวข้องกับความแปลก นี่มันไม่มีอะไรเลย!
ความพยายามด้วย 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.
และนี่คือความพยายามด้วย 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.
อาจมีวิธีอื่นในการเขียนสิ่งเหล่านี้ แต่ฉันจะทิ้งเรื่องนั้นไว้กับคนที่อาจจะใช้EXCEPT
และINTERSECT
บ่อยกว่าที่ฉันทำ
หากคุณต้องการจำนวนที่
ฉันใช้COUNT
ในการสอบถามของฉันเป็นแบบสั้น ๆ (อ่าน: ฉันขี้เกียจเกินไปที่จะเกิดสถานการณ์ที่เกี่ยวข้องมากขึ้นในบางครั้ง) หากคุณต้องการการนับคุณสามารถใช้CASE
นิพจน์เพื่อทำสิ่งเดียวกัน
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
สิ่งเหล่านี้ทั้งสองได้รับแผนเดียวกันและมี 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 = 719 ms, elapsed time = 719 ms.
ผู้ชนะ?
ในการทดสอบของฉันแผนขนานที่บังคับใช้กับ SUM บนตารางที่ได้รับนั้นทำได้ดีที่สุด และใช่แบบสอบถามเหล่านี้จำนวนมากสามารถได้รับความช่วยเหลือโดยการเพิ่มดัชนีที่กรองแล้วสองสามรายการลงในบัญชีของภาคแสดงทั้งสอง แต่ฉันต้องการที่จะปล่อยให้การทดลองอื่น ๆ
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);
ขอบคุณ!
NOT EXISTS ( INTERSECT / EXCEPT )
คำสั่งสามารถทำงานได้โดยไม่ต้องINTERSECT / EXCEPT
ส่วนWHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );
อีกวิธีหนึ่ง - ที่ใช้EXCEPT
:SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ;
(ที่ UserID เป็น PK หรือที่ไม่ซ้ำกันในคอลัมน์ไม่เป็นโมฆะ (s))