Ví dụ thực tế, khi nào nên sử dụng OUTER / CROSS ỨNG DỤNG trong SQL


124

Tôi đã xem xét CROSS / OUTER APPLYvới một đồng nghiệp và chúng tôi đang vật lộn để tìm các ví dụ thực tế về nơi sử dụng chúng.

Tôi đã dành khá nhiều thời gian để xem Khi nào tôi nên sử dụng Cross Apply trên Inside Join? và googling nhưng ví dụ chính (chỉ) có vẻ khá kỳ quái (sử dụng hàng đếm từ một bảng để xác định có bao nhiêu hàng để chọn từ một bảng khác).

Tôi nghĩ kịch bản này có thể được hưởng lợi từ OUTER APPLY:

Bảng liên hệ (chứa 1 bản ghi cho mỗi số liên lạc) Bảng mục nhập liên lạc (có thể chứa n điện thoại, fax, email qua từng liên hệ)

Nhưng sử dụng các truy vấn con, biểu thức bảng chung, OUTER JOINvới RANK()OUTER APPLYtất cả dường như thực hiện như nhau. Tôi đoán điều này có nghĩa là kịch bản không áp dụng được APPLY.

Hãy chia sẻ một số ví dụ thực tế và giúp giải thích tính năng này!


5
"top n mỗi nhóm" hoặc phân tích cú pháp XML là phổ biến. Xem một số câu trả lời của tôi stackoverflow.com/
Kẻ




Câu trả lời:


174

Một số sử dụng cho APPLY...

1) N truy vấn hàng đầu trên mỗi nhóm (có thể hiệu quả hơn đối với một số yếu tố chính)

SELECT pr.name,
       pa.name
FROM   sys.procedures pr
       OUTER APPLY (SELECT TOP 2 *
                    FROM   sys.parameters pa
                    WHERE  pa.object_id = pr.object_id
                    ORDER  BY pr.name) pa
ORDER  BY pr.name,
          pa.name 

2) Gọi một hàm có giá trị bảng cho mỗi hàng trong truy vấn bên ngoài

SELECT *
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)

3) Sử dụng lại bí danh cột

SELECT number,
       doubled_number,
       doubled_number_plus_one
FROM master..spt_values
CROSS APPLY (SELECT 2 * CAST(number AS BIGINT)) CA1(doubled_number)  
CROSS APPLY (SELECT doubled_number + 1) CA2(doubled_number_plus_one)  

4) Hủy liên kết nhiều hơn một nhóm cột

Giả sử cấu trúc bảng vi phạm 1NF ....

CREATE TABLE T
  (
     Id   INT PRIMARY KEY,

     Foo1 INT, Foo2 INT, Foo3 INT,
     Bar1 INT, Bar2 INT, Bar3 INT
  ); 

Ví dụ sử dụng VALUEScú pháp 2008+ .

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (VALUES(Foo1, Bar1),
                          (Foo2, Bar2),
                          (Foo3, Bar3)) V(Foo, Bar); 

Trong năm 2005 UNION ALLcó thể được sử dụng thay thế.

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (SELECT Foo1, Bar1 
                    UNION ALL
                    SELECT Foo2, Bar2 
                    UNION ALL
                    SELECT Foo3, Bar3) V(Foo, Bar);

1
Một danh sách tốt đẹp về sử dụng ở đó nhưng chìa khóa là những ví dụ thực tế - tôi rất muốn thấy một cái cho mỗi cái.
Lee Tickett

Đối với # 1, điều này có thể đạt được bằng cách sử dụng thứ hạng, truy vấn con hoặc biểu thức bảng chung? Bạn có thể cung cấp một ví dụ khi điều này không đúng?
Lee Tickett

@LeeTickett - Vui lòng đọc liên kết. Nó có một cuộc thảo luận gồm 4 trang về thời điểm bạn thích cái này hơn cái khác.
Martin Smith

