요약
SQL Server는 올바른 조인 (내부 또는 외부)을 사용하고 apply 와 join 사이의 내부 변환을 수행 할 때 원래 쿼리의 모든 의미 를 준수 하기 위해 필요한 경우 투영을 추가합니다 .
계획의 차이점 은 SQL Server에서 group by 절이 있거나없는 집계 의 서로 다른 의미 로 설명 할 수 있습니다 .
세부
가입 대 신청
apply 와 join 을 구별 할 수 있어야합니다 .
대다
Apply 의 내부 (아래) 입력은 외부 (위) 입력의 각 행에 대해 실행되며 현재 외부 행에서 제공하는 하나 이상의 내부 매개 변수 값이 있습니다. 적용 의 전체 결과 는 매개 변수화 된 내부 실행으로 생성 된 모든 행의 조합 (모두 조합)입니다. 매개 변수가 존재한다는 것은 적용 은 때때로 상관 조인이라고합니다.
이 적용 항상 의한 실행 계획에 구현되어 중첩 루프 연산자. 연산자는 결합 술어가 아닌 외부 참조 특성을 갖습니다 . 외부 참조는 루프의 각 반복에서 외부에서 내부로 전달되는 매개 변수입니다.
붙다
결합은 결합 연산자에서 결합 술어를 평가합니다. 조인은 일반적으로 SQL Server의 Hash Match , Merge 또는 Nested Loops 연산자 로 구현할 수 있습니다 .
중첩 루프 를 선택 하면 외부 참조 가 부족 하고 일반적으로 결합 술어가 존재 하므로 적용 과 구별 될 수 있습니다 . 조인 의 내부 입력은 외부 입력의 값을 절대 참조하지 않습니다. 내부는 여전히 각 외부 행에 대해 한 번 실행되지만 내부 실행은 현재 외부 행의 값에 의존하지 않습니다.
자세한 내용은 내 게시물 Apply vs Nested Loops Join을 참조하십시오 .
... 내부 조인 대신 실행 계획에 외부 조인 이있는 이유는 무엇입니까?
외부 조인은 옵티마이 저가보다 저렴한 조인 기반 계획을 찾을 수 있는지 확인 하기 위해 조인에 적용 (이라는 규칙 사용 )을 변환 할 때 발생합니다 . 적용 에 스칼라 집계 가 포함 된 경우 조인은 정확성을 위해 외부 조인이어야합니다 . 내부 조인은 원래 적용 할 때와 동일한 결과를 보장 하지 않습니다 .ApplyHandler
스칼라 및 벡터 집계
- 해당
GROUP BY
절이 없는 집계 는 스칼라 집계입니다.
- 해당
GROUP BY
절이 있는 집계 는 벡터 집계입니다.
SQL Server에서 스칼라 집계는 집계 할 행이없는 경우에도 항상 행을 생성합니다. 예를 들어, COUNT
행이없는 스칼라 집계는 0입니다. 벡터 COUNT
없는 행 집합은 공집합 (전혀 행)입니다.
다음 장난감 쿼리는 차이점을 보여줍니다. 내 기사 Scalar 및 Vector Aggregates의 Fun 에서 스칼라 및 벡터 집계에 대한 자세한 내용을 읽을 수도 있습니다 .
-- Produces a single zero value
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1;
-- Produces no rows
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1 GROUP BY ();
db <> 바이올린 데모
변형이 조인에 적용됩니다.
이전 에 원래 적용 에 스칼라 집계 가 포함 된 경우 조인이 정확성을 위해 외부 조인이어야한다고 언급했습니다 . 이것이 왜 그런지 자세히 보여주기 위해, 간단한 질문 질의 예제를 사용할 것입니다.
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
SELECT * FROM @A AS A
CROSS APPLY (SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A) AS CA;
은 스칼라 집계 이므로 column c
에 대한 올바른 결과 는 0 입니다. 이 적용 쿼리를 조인 형식으로 변환 할 때 SQL Server는 T-SQL로 표현 된 경우 다음과 유사한 내부 대안을 생성합니다.COUNT_BIG
SELECT A.*, c = COALESCE(J1.c, 0)
FROM @A AS A
LEFT JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
적용을 비 상관 조인으로 다시 쓰려면 GROUP BY
파생 테이블에 를 도입해야 합니다 (그렇지 않으면 A
조인 할 열 이 없을 수 있음 ). 조인은 외부 조인 이어야 하므로 테이블의 각 행이 @A
계속 출력에서 행을 생성합니다. 결합 술어가 true로 평가되지 않으면 왼쪽 결합은 NULL
for 열 을 생성 c
합니다. 그건 NULL
필요에 의해 제로로 번역 할 수 COALESCE
에서 정확한 변환 완료 적용을 .
아래 데모는 외부 조인과 원래 적용 쿼리 COALESCE
와 같은 조인 을 사용하여 동일한 결과를 생성하는 방법을 보여줍니다 .
db <> 바이올린 데모
이랑 GROUP BY
... 그룹 별 절을 주석 해제하면 왜 내부 조인이 발생합니까?
단순화 된 예제를 계속하면서 다음을 추가하십시오 GROUP BY
.
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
-- Original
SELECT * FROM @A AS A
CROSS APPLY
(SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A GROUP BY B.A) AS CA;
은 COUNT_BIG
지금이다 벡터 빈 입력 세트에 대한 올바른 결과가 더 이상 제로 없도록는 없다, 집계 전혀 행 . 즉, 위의 명령문을 실행하면 출력이 생성되지 않습니다.
이 의미는 apply 에서 join으로 변환 할 때 CROSS APPLY
내부 행을 생성하지 않는 외부 행을 자연스럽게 거부 하므로 적용 하기 가 훨씬 쉽습니다 . 따라서 추가적인 표현식 투영없이 내부 조인을 안전하게 사용할 수 있습니다.
-- Rewrite
SELECT A.*, J1.c
FROM @A AS A
JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
아래 데모는 내부 조인 재 작성이 벡터 집계를 사용한 원래 적용과 동일한 결과를 생성 함을 보여줍니다.
db <> 바이올린 데모
옵티마이 저는 저렴한 조인 계획 (빠른 계획이 충분 함)을 빨리 찾기 때문에 작은 테이블과의 병합 내부 조인을 선택합니다 . 비용 기반 옵티마이 저는 루프 조인 또는 ForceSeek 힌트를 사용하는 경우 여기에서와 같이 저렴하게 적용 계획을 찾는 대신 적용에 다시 조인을 다시 작성하지만이 경우에는 노력할 가치가 없습니다.
노트
단순화 된 예는 의미가 다른 것을보다 명확하게 보여주기 위해 내용이 다른 여러 테이블을 사용합니다.
옵티마이 저는 자체 조인이 일치하지 않는 (조인하지 않는) 행을 생성 할 수 없다는 이유로 추론 할 수 있어야한다고 주장 할 수 있지만 오늘날에는 해당 논리가 포함되어 있지 않습니다. 쿼리에서 동일한 테이블에 여러 번 액세스해도 격리 수준 및 동시 활동에 따라 동일한 결과가 생성되는 것은 아닙니다.
옵티마이 저는 이러한 의미 및 엣지 케이스에 대해 걱정하므로 필요하지 않습니다.
보너스 : 내부 적용 플랜
SQL Server 는 예제 쿼리에 대해 내부 적용 계획 (내부 조인 계획이 아님 !)을 생성 할 수 있으며 비용 때문에하지 않기로 선택합니다. 질문에 표시된 외부 조인 계획의 비용은 랩톱의 SQL Server 2017 인스턴스에서 0.02898 단위입니다.
설명을 위해 문서화되지 않은 지원되지 않는 추적 플래그 9114 (비활성화 등) 를 사용하여 적용 (상관 결합) 계획을 강제 실행할 수 있습니다 ApplyHandler
.
SELECT *
FROM #MyTable AS mt
CROSS APPLY
(
SELECT COUNT_BIG(DISTINCT mt2.Col_B) AS dc
FROM #MyTable AS mt2
WHERE mt2.Col_A = mt.Col_A
--GROUP BY mt2.Col_A
) AS ca
OPTION (QUERYTRACEON 9114);
지연 인덱스 스풀이있는 중첩 루프 적용 계획 이 생성 됩니다 . 총 예상 비용은 0.0463983입니다 (선택한 계획보다 높음).
Apply 중첩 루프를 사용하는 실행 계획 은 GROUP BY
절의 존재 여부에 관계없이 "내부 조인"시맨틱을 사용하여 올바른 결과를 생성합니다 .
현실 세계에서, 우리는 일반적으로는의가 안쪽에 찾아 지원하는 인덱스 것이다 적용 예를 들어, 자연이 옵션을 선택하는 SQL 서버를 격려하기를 :
CREATE INDEX i ON #MyTable (Col_A, Col_B);
db <> 바이올린 데모