난수 및 조인 유형의 예기치 않은 결과


16

4 개의 임의의 숫자 (1 ~ 4)를 얻은 다음 일치하는 database_id 번호를 얻기 위해 다시 결합하는 간단한 스크립트가 있습니다. LEFT JOIN으로 스크립트를 실행하면 매번 4 개의 행이 반환됩니다 (예상 결과). 그러나 INNER JOIN으로 실행하면 다양한 수의 행 (때로는 2, 때로는 8)이 나타납니다.

논리적으로, database_ids 1-4의 행이 sys.databases에 있다는 것을 알고 있기 때문에 아무런 차이가 없어야합니다. 그리고 조인이 아닌 4 개의 행이있는 난수 테이블에서 선택하므로 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를 추가하면 계산 스칼라 평가가 계획으로 더 깊이 들어가고 결합 술어를 제공하고 맨 위에있는 계산 스칼라가 이전 계산 스칼라를 참조합니다.

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

여전히 늦게 기다리는 이유에 대해 파고 있지만 현재 Paul White 가이 게시물을 읽습니다 ( 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 년 계획은 다르게 보이지만 Compute Scalar 작업은 가입 후에도 유지됩니다.

상수 표현식을 사용한 2014 년의 쿼리 계획입니다.

여기에 이미지 설명을 입력하십시오

이것은 newid 표현식을 사용하는 2014 인스턴스에 대한 쿼리 계획입니다.

여기에 이미지 설명을 입력하십시오

이것은 분명히 의도적으로 설계된 것입니다, 연결이 발행 여기. 그 사실을 알고있는 @paulWhite에게 감사드립니다.


1
그렇습니다. 정확히 무슨 일이 일어나고 있는지는 확실하지 않습니다. 결과는 전달되는 T-SQL과 일치하지 않으므로 질문입니다.
Brent Ozar

난수를 정적 1로 바꾸어도 조인 연산자에 조인 조건자가 없습니다
James Anderson

당신이 뭔가에있는 것 같습니다. 심지어 동작을 변경하지 않습니다 (FORCE ORDER) 옵션을 사용하여 - 임의의 숫자는 여전히 마지막으로 계산됩니다 ...
예레미야 Peschka

하려면 sys.databases TVF 제거, 다음과 같은 계획을 생성합니다 gist.github.com/peschkaj/cebdeb98daa4d1f08dc5을
예레미야 Peschka

이것은 운영자 우선 순위 문제인 것 같습니다
James Anderson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.