1
Đảm bảo truy cập vào liên kết có trong ví dụ # 1. Tôi đã sử dụng cả hai cách tiếp cận này (ROW OVER và CROSS ỨNG DỤNG) với cả hai cách thực hiện tốt trong các tình huống khác nhau, nhưng tôi chưa bao giờ hiểu tại sao chúng thực hiện khác nhau. Bài báo đó đã được gửi từ thiên đàng !! Việc tập trung vào lập chỉ mục phù hợp theo thứ tự theo hướng đã giúp ích rất nhiều cho các truy vấn có cấu trúc "phù hợp" nhưng vấn đề về hiệu năng khi được truy vấn. Cảm ơn bạn đã bao gồm nó !!
Chris Porter

1
@mr_eclair có vẻ như bây giờ tại itprotoday.com/software-development/iêu
Martin Smith

87

Có nhiều tình huống mà bạn không thể tránh CROSS APPLYhoặc OUTER APPLY.

Hãy xem xét bạn có hai bảng.

BẢNG MASTER

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x

BẢNG CHI TIẾT

x------x--------------------x-------x
| Id   |      PERIOD        |   QTY |
x------x--------------------x-------x
|  1   |   2014-01-13       |   10  |
|  1   |   2014-01-11       |   15  |
|  1   |   2014-01-12       |   20  |
|  2   |   2014-01-06       |   30  |
|  2   |   2014-01-08       |   40  |
x------x--------------------x-------x                                       



                                                            ÁP DỤNG CROSS

Có nhiều tình huống chúng ta cần thay thế INNER JOINbằng CROSS APPLY.

1. Nếu chúng tôi muốn tham gia 2 bảng về TOP nkết quả với INNER JOINchức năng

Xem xét nếu chúng ta cần chọn IdNametừ Mastervà hai ngày cuối cùng cho mỗi Idtừ Details table.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
INNER JOIN
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D      
    ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID

Các truy vấn trên tạo ra kết quả sau.

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
x------x---------x--------------x-------x

Xem, nó đã tạo kết quả cho hai ngày cuối cùng với hai ngày cuối cùng Idvà sau đó chỉ tham gia các bản ghi này trong truy vấn bên ngoài Id, điều này là sai. Để thực hiện điều này, chúng ta cần sử dụng CROSS APPLY.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
CROSS APPLY
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    WHERE M.ID=D.ID
    ORDER BY CAST(PERIOD AS DATE)DESC
)D

và hình thức anh ta theo kết quả.

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
x------x---------x--------------x-------x

Đây là công việc. Truy vấn bên trong CROSS APPLYcó thể tham chiếu bảng bên ngoài, nơi INNER JOINkhông thể thực hiện điều này (ném lỗi biên dịch). Khi tìm thấy hai ngày cuối cùng, việc tham gia được thực hiện bên trong CROSS APPLYtức là WHERE M.ID=D.ID.

2. Khi chúng ta cần INNER JOINchức năng sử dụng các chức năng.

CROSS APPLYcó thể được sử dụng thay thế INNER JOINkhi chúng ta cần lấy kết quả từ Masterbảng và a function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C

Và đây là chức năng

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

tạo ra kết quả sau

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-11   |  15   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-06   |  30   |
|   2  |   B     | 2014-01-08   |  40   |
x------x---------x--------------x-------x



                                                            ỨNG DỤNG NGOÀI

1. Nếu chúng tôi muốn tham gia 2 bảng về TOP nkết quả với LEFT JOINchức năng

Xem xét nếu chúng ta cần chọn Id và Tên từ Mastervà hai ngày cuối cùng cho mỗi Id từ Detailsbảng.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
LEFT JOIN
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID

tạo thành kết quả sau

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     |   NULL       |  NULL |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

Điều này sẽ mang lại kết quả sai, tức là nó sẽ chỉ mang lại dữ liệu hai ngày mới nhất từ Detailsbảng bất kể Idchúng ta tham gia cùng Id. Vì vậy, giải pháp thích hợp là sử dụng OUTER APPLY.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
OUTER APPLY
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    WHERE M.ID=D.ID
    ORDER BY CAST(PERIOD AS DATE)DESC
)D

