Esempio di vita reale, quando utilizzare OUTER / CROSS APPLY in SQL


124

Ho guardato CROSS / OUTER APPLYcon un collega e stiamo lottando per trovare esempi di vita reale su dove usarli.

Ho passato parecchio tempo a guardare Quando dovrei usare Cross Apply su Inner Join? e googling ma l'esempio (unico) principale sembra piuttosto bizzarro (usando il conteggio delle righe da una tabella per determinare quante righe selezionare da un'altra tabella).

Ho pensato che questo scenario potesse beneficiare di OUTER APPLY:

Tabella dei contatti (contiene 1 record per ciascun contatto) Tabella delle voci di comunicazione (può contenere n telefono, fax, e-mail per ciascun contatto)

Ma usando subquery, espressioni di tabella comuni, OUTER JOINcon RANK()e OUTER APPLYtutte sembrano funzionare allo stesso modo. Immagino che questo significhi che lo scenario non è applicabile APPLY.

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


5
"top n per group" o analisi di XML è comune. Vedi alcune delle mie risposte stackoverflow.com/…
gbn




Risposte:


174

Alcuni usi per APPLYsono ...

1) Principali query N per gruppo (può essere più efficiente per alcune 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) Chiamare una funzione con valori di tabella per ogni riga nella query esterna

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

3) Riutilizzare un alias di colonna

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) Annullamento di più gruppi di colonne

Suppone che 1NF violi la struttura della tabella ....

CREATE TABLE T
  (
     Id   INT PRIMARY KEY,

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

Esempio usando la VALUESsintassi 2008+ .

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

Nel 2005 UNION ALLpuò essere utilizzato invece.

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
Un bel elenco di usi lì, ma la chiave sono gli esempi di vita reale, mi piacerebbe vederne uno per ciascuno.
Lee Tickett,

Per il n. 1 questo obiettivo può essere raggiunto allo stesso modo usando rank, sottoquery o espressioni di tabella comuni? Puoi fornire un esempio quando questo non è vero?
Lee Tickett,

@LeeTickett - Leggi il link. Ha una discussione di 4 pagine su quando preferiresti l'uno all'altro.
Martin Smith,

1
Assicurati di visitare il link incluso nell'esempio n. 1. Ho usato entrambi questi approcci (ROW OVER e CROSS APPLY), entrambi con buoni risultati in vari scenari, ma non ho mai capito perché si comportino diversamente. Quell'articolo è stato inviato dal cielo !! L'attenzione per una corretta indicizzazione corrispondente all'ordine per indicazioni ha aiutato in larga misura per le query che hanno una struttura "corretta" ma problemi di prestazioni quando vengono interrogate. Grazie per averlo incluso !!
Chris Porter,

1
@mr_eclair sembra che sia ora su itprotoday.com/software-development/…
Martin Smith,

87

Ci sono varie situazioni in cui non puoi evitare CROSS APPLYo OUTER APPLY.

Considera di avere due tavoli.

TABELLA MASTER

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

TABELLA DETTAGLI

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                                       



                                                            APPLICAZIONE CROCE

Ci sono molte situazioni in cui abbiamo bisogno di sostituire INNER JOINcon CROSS APPLY.

1. Se vogliamo unire 2 tabelle sui TOP nrisultati con INNER JOINfunzionalità

Considera se dobbiamo selezionare Ide Nameda Mastere ultime due date per ognuna Idda 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 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

Vedi, ha generato risultati per le ultime due date con le ultime due date Ide poi ha unito questi record solo nella query esterna attiva Id, il 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 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     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
x------x---------x--------------x-------x

Ecco il lavoro. La query interna CROSS APPLYpuò fare riferimento alla tabella esterna, dove INNER JOINnon è possibile farlo (genera errore di compilazione). Quando si trovano le ultime due date, l'unione avviene all'interno CROSS APPLY, ad es WHERE M.ID=D.ID.

2. Quando abbiamo bisogno di INNER JOINfunzionalità usando le funzioni.

CROSS APPLYpuò essere usato in sostituzione di INNER JOINquando abbiamo bisogno di ottenere risultati dalla Mastertabella e a function.

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

Ed ecco la funzione

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

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



                                                            APPLICARE ESTERNO

1. Se vogliamo unire 2 tabelle sui TOP nrisultati con LEFT JOINfunzionalità

Considera se dobbiamo selezionare Id e Nome da Mastere le ultime due date per ogni ID dalla Detailstabella.

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

Ciò porterà a risultati errati, cioè porterà solo i dati delle ultime due date dalla Detailstabella, indipendentemente dal fatto che Idci uniamo Id. Quindi sta usando la soluzione corretta 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 costituisce 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 abbiamo bisogno di LEFT JOINfunzionalità utilizzando functions.

OUTER APPLYpuò essere usato in sostituzione di LEFT JOINquando abbiamo bisogno di ottenere risultati dalla Mastertabella e a 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 ID=@Id
)

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 di CROSS APPLYeOUTER APPLY

