CROSS ỨNG DỤNG tạo ra sự tham gia bên ngoài


17

Để trả lời cho việc đếm SQL khác biệt trên phân vùng, Erik Darling đã đăng mã này để giải quyết vấn đề thiếu COUNT(DISTINCT) OVER ():

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY (   SELECT COUNT(DISTINCT mt2.Col_B) AS dc
                FROM   #MyTable AS mt2
                WHERE  mt2.Col_A = mt.Col_A
                -- GROUP BY mt2.Col_A 
            ) AS ca;

Truy vấn sử dụng CROSS APPLY(không OUTER APPLY) vậy tại sao lại có một phép nối ngoài trong kế hoạch thực hiện thay vì một phép nối bên trong ?

nhập mô tả hình ảnh ở đây

Ngoài ra, tại sao việc không chú ý đến nhóm theo mệnh đề dẫn đến sự tham gia bên trong?

nhập mô tả hình ảnh ở đây

Tôi không nghĩ dữ liệu là quan trọng nhưng sao chép từ dữ liệu được đưa ra bởi kevinwhat cho câu hỏi khác:

create table #MyTable (
Col_A varchar(5),
Col_B int
)

insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)

insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)

Câu trả lời:


23

Tóm lược

SQL Server sử dụng phép nối chính xác (bên trong hoặc bên ngoài) và thêm các phép chiếu khi cần để tôn vinh tất cả các ngữ nghĩa của truy vấn ban đầu khi thực hiện các bản dịch nội bộ giữa áp dụngtham gia .

Sự khác biệt trong các kế hoạch đều có thể được giải thích bằng các ngữ nghĩa khác nhau của các tập hợp có và không có nhóm theo mệnh đề trong SQL Server.


Chi tiết

Tham gia vs Áp dụng

Chúng ta sẽ cần có khả năng phân biệt giữa ứng dụngtham gia :

  • Ứng dụng

    Đầu vào bên trong (phía dưới) của ứng dụng được chạy cho mỗi hàng của đầu vào bên ngoài (phía trên), với một hoặc nhiều giá trị tham số bên trong được cung cấp bởi hàng ngoài hiện tại. Kết quả tổng thể của ứng dụng là sự kết hợp (kết hợp tất cả) của tất cả các hàng được tạo bởi các thực thi bên trong được tham số hóa. Sự hiện diện của các tham số có nghĩa là áp dụng đôi khi được gọi là một tham gia tương quan.

    Một ứng dụng luôn được triển khai trong các kế hoạch thực hiện bởi toán tử Nested Loops . Toán tử sẽ có thuộc tính Tham chiếu ngoài thay vì tham gia các biến vị ngữ. Các tham chiếu bên ngoài là các tham số được truyền từ phía bên ngoài sang phía bên trong trên mỗi lần lặp của vòng lặp.

  • Tham gia

    Một phép nối đánh giá vị từ nối của nó tại toán tử nối. Việc tham gia thường có thể được thực hiện bởi các toán tử Hash Match , Merge hoặc Nested Loops trong SQL Server.

    Khi các vòng lặp lồng nhau được chọn, nó có thể được phân biệt với một ứng dụng do thiếu các Tham chiếu ngoài (và thường là sự hiện diện của một vị từ tham gia). Đầu vào bên trong của phép nối không bao giờ tham chiếu các giá trị từ đầu vào bên ngoài - bên trong vẫn được thực hiện một lần cho mỗi hàng bên ngoài, nhưng thực thi bên trong không phụ thuộc vào bất kỳ giá trị nào từ hàng ngoài hiện tại.

Để biết thêm chi tiết, xem bài đăng của tôi Áp dụng so với Vòng lặp lồng nhau Tham gia .

... Tại sao có một tham gia bên ngoài trong kế hoạch thực hiện thay vì tham gia bên trong ?

Phép nối ngoài phát sinh khi trình tối ưu hóa chuyển đổi áp dụng cho phép nối (sử dụng quy tắc được gọi ApplyHandler) để xem liệu nó có thể tìm thấy gói dựa trên phép nối rẻ hơn không. Phép nối được yêu cầu là phép nối ngoài cho chính xác khi ứng dụng chứa tổng hợp vô hướng . Một kết nối bên trong sẽ không được đảm bảo để tạo ra kết quả giống như áp dụng ban đầu như chúng ta sẽ thấy.