tạo thành kết quả mong muốn sau đây

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

2. Khi chúng ta cần LEFT JOINchức năng sử dụng functions.

OUTER APPLYcó thể được sử dụng thay thế LEFT JOINkhi chúng ta cần lấy kết quả từ Masterbảng và a function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
OUTER APPLY dbo.FnGetQty(M.ID) C

Và chức năng ở đây.

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

tạo ra kết quả sau

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-11   |  15   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-06   |  30   |
|   2  |   B     | 2014-01-08   |  40   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x



                             Đặc điểm chung của CROSS APPLYOUTER APPLY

CROSS APPLYhoặc OUTER APPLYcó thể được sử dụng để giữ lại NULLcác giá trị khi không xoay vòng, có thể hoán đổi cho nhau.

Xem xét bạn có bảng dưới đây

x------x-------------x--------------x
|  Id  |   FROMDATE  |   TODATE     |
x------x-------------x--------------x
|   1  |  2014-01-11 | 2014-01-13   | 
|   1  |  2014-02-23 | 2014-02-27   | 
|   2  |  2014-05-06 | 2014-05-30   |    
|   3  |   NULL      |   NULL       | 
x------x-------------x--------------x

Khi bạn sử dụng UNPIVOTđể đưa FROMDATEAND TODATEđến một cột, nó sẽ loại bỏ NULLcác giá trị theo mặc định.

SELECT ID,DATES
FROM MYTABLE
UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P

tạo ra kết quả dưới đây. Lưu ý rằng chúng tôi đã bỏ lỡ hồ sơ Idsố3

  x------x-------------x
  | Id   |    DATES    |
  x------x-------------x
  |  1   |  2014-01-11 |
  |  1   |  2014-01-13 |
  |  1   |  2014-02-23 |
  |  1   |  2014-02-27 |
  |  2   |  2014-05-06 |
  |  2   |  2014-05-30 |
  x------x-------------x

Trong những trường hợp như vậy, một CROSS APPLYhoặc OUTER APPLYsẽ hữu ích

SELECT DISTINCT ID,DATES
FROM MYTABLE 
OUTER APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)

tạo thành kết quả sau và giữ nguyên Idgiá trị của nó3

  x------x-------------x
  | Id   |    DATES    |
  x------x-------------x
  |  1   |  2014-01-11 |
  |  1   |  2014-01-13 |
  |  1   |  2014-02-23 |
  |  1   |  2014-02-27 |
  |  2   |  2014-05-06 |
  |  2   |  2014-05-30 |
  |  3   |     NULL    |
  x------x-------------x

Thay vì đăng cùng một câu trả lời chính xác cho hai câu hỏi, tại sao không gắn cờ một câu trùng lặp?
Tab Alleman

2
Tôi thấy câu trả lời này có thể áp dụng nhiều hơn để trả lời câu hỏi ban đầu. Các ví dụ của nó cho thấy các tình huống 'đời thực'.
FrankO 2/2/2016

Vì vậy, để làm rõ. Kịch bản "top n"; điều đó có thể được thực hiện với phép nối trái / trong nhưng sử dụng "row_number over phân vùng theo id" và sau đó chọn "WHERE M.RowNumber <3" hoặc một cái gì đó tương tự?
Chaitanya

1
Câu trả lời tuyệt vời tổng thể! Chắc chắn đây là một câu trả lời tốt hơn câu trả lời được chấp nhận, bởi vì nó là: đơn giản, với các ví dụ trực quan tiện dụng và giải thích.
Asen Khachaturyan

8

Một ví dụ thực tế sẽ là nếu bạn có một trình lập lịch biểu và muốn xem mục nhật ký gần đây nhất là gì cho mỗi tác vụ theo lịch trình.

select t.taskName, lg.logResult, lg.lastUpdateDate
from task t
cross apply (select top 1 taskID, logResult, lastUpdateDate
             from taskLog l
             where l.taskID = t.taskID
             order by lastUpdateDate desc) lg

