2012-02-14 2 views
85

Ho guardato allo CROSS/OUTER APPLY con un collega e stiamo lottando per trovare esempi di vita reale su dove utilizzarli.Esempio di vita reale, quando utilizzare OUTER/CROSS APPLY in SQL

Ho passato molto tempo a guardare When should I use Cross Apply over Inner Join? e googling ma l'esempio principale (solo) sembra piuttosto bizzarro (utilizzando il conteggio delle righe da una tabella per determinare quante righe selezionare da un'altra tabella).

Ho pensato che questo scenario può beneficiare di OUTER APPLY:

Contatti Tabella (contiene 1 record per ogni contatto) Comunicazione voci della tabella (può contenere n telefono, fax, e-mail fro ogni contatto)

Ma usando subquery, espressioni di tabella comuni, OUTER JOIN con RANK() e OUTER APPLY sembrano tutte uguali. Sto indovinando questo significa che lo scenario non è applicabile a APPLY.

Si prega di condividere alcuni esempi di vita reale e aiutare a spiegare la funzione!

+3

"top n per gruppo" o analisi XML è comune. Vedi alcune delle mie risposte http://stackoverflow.com/search?tab=votes&q=user%3a27535%20%22cross%20apply%22%20or%20%22outer%20apply%22 – gbn

+0

http://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ – mehrdad

+0

http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/ –

risposta

125

Alcuni usi per APPLY sono ...

1)Top N per group queries (può essere più efficiente per alcuni cardinalità)

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) Chiamata di una tabella funzione valutata per ogni riga la query esterna

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

3)Reusing a column alias

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)Unpivoting more than one group of columns

Assume 1NF violare struttura della tabella ....