Tổng hợp vô hướng và vectơ

  • Một tổng hợp không có GROUP BYmệnh đề tương ứng là tổng hợp vô hướng .
  • Một tổng hợp với một GROUP BYmệnh đề tương ứng là một tổng hợp vector .

Trong SQL Server, tổng hợp vô hướng sẽ luôn tạo ra một hàng, ngay cả khi nó không có hàng nào để tổng hợp. Ví dụ, COUNTtổng vô hướng của không có hàng nào bằng không. Tập hợp vectơ COUNT không có hàng là tập hợp trống (không có hàng nào cả).

Các truy vấn đồ chơi sau đây minh họa sự khác biệt. Bạn cũng có thể đọc thêm về tổng hợp vô hướng và vectơ trong bài viết của tôi Vui với vô hướng và vectơ tổng hợp .

-- 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 <> fiddle demo

Chuyển đổi áp dụng để tham gia

Tôi đã đề cập trước đó rằng phép nối được yêu cầu là phép nối ngoài cho chính xác khi áp dụng ban đầu chứa tổng hợp vô hướng . Để chỉ ra lý do tại sao đây là trường hợp chi tiết, tôi sẽ sử dụng một ví dụ đơn giản về truy vấn câu hỏi:

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;

Kết quả chính xác cho cột ckhông , bởi vì COUNT_BIGlà một đại lượng vô hướng tổng hợp. Khi dịch truy vấn áp dụng này thành biểu mẫu tham gia, SQL Server tạo ra một thay thế bên trong trông giống như sau nếu nó được thể hiện trong T-SQL:

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;

Để viết lại ứng dụng dưới dạng tham gia không tương thích, chúng tôi phải giới thiệu một GROUP BYtrong bảng dẫn xuất (nếu không thì không thể có Acột để tham gia). Phép nối phải là phép nối ngoài để mỗi hàng từ bảng @Atiếp tục tạo ra một hàng trong đầu ra. Phép nối trái sẽ tạo ra một NULLcột for ckhi vị từ nối không đánh giá là đúng. Điều đó NULLcần được dịch thành 0 bằng cách COALESCEhoàn thành một chuyển đổi chính xác từ áp dụng .

Bản demo dưới đây cho thấy cả hai phép nối ngoài và COALESCEđược yêu cầu để tạo ra cùng một kết quả bằng cách sử dụng phép nối như truy vấn áp dụng ban đầu :

db <> fiddle demo

Với GROUP BY

... tại sao việc không chú ý đến nhóm theo mệnh đề dẫn đến sự tham gia bên trong?

Tiếp tục ví dụ đơn giản hóa, nhưng thêm một 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;

Các COUNT_BIGbây giờ là một véc tơ tổng hợp, vì vậy kết quả chính xác cho một bộ đầu vào trống rỗng không còn bằng không, nó là không có hàng ở tất cả . Nói cách khác, chạy các câu lệnh trên không tạo ra đầu ra.

Các ngữ nghĩa này dễ dàng hơn nhiều để tôn vinh khi dịch từ áp dụng sang tham gia , vì CROSS APPLYtự nhiên từ chối bất kỳ hàng bên ngoài nào không tạo ra các hàng bên trong. Do đó, chúng ta có thể sử dụng một phép nối bên trong một cách an toàn ngay bây giờ, không có phép chiếu biểu thức phụ:

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

Bản demo dưới đây cho thấy việc viết lại tham gia bên trong tạo ra kết quả giống như áp dụng ban đầu với tổng hợp vectơ:

db <> fiddle demo

Trình tối ưu hóa tình cờ chọn một phép nối bên trong hợp nhất với bảng nhỏ vì nó tìm thấy một kế hoạch tham gia giá rẻ một cách nhanh chóng (tìm thấy kế hoạch đủ tốt). Trình tối ưu hóa dựa trên chi phí có thể tiếp tục viết lại liên kết trở lại cho một ứng dụng - có thể tìm thấy một kế hoạch áp dụng rẻ hơn, vì nó sẽ ở đây nếu sử dụng một tham gia vòng lặp hoặc gợi ý lực lượng - nhưng nó không đáng để nỗ lực trong trường hợp này.

