ผลลัพธ์ที่ไม่คาดคิดพร้อมตัวเลขสุ่มและประเภทการเข้าร่วม


16

ฉันมีสคริปต์ง่าย ๆ ที่ได้รับตัวเลขสุ่มสี่ตัว (1 ถึง 4) จากนั้นก็กลับมารวมกันเพื่อรับหมายเลข database_id ที่ตรงกัน เมื่อฉันรันสคริปต์ด้วย LEFT JOIN ฉันจะได้สี่แถวกลับมาทุกครั้ง (ผลลัพธ์ที่คาดหวัง) อย่างไรก็ตามเมื่อฉันเรียกใช้ด้วยการเข้าร่วมภายในฉันได้รับจำนวนแถวที่แตกต่างกัน - บางครั้งสองครั้งบางครั้งแปด

เหตุผลไม่ควรมีความแตกต่างเพราะฉันรู้ว่าแถวที่มี database_ids 1-4 อยู่ในฐานข้อมูล และเนื่องจากเราเลือกจากตารางตัวเลขสุ่มที่มีสี่แถว (ซึ่งต่างจากการเข้าร่วมกับตาราง) จึงไม่ควรมีการส่งคืนเกินสี่แถว

สิ่งนี้เกิดขึ้นทั้งใน SQL Server 2012 และ 2014 อะไรคือสาเหตุที่ทำให้ INNER JOIN ส่งกลับจำนวนแถวที่แตกต่างกัน

/* Works as expected -- always four rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
LEFT JOIN sys.databases d ON rando.RandomNumber = d.database_id;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id;

/* Also returns a varying number of rows */

WITH rando AS (
  SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
  FROM sys.databases WHERE database_id <= 4
)

SELECT r.RandomNumber, d.database_id
FROM rando AS r
INNER JOIN sys.databases d ON r.RandomNumber = d.database_id;

3
อีกวิธีในการรับ 4 แถวเสมอ: SELECT TOP (4) d.database_id FROM sys.databases AS d CROSS JOIN (VALUES (1),(2),(3),(4)) AS multi (i) WHERE d.database_id <= 4 ORDER BY CHECKSUM(NEWID()) ;ฉันเดาว่ามันทำงานได้ดีเพราะไม่มีการเข้าร่วมกับค่าของฟังก์ชันที่ไม่ได้กำหนดค่าไว้
ypercubeᵀᴹ

คำตอบ:


9

ด้วยการเพิ่มตัวเลือกเพิ่มเติมมันจะเพิ่มการประเมินสเกลาร์ของการคำนวณให้ลึกลงไปในแผนและให้ภาคแสดงการรวมเข้าด้วยกันสเกลาร์ของการคำนวณที่ด้านบนจากนั้นอ้างอิงที่ก่อนหน้านี้

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT ( SELECT 1 + ABS(CHECKSUM(NEWID())) % (4)) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id

|--Compute Scalar(DEFINE:([Expr1071]=[Expr1070]))

|--Compute Scalar(DEFINE:([Expr1070]=(1)+abs(checksum(newid()))%(4)))

ยังคงขุดเป็นเพียงทำไมมันรอช้าที่จะทำมัน แต่ในขณะนี้อ่านโพสต์นี้โดยพอลสีขาว ( https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html ) . บางทีมันอาจจะเกี่ยวข้องกับความจริงที่ว่า NEWID นั้นไม่ได้ถูกกำหนดไว้แล้ว?


12

สิ่งนี้อาจให้ข้อมูลเชิงลึกบางอย่างจนกว่าบุคคลที่ชาญฉลาดในไซต์จะเข้ามา

ฉันใส่ผลลัพธ์สุ่มลงในตารางชั่วคราวและฉันได้รับ 4 ผลลัพธ์อย่างต่อเนื่องโดยไม่คำนึงถึงประเภทการรวม

/* Works as expected -- always four rows */

DECLARE @Rando table
(
    RandomNumber int
);