CROSS APPLYo OUTER APPLYpuò essere usato per conservare NULLvalori quando non si fa perno, che sono intercambiabili.

Considera di avere la tabella seguente

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 UNPIVOTper portare FROMDATEAND TODATEin una colonna, eliminerà i NULLvalori per impostazione predefinita.

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

che genera il risultato seguente. Si noti che abbiamo perso il record del Idnumero3

  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 tali casi un CROSS APPLYo OUTER APPLYsarà utile

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

che forma il seguente risultato e conserva Iddove si trova il suo valore3

  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

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

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

Quindi per chiarire. Lo scenario "top n"; questo potrebbe essere fatto con join left / inner ma usando un "row_number over partition by id" e selezionando quindi "WHERE M.RowNumber <3" o qualcosa del genere?
Chaitanya,

1
Ottima risposta nel complesso! Sicuramente questa è una risposta migliore di quella accettata, perché è: semplice, con utili esempi visivi e spiegazioni.
Arsen Khachaturyan,

8

Un esempio di vita reale sarebbe se tu avessi uno scheduler e volessi vedere quale fosse la voce di registro più recente per ogni attività 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

nei nostri test abbiamo sempre trovato il join con la funzione finestra il più efficiente per top n (ho pensato che sarebbe sempre vero in quanto applicare e subquery sono entrambi corsivi / richiedono cicli nidificati). anche se penso che ora avrei potuto risolverlo ... grazie al collegamento di Martin che suggerisce se non stai restituendo l'intero tavolo e non ci sono indici ottimali sul tavolo, il numero di letture sarebbe molto più piccolo usando cross apply (o una sottoquery se top n dove n = 1)
Lee Tickett,

Ho essenzialmente quella query qui e certamente non esegue alcuna subquery con loop nidificati. Dato che la tabella dei registri ha un PK di taskID e lastUpdateDate, è un'operazione molto rapida. Come riformuteresti quella query per usare una funzione di finestra?
Giuria

2
seleziona * da task t inner join (seleziona taskid, logresult, lastupdatedate, rank () over (partizione per ordine taskid secondo lastdate data desc) _rank) lg on lg.taskid = t.taskid e lg._rank = 1
Lee Tickett

5

Per rispondere al punto sopra, fai 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 esegui le 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

Puoi vedere che la query di applicazione esterna è più efficiente. (Impossibile allegare il piano perché sono un nuovo utente ... Doh.)


il piano di esecuzione mi interessa- sai perché la soluzione rank () esegue una scansione dell'indice e un ordinamento costoso rispetto all'applicativo esterno che cerca un indice e non sembra fare un ordinamento (anche se è necessario perché puoi " fare un top senza una specie?)
Lee Tickett

1
L'applicazione esterna non deve eseguire un ordinamento, poiché può utilizzare l'indice sulla tabella sottostante. Presumibilmente la query con la funzione rank () deve elaborare l'intera tabella per assicurarsi che le sue classifiche siano corrette.
Giuria

non puoi fare un top senza una specie. anche se il tuo punto sull'elaborazione dell'intera tabella POTREBBE essere vero, mi sorprenderebbe (so che l'ottimizzatore / compilatore sql può deludere di tanto in tanto ma questo sarebbe un comportamento folle)
Lee Tickett,

2
Puoi eseguire il top senza un ordinamento quando i dati per cui il tuo raggruppamento si trova su un indice, poiché l'ottimizzatore sa che è già stato ordinato, quindi letteralmente deve solo estrarre la prima (o l'ultima) voce dall'indice.
Giuria
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.