Ghi chú

Các ví dụ đơn giản sử dụng các bảng khác nhau với các nội dung khác nhau để hiển thị sự khác biệt về ngữ nghĩa rõ ràng hơn.

Người ta có thể lập luận rằng trình tối ưu hóa phải có khả năng lý giải về việc tự tham gia không có khả năng tạo ra bất kỳ hàng nào không khớp (không tham gia), nhưng ngày nay nó không chứa logic đó. Truy cập cùng một bảng nhiều lần trong một truy vấn không được đảm bảo để tạo ra cùng một kết quả nói chung, tùy thuộc vào mức độ cô lập và hoạt động đồng thời.

Trình tối ưu hóa lo lắng về các ngữ nghĩa và trường hợp cạnh này vì vậy bạn không cần phải làm vậy.


Tiền thưởng: Gói áp dụng nội bộ

SQL Server có thể tạo ra một kế hoạch áp dụng bên trong (không phải là một kế hoạch tham gia bên trong !) Cho truy vấn mẫu, nó chỉ chọn không vì lý do chi phí. Chi phí của kế hoạch tham gia bên ngoài được hiển thị trong câu hỏi là 0,02898 đơn vị trên phiên bản SQL Server 2017 của máy tính xách tay của tôi.

Bạn có thể buộc một kế hoạch áp dụng (tham gia tương quan) bằng cách sử dụng cờ theo dõi không có giấy tờ và không được hỗ trợ 9114 (vô hiệu hóa, ApplyHandlerv.v.) chỉ để minh họa:

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

Điều này tạo ra một kế hoạch vòng lặp lồng nhau áp dụng với một chỉ số lười biếng. Tổng chi phí ước tính là 0,0463983 (cao hơn gói đã chọn):

Index Spool áp dụng kế hoạch

Lưu ý rằng kế hoạch thực hiện sử dụng các vòng lặp lồng nhau áp dụng tạo ra kết quả chính xác bằng cách sử dụng ngữ nghĩa "nối bên trong" bất kể sự hiện diện của GROUP BYmệnh đề.

Trong thế giới thực, chúng ta thường có một chỉ mục để hỗ trợ tìm kiếm ở phía bên trong ứng dụng để khuyến khích SQL Server chọn tùy chọn này một cách tự nhiên, ví dụ:

CREATE INDEX i ON #MyTable (Col_A, Col_B);

db <> fiddle demo


-3

Áp dụng chéo là một hoạt động hợp lý trên dữ liệu. Khi quyết định cách lấy dữ liệu đó, SQL Server chọn toán tử vật lý phù hợp để lấy dữ liệu bạn muốn.

Không có toán tử ứng dụng vật lý và SQL Server chuyển nó thành toán tử nối thích hợp và hy vọng hiệu quả.

Bạn có thể tìm thấy một danh sách các toán tử vật lý trong liên kết dưới đây.

https://docs.microsoft.com/en-us/sql/relational-database/showplan-logical-and-physical-operators-reference?view=sql-server-2017

Trình tối ưu hóa truy vấn tạo ra một kế hoạch truy vấn dưới dạng cây bao gồm các toán tử logic. Sau khi trình tối ưu hóa truy vấn tạo kế hoạch, trình tối ưu hóa truy vấn sẽ chọn toán tử vật lý hiệu quả nhất cho mỗi toán tử logic. Trình tối ưu hóa truy vấn sử dụng cách tiếp cận dựa trên chi phí để xác định toán tử vật lý nào sẽ thực hiện toán tử logic.

Thông thường, một hoạt động logic có thể được thực hiện bởi nhiều toán tử vật lý. Tuy nhiên, trong những trường hợp hiếm hoi, một toán tử vật lý cũng có thể thực hiện nhiều hoạt động logic.

chỉnh sửa / Có vẻ như tôi hiểu câu hỏi của bạn sai. Máy chủ SQL thường sẽ chọn toán tử thích hợp nhất. Truy vấn của bạn không cần trả về giá trị cho tất cả các kết hợp của cả hai bảng khi sử dụng phép nối chéo. Chỉ cần tính toán giá trị bạn muốn cho mỗi hàng là đủ những gì được thực hiện ở đây.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.