INSERT INTO
    @Rando
(
    RandomNumber
)
-- This generates 4 random numbers from 1 to 4, endpoints inclusive
SELECT
    1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
FROM
    sys.databases
WHERE
    database_id <= 4;

SELECT
    *
FROM
    @Rando AS R;

SELECT
    rando.RandomNumber
,   d.database_id
FROM 
    @Rando AS rando
    LEFT JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
    @Rando AS rando
    INNER JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;

/* Also returns a varying number of rows */

WITH rando AS 
(
    SELECT * FROM @Rando AS rando
)
SELECT r.RandomNumber, d.database_id
FROM 
    rando AS r
    INNER JOIN 
        sys.databases d 
        ON r.RandomNumber = d.database_id
ORDER BY 1,2;

ถ้าฉันเปรียบเทียบแผนคิวรีระหว่างเคียวรีที่สองของคุณกับการเปลี่ยนแปลงกับตัวแปรตารางฉันจะเห็นว่ามีความแตกต่างที่ชัดเจนระหว่างสองเคียวรี X สีแดงNo Join Predicateนั้นดูแปลกสำหรับสมองผู้พัฒนามนุษย์ถ้ำของฉันจริงๆ

ป้อนคำอธิบายรูปภาพที่นี่

ถ้าฉันกำจัดการสุ่มบิตของแบบสอบถามเป็นค่าคงที่ 1 % (4)แผนของฉันดูดีขึ้น แต่ Compute Scalar ถูกกำจัดเพื่อให้ฉันมองใกล้

ป้อนคำอธิบายรูปภาพที่นี่

มันคำนวณการแสดงออกของตัวเลขสุ่มหลังจากเข้าร่วม ไม่ว่าจะเป็นที่คาดหวังฉันยังคงออกไปยังตัวช่วยสร้างภายในบนไซต์ แต่อย่างน้อยนั่นคือสาเหตุที่คุณได้รับผลลัพธ์ที่หลากหลายในการเข้าร่วมของคุณ

2014

สำหรับผู้ที่เล่นที่บ้านแผนคิวรีข้างต้นถูกสร้างขึ้นจากอินสแตนซ์ 2008 R2 แผน 2014 ดูแตกต่าง แต่การคำนวณ Compal Scalar ยังคงอยู่หลังจากเข้าร่วม

นี่คือแผนแบบสอบถามสำหรับปี 2014 โดยใช้นิพจน์คงที่

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือแผนแบบสอบถามสำหรับอินสแตนซ์ 2014 โดยใช้นิพจน์ newid

ป้อนคำอธิบายรูปภาพที่นี่

เห็นได้ชัดว่านี่คือโดยการออกแบบปัญหาการเชื่อมต่อที่นี่ ขอบคุณ @paulWhite ที่รู้ว่ามีอยู่จริง


1
ถูกต้อง - นั่นคือสิ่งที่เกิดขึ้น แต่ก็ไม่ได้คาดหวังอย่างแน่นอน ผลลัพธ์ไม่ตรงกับ T-SQL ที่ถูกส่งผ่านและทำให้เกิดคำถาม
เบรนต์โอซาร์

แม้การเปลี่ยนหมายเลขสุ่มด้วยสแตติก 1 ให้ตัวดำเนินการเข้าร่วมโดยไม่มีส่วนร่วม
เจมส์แอนเดอร์สัน

ดูเหมือนว่าคุณกำลังทำอะไรอยู่ แม้แต่การใช้ OPTION (FORCE ORDER) ก็ไม่เปลี่ยนพฤติกรรม - ตัวเลขสุ่มยังคงถูกคำนวณล่าสุด ...
Jeremiah Peschka

การลบ sys.database TVF สิ่งต่อไปนี้จะสร้างแผนเดียวกัน: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremiah Peschka

ดูเหมือนว่าปัญหาผู้ดำเนินการลำดับความสำคัญ
James Anderson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.