นี่เป็นปัญหาที่ฉันคิดขึ้นมาเป็นระยะและยังไม่พบทางออกที่ดีสำหรับ
เผื่อว่าโครงสร้างตารางต่อไปนี้
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
และข้อกำหนดคือการพิจารณาว่าคอลัมน์ใดคอลัมน์หนึ่งที่มีค่าว่างB
หรือC
มีNULL
ค่าใด ๆ(และถ้าเป็นเช่นนั้น)
นอกจากนี้สมมติว่าตารางมีแถวนับล้านแถว (และไม่มีสถิติคอลัมน์ที่สามารถมองได้เนื่องจากฉันสนใจวิธีแก้ปัญหาทั่วไปสำหรับคิวรีประเภทนี้)
ฉันสามารถคิดถึงวิธีการไม่กี่ทางที่จะเข้าใกล้สิ่งนี้ แต่ทุกคนก็มีจุดอ่อน
EXISTS
งบสองแยก นี่จะมีข้อดีของการอนุญาตให้แบบสอบถามหยุดสแกนเร็วที่สุดเท่าที่NULL
จะพบ แต่ถ้าในความเป็นจริงไม่มีทั้งคอลัมน์ทั้งNULL
สองการสแกนแบบเต็มจะส่งผลให้
แบบสอบถามรวมครั้งเดียว
SELECT
MAX(CASE WHEN B IS NULL THEN 1 ELSE 0 END) AS B,
MAX(CASE WHEN C IS NULL THEN 1 ELSE 0 END) AS C
FROM T
นี่สามารถประมวลผลทั้งสองคอลัมน์ในเวลาเดียวกันดังนั้นจึงมีกรณีที่เลวร้ายที่สุดของการสแกนแบบเต็ม ข้อเสียคือแม้ว่าจะพบ a NULL
ในคอลัมน์ทั้งสอง แต่เนิ่นๆบนเคียวรีจะยังคงสแกนส่วนที่เหลือทั้งหมดของตาราง
ตัวแปรผู้ใช้
ฉันสามารถคิดถึงวิธีที่สามในการทำสิ่งนี้
BEGIN TRY
DECLARE @B INT, @C INT, @D INT
SELECT
@B = CASE WHEN B IS NULL THEN 1 ELSE @B END,
@C = CASE WHEN C IS NULL THEN 1 ELSE @C END,
/*Divide by zero error if both @B and @C are 1.
Might happen next row as no guarantee of order of
assignments*/
@D = 1 / (2 - (@B + @C))
FROM T
OPTION (MAXDOP 1)
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 /*Divide by zero*/
BEGIN
SELECT 'B,C both contain NULLs'
RETURN;
END
ELSE
RETURN;
END CATCH
SELECT ISNULL(@B,0),
ISNULL(@C,0)
แต่สิ่งนี้ไม่เหมาะสำหรับรหัสการผลิตเนื่องจากพฤติกรรมที่ถูกต้องสำหรับแบบสอบถามการต่อข้อมูลรวมไม่ได้ถูกกำหนดไว้ และการยกเลิกการสแกนโดยการโยนข้อผิดพลาดเป็นวิธีที่น่ากลัวอยู่ดี
มีตัวเลือกอื่นที่รวมจุดแข็งของวิธีการข้างต้นหรือไม่
แก้ไข
เพียงอัปเดตสิ่งนี้กับผลลัพธ์ที่ฉันได้รับในแง่ของการอ่านสำหรับคำตอบที่ส่งมาจนถึงตอนนี้ (โดยใช้ข้อมูลการทดสอบของ @ ypercube)
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | 2 * EXISTS | CASE | Kejser | Kejser | Kejser | ypercube | 8kb |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | | | | MAXDOP 1 | HASH GROUP, MAXDOP 1 | | |
| No Nulls | 15208 | 7604 | 8343 | 7604 | 7604 | 15208 | 8346 (8343+3) |
| One Null | 7613 | 7604 | 8343 | 7604 | 7604 | 7620 | 7630 (25+7602+3) |
| Two Null | 23 | 7604 | 8343 | 7604 | 7604 | 30 | 30 (18+12) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
สำหรับ @ คำตอบของโทมัสผมเปลี่ยนTOP 3
ไปTOP 2
ที่อาจอนุญาตให้ออกจากก่อนหน้านี้ ฉันได้รับแผนแบบขนานโดยค่าเริ่มต้นสำหรับคำตอบนั้นลองด้วยMAXDOP 1
คำใบ้เพื่อให้จำนวนการอ่านเปรียบเทียบกับแผนอื่นมากขึ้น ฉันค่อนข้างประหลาดใจกับผลลัพธ์ที่ได้จากการทดสอบก่อนหน้านี้ฉันเห็นว่ามีการลัดวงจรแบบสอบถามโดยไม่อ่านตารางทั้งหมด
แผนสำหรับข้อมูลการทดสอบของฉันที่มีวงจรสั้นอยู่ด้านล่าง
แผนสำหรับข้อมูลของ ypercube คือ
ดังนั้นจึงเพิ่มตัวดำเนินการเรียงลำดับการบล็อกในแผน ฉันลองด้วยHASH GROUP
คำใบ้ แต่ก็ยังจบลงด้วยการอ่านทุกแถว
ดังนั้นดูเหมือนว่ากุญแจสำคัญคือการให้hash match (flow distinct)
โอเปอเรเตอร์อนุญาตให้แผนนี้ลัดวงจรเนื่องจากทางเลือกอื่นจะปิดกั้นและใช้แถวทั้งหมด ฉันไม่คิดว่าจะมีคำใบ้ที่จะบังคับสิ่งนี้โดยเฉพาะ แต่เห็นได้ชัดว่า"โดยทั่วไปเครื่องมือเพิ่มประสิทธิภาพเลือก Flow Distinct ที่กำหนดว่าต้องใช้แถวเอาท์พุทน้อยลงกว่าค่าที่แตกต่างกันในชุดอินพุต" .
@ ข้อมูล ypercube ของมีเพียง 1 แถวในแต่ละคอลัมน์มีNULL
ค่า (ตาราง cardinality = 30300) 1
และแถวที่คาดจะเข้าและออกของผู้ประกอบการทั้งสอง ด้วยการทำให้เพรดิเคตเพิ่มความทึบแสงให้กับเครื่องมือเพิ่มประสิทธิภาพอีกเล็กน้อยจึงสร้างแผนพร้อมกับโอเปอเรเตอร์ Flow Distinct
SELECT TOP 2 *
FROM (SELECT DISTINCT
CASE WHEN b IS NULL THEN NULL ELSE 'foo' END AS b
, CASE WHEN c IS NULL THEN NULL ELSE 'bar' END AS c
FROM test T
WHERE LEFT(b,1) + LEFT(c,1) IS NULL
) AS DT
แก้ไข 2
หนึ่งบิดสุดท้ายที่เกิดขึ้นกับผมคือว่าแบบสอบถามดังกล่าวข้างต้นยังคงสามารถจบลงด้วยการประมวลผลแถวเกินกว่าที่จำเป็นในกรณีที่แถวแรกจะพบกับNULL
มี NULLs ในคอลัมน์ทั้งสองและB
C
มันจะสแกนต่อไปแทนที่จะออกทันที วิธีหนึ่งในการหลีกเลี่ยงปัญหานี้คือการยกเลิกการหมุนแถวตามที่สแกน ดังนั้นการแก้ไขครั้งสุดท้ายของฉันสำหรับคำตอบของ Thomas Kejserอยู่ด้านล่าง
SELECT DISTINCT TOP 2 NullExists
FROM test T
CROSS APPLY (VALUES(CASE WHEN b IS NULL THEN 'b' END),
(CASE WHEN c IS NULL THEN 'c' END)) V(NullExists)
WHERE NullExists IS NOT NULL
มันอาจจะดีกว่าสำหรับภาคที่จะเป็นWHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
แต่กับข้อมูลการทดสอบก่อนหน้านี้ที่ไม่มีแผนให้ฉันด้วยความแตกต่างของการไหลในขณะที่NullExists IS NOT NULL
คนที่ทำ (แผนด้านล่าง)
TOP 3
ก็อาจจะTOP 2
เป็นในขณะนี้จะสแกนจนกว่าจะพบหนึ่งของแต่ละต่อไป(NOT_NULL,NULL)
, ,(NULL,NOT_NULL)
(NULL,NULL)
2 จาก 3 เหล่านั้นจะเพียงพอ - และหากพบ(NULL,NULL)
ก่อนก็ไม่จำเป็นต้องใช้สอง นอกจากนี้เพื่อให้การลัดวงจรแผนจะต้องดำเนินการที่แตกต่างกันผ่านทางhash match (flow distinct)
ผู้ประกอบการมากกว่าhash match (aggregate)
หรือdistinct sort