trong các thử nghiệm của chúng tôi, chúng tôi luôn thấy tham gia với chức năng cửa sổ hiệu quả nhất cho top n (tôi nghĩ điều này sẽ luôn đúng vì áp dụng và truy vấn con đều là các vòng lặp / yêu cầu lồng nhau). mặc dù tôi nghĩ rằng bây giờ tôi có thể đã bẻ khóa nó ... nhờ vào liên kết của Martin gợi ý rằng nếu bạn không trả lại toàn bộ bảng và không có chỉ mục tối ưu trên bảng thì số lần đọc sẽ nhỏ hơn nhiều khi sử dụng áp dụng chéo (hoặc một truy vấn con nếu top n trong đó n = 1)
Lee Tickett

Về cơ bản, tôi đã có truy vấn đó và chắc chắn nó không thực hiện bất kỳ truy vấn con nào với các vòng lặp lồng nhau. Với bảng nhật ký có PK của taskID và lastUpdateDate, đây là một thao tác rất nhanh. Làm thế nào bạn sẽ cải cách truy vấn đó để sử dụng một chức năng cửa sổ?
BJury

2
chọn * từ tác vụ tham gia bên trong (chọn taskid, logresult, lastupdatedate, rank () over (phân vùng theo thứ tự taskid của lastupdatedate desc) _rank) lg trên lg.taskid = t.taskid và lg._rank = 1
Lee Tickett

5

Để trả lời điểm trên gõ lên một ví dụ:

create table #task (taskID int identity primary key not null, taskName varchar(50) not null)
create table #log (taskID int not null, reportDate datetime not null, result varchar(50) not null, primary key(reportDate, taskId))

insert #task select 'Task 1'
insert #task select 'Task 2'
insert #task select 'Task 3'
insert #task select 'Task 4'
insert #task select 'Task 5'
insert #task select 'Task 6'

insert  #log
select  taskID, 39951 + number, 'Result text...'
from    #task
        cross join (
            select top 1000 row_number() over (order by a.id) as number from syscolumns a cross join syscolumns b cross join syscolumns c) n

Và bây giờ chạy hai truy vấn với một kế hoạch thực hiện.

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        left join (select taskID, reportDate, result, rank() over (partition by taskID order by reportDate desc) rnk from #log) lg
            on lg.taskID = t.taskID and lg.rnk = 1

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        outer apply (   select  top 1 l.*
                        from    #log l
                        where   l.taskID = t.taskID
                        order   by reportDate desc) lg

Bạn có thể thấy rằng truy vấn áp dụng bên ngoài là hiệu quả hơn. (Không thể đính kèm gói vì tôi là người dùng mới ... Doh.)


kế hoạch thực hiện làm tôi quan tâm - bạn có biết tại sao giải pháp xếp hạng () thực hiện quét chỉ mục và một loại đắt tiền chứ không phải áp dụng bên ngoài mà chỉ mục tìm kiếm và dường như không thực hiện một loại (mặc dù phải vì bạn có thể ' t làm một đỉnh mà không có một loại?)
Lee Tickett

1
Áp dụng bên ngoài không cần thực hiện sắp xếp, vì nó có thể sử dụng chỉ mục trên bảng bên dưới. Có lẽ truy vấn với hàm rank () cần xử lý toàn bộ bảng để đảm bảo thứ hạng của nó là chính xác.
BJury

bạn không thể làm một đỉnh mà không có một loại. Mặc dù quan điểm của bạn về việc xử lý toàn bộ bảng COULD là đúng nhưng điều đó sẽ làm tôi ngạc nhiên (tôi biết trình tối ưu hóa / trình biên dịch sql có thể gây thất vọng theo thời gian nhưng đây sẽ là hành vi điên rồ)
Lee Tickett

2
Bạn có thể lên đỉnh mà không cần sắp xếp khi dữ liệu mà nhóm của bạn chống lại một chỉ mục, vì trình tối ưu hóa biết nó đã được sắp xếp theo nghĩa đen chỉ cần rút mục nhập đầu tiên (hoặc cuối cùng) khỏi chỉ mục.
BJury
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.