CREATE TABLE T 
    (
    Id INT PRIMARY KEY, 

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

Esempio utilizzando 2008+ VALUES sintassi.

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

Nel 2005 è possibile utilizzare UNION ALL.

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

Una bella lista di usi lì, ma la chiave sono gli esempi di vita reale - mi piacerebbe vederne una per ciascuna. –

+0

Per il numero 1 questo può essere ottenuto ugualmente usando rank, sottoquery o espressioni di tabella comuni? Puoi fornire un esempio quando questo non è vero? –

+0

@LeeTickett - Si prega di leggere il link. Ha una discussione di 4 pagine su quando preferiresti l'una all'altra. –

6

Un esempio di vita reale sarebbe se si ha uno scheduler e voleva vedere ciò che la voce di registro più recente è stato per ogni operazione pianificata.

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 
+0

nei nostri test abbiamo sempre trovato join con la funzione window più efficiente per top n (ho pensato che sarebbe sempre stato vero in quanto apply e subquery sono entrambi cursive/richiedono cicli annidati). anche se penso che ora potrei averlo rotto ... grazie al collegamento di Martin che suggerisce se non stai restituendo l'intera tabella e non ci sono indici ottimali sul tavolo, allora il numero di letture sarebbe molto più piccolo usando cross apply (o una sottoquery se in alto n dove n = 1) –

+0

Ho qui essenzialmente questa query e sicuramente non esegue alcuna sottoquery con cicli annidati. Data la tabella di registro ha un PK di taskID e lastUpdateDate, è un'operazione molto veloce. Come riformeresti quella query per utilizzare una funzione finestra? – BJury

+1

select * dal compito t join interno (selezionare TaskId, logresult, LASTUPDATEDATE, rango() su (partizione per ordine taskid dal disc LASTUPDATEDATE) _rank) lg su lg.taskid = t.taskid e lg._rank = 1 –

4

Per rispondere al punto precedente bussare fino un esempio:

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 

E ora eseguire i due query con un piano di esecuzione.

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 

Si può vedere che la query di applicazione esterna è più efficiente. (Non posso allegare il piano dato che sono un nuovo utente ... Doh.)

+0

mi interessa il piano di esecuzione: sai perché la soluzione rank() esegue una scansione dell'indice e un ordinamento costoso anziché esterno si applica a un indice che cerca e non sembra fare un ordinamento (anche se è necessario perché è possibile fare un top senza un ordinamento?) –

+0

L'applicazione esterna non ha bisogno di eseguire un ordinamento, in quanto può utilizzare l'indice nella tabella sottostante. Presumibilmente, la query con la funzione rank() deve elaborare l'intera tabella per garantire che le sue classifiche siano corrette. – BJury

+0

non puoi fare una cima senza un ordinamento.anche se il tuo punto sull'elaborazione dell'intero tavolo potrebbe essere vero, mi sorprenderebbe (so che lo sql optimizer/compiler può deludere di tanto in tanto ma questo sarebbe un comportamento pazzesco) –

60

Ci sono varie situazioni in cui non è possibile evitare CROSS APPLY o OUTER APPLY.

Considerate che avete due tabelle.

MASTER TABELLA

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

DATI TABELLA

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          



                                                                                                                        CROSS APPLICA

ci sono molte situazioni in cui abbiamo bisogno di sostituire INNER JOIN con CROSS APPLY.

1. Se vogliamo unire 2 tabelle su TOP n risultati con INNER JOIN funzionalità

considerare se dobbiamo selezionare Id e Name da Master e le ultime due date per ogni Id da 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 

La query sopra riportata genera il seguente risultato.

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 

Sede, ha generato risultati per due date con due ultime data Id e poi si è unito questi record solo in query esterna su Id, che è sbagliato. Per fare ciò, dobbiamo usare 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 

e forme che segue risultato.

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 

Ecco il funzionamento. La query all'interno di CROSS APPLY può fare riferimento alla tabella esterna, dove INNER JOIN non può eseguire questa operazione (genera errore di compilazione). Quando si trovano le ultime due date, l'unione viene eseguita all'interno di CROSS APPLY ie, WHERE M.ID=D.ID.

2. Quando è necessaria la funzionalità INNER JOIN tramite le funzioni.

CROSS APPLY può essere usato come una sostituzione con INNER JOIN quando abbiamo bisogno di ottenere il risultato di Master tavolo e un function.

Ed ecco la funzione

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

che ha generato il seguente risultato

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 



                                                                                                                        ESTERNO APPLICABILE

1. Se vogliamo unire 2 tabelle su TOP n risultati con LEFT JOIN funzionalità

Considerare se è necessario selezionare Id e Nome da Master e le ultime due date per ogni Id dalla tabella Details.

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 

che forma il seguente risultato

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 

Questo porterà risultati errati esempio, che porterà solo i dati più recenti due date da Details tabella indipendentemente Id anche se ci uniamo con Id. Quindi la soluzione corretta sta usando 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 

che forma il seguente risultato desiderato

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. Quando occorre LEFT JOIN funzionalità utilizzando functions.

OUTER APPLY può essere usato come una sostituzione con LEFT JOIN quando abbiamo bisogno di ottenere il risultato di Master tavolo e un function.

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

E la funzione va qui.

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

che ha generato il seguente risultato

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 



                                                          caratteristica comune delle CROSS APPLY e OUTER APPLY

CROSS APPLY o OUTER APPLY possono essere utilizzati per mantenere NULL valori quando unpivoting, intercambiabili.

considera di avere una tabella sottostante

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 

Quando si utilizza UNPIVOT di portare FROMDATE E TODATE a una colonna, si occuperà di eliminare NULL valori di default.

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

che genera il risultato di seguito.Si noti che abbiamo perso il record di Id numero 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 

In questi casi un CROSS APPLY o OUTER APPLY sarà utile

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

che forma il seguente risultato e mantiene Id quando il suo valore è 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 
+0

Invece di pubblicare la stessa risposta esatta su due domande, perché non contrassegnarne una come duplicata? –

+1

Trovo che questa risposta sia più adatta a rispondere alla domanda originale. I suoi esempi mostrano scenari di "vita reale". – FrankO