Quando dovrei usare cross apply su inner join?


925

Qual è lo scopo principale dell'utilizzo di CROSS APPLY ?

Ho letto (vagamente, tramite post su Internet) che cross applypuò essere più efficiente quando si selezionano set di dati di grandi dimensioni se si esegue il partizionamento. (Mi viene in mente il paging)

So anche che CROSS APPLYnon richiede un UDF come tabella di destra.

Nella maggior parte delle INNER JOINquery (relazioni uno-a-molti), potrei riscriverle per l'uso CROSS APPLY, ma mi danno sempre piani di esecuzione equivalenti.

Qualcuno può darmi un buon esempio di quando CROSS APPLYfa la differenza in quei casi in cui INNER JOINfunzionerà anche?


Modificare:

Ecco un esempio banale, in cui i piani di esecuzione sono esattamente gli stessi. (Mostramene uno dove differiscono e dove cross applyè più veloce / più efficiente)

create table Company (
    companyId int identity(1,1)
,   companyName varchar(100)
,   zipcode varchar(10) 
,   constraint PK_Company primary key (companyId)
)
GO

create table Person (
    personId int identity(1,1)
,   personName varchar(100)
,   companyId int
,   constraint FK_Person_CompanyId foreign key (companyId) references dbo.Company(companyId)
,   constraint PK_Person primary key (personId)
)
GO

insert Company
select 'ABC Company', '19808' union
select 'XYZ Company', '08534' union
select '123 Company', '10016'


insert Person
select 'Alan', 1 union
select 'Bobby', 1 union
select 'Chris', 1 union
select 'Xavier', 2 union
select 'Yoshi', 2 union
select 'Zambrano', 2 union
select 'Player 1', 3 union
select 'Player 2', 3 union
select 'Player 3', 3 


/* using CROSS APPLY */
select *
from Person p
cross apply (
    select *
    from Company c
    where p.companyid = c.companyId
) Czip

/* the equivalent query using INNER JOIN */
select *
from Person p
inner join Company c on p.companyid = c.companyId

50
So che questo è ANCHE PICKIER, ma "performer" è sicuramente una parola. Non è solo correlato all'efficienza.
Rire1979,

2
È molto utile per sql xquery. controlla questo .
ARZ,

3
Sembra che l'uso di "inner loop join" sarebbe molto vicino all'applicazione incrociata. Vorrei che il tuo esempio fosse dettagliato quale suggerimento di join era equivalente. Il solo dire che join potrebbe comportare inner / loop / merge o persino "altro" perché potrebbe riorganizzarsi con altri join.
crokusek,

3
Quando il join creerà molte righe ma è necessario valutare solo una riga alla volta. Ho avuto un caso in cui avevo bisogno di un self join su un tavolo con oltre 100 milioni di righe e non c'era abbastanza memoria sufficiente. Quindi sono andato cursore per ridurre il footprint di memoria. Dal cursore ho attraversato applicare come footprint di memoria ancora gestito ed era 1/3 più veloce del cursore.
paparazzo,

10
CROSS APPLYha il suo ovvio utilizzo nel consentire a un set di dipendere da un altro (a differenza JOINdell'operatore), ma ciò non comporta costi: si comporta come una funzione che opera su ciascun membro del set di sinistra , quindi, in termini di SQL Server esegui sempre a Loop Join, che non è quasi mai il modo migliore per unire i set. Quindi, usa APPLYquando è necessario, ma non abusarne JOIN.
Gerardo Lima,

Risposte:


668

Qualcuno può darmi un buon esempio di quando CROSS APPLY fa la differenza in quei casi in cui funzionerà anche INNER JOIN?

Vedi l'articolo nel mio blog per un confronto dettagliato delle prestazioni:

CROSS APPLYfunziona meglio su cose che non hanno JOINcondizioni semplici .

Questo seleziona gli 3ultimi record da t2per ogni record da t1:

SELECT  t1.*, t2o.*
FROM    t1
CROSS APPLY
        (
        SELECT  TOP 3 *
        FROM    t2
        WHERE   t2.t1_id = t1.id
        ORDER BY
                t2.rank DESC
        ) t2o

Non può essere facilmente formulato con una INNER JOINcondizione.

Probabilmente potresti fare qualcosa del genere usando CTEla funzione finestra e:

WITH    t2o AS
        (
        SELECT  t2.*, ROW_NUMBER() OVER (PARTITION BY t1_id ORDER BY rank) AS rn
        FROM    t2
        )
SELECT  t1.*, t2o.*
FROM    t1
INNER JOIN
        t2o
ON      t2o.t1_id = t1.id
        AND t2o.rn <= 3

, ma questo è meno leggibile e probabilmente meno efficiente.

Aggiornare:

Ho appena controllato.

masterè una tabella dei 20,000,000record con un PRIMARY KEYon id.

Questa query:

WITH    q AS
        (
        SELECT  *, ROW_NUMBER() OVER (ORDER BY id) AS rn
        FROM    master
        ),
        t AS 
        (
        SELECT  1 AS id
        UNION ALL
        SELECT  2
        )
SELECT  *
FROM    t
JOIN    q
ON      q.rn <= t.id

funziona per quasi 30secondi, mentre questo:

WITH    t AS 
        (
        SELECT  1 AS id
        UNION ALL
        SELECT  2
        )
SELECT  *
FROM    t
CROSS APPLY
        (
        SELECT  TOP (t.id) m.*
        FROM    master m
        ORDER BY
                id
        ) q

è istantaneo.


2
Vedi la fine del link di Ariel. Una query row_number () è altrettanto piacevole e non richiede nemmeno un join. Quindi non penso che dovrei usare cross apply per questa situazione (selezionare la top 3, partizione di t1.id).
Jeff Meatball Yang,

375
Sebbene questa sia la risposta più popolare, non credo che risponda alla domanda reale "Qual è lo scopo principale dell'uso di CROSS APPLY?". Lo scopo principale è quello di abilitare le funzioni della tabella con i parametri da eseguire una volta per riga e quindi uniti ai risultati.
MikeKulls,

5
@Mike: come si chiama a TVFwith INNER JOIN?
Quassnoi,

15
@MikeKulls Sì, ma l'OP non ha chiesto lo scopo principale dell'utilizzo CROSS APPLY, ha chiesto quando sceglierlo INNER JOIN, quando anche quello avrebbe funzionato.
ErikE,

8
Vale la pena ricordare che questo è chiamato a lateral joinin standard (ANSI) SQL
a_horse_with_no_name

198

cross applya volte ti consente di fare cose con cui non puoi fare inner join.

Esempio (un errore di sintassi):

select F.* from sys.objects O  
inner join dbo.myTableFun(O.name) F   
on F.schema_id= O.schema_id

Questo è un errore di sintassi perché, quando utilizzato con inner join, le funzioni di tabella possono assumere solo variabili o costanti come parametri. (Vale a dire, il parametro della funzione tabella non può dipendere dalla colonna di un'altra tabella.)

Però:

select F.* from sys.objects O  
cross apply ( select * from dbo.myTableFun(O.name) ) F  
where F.schema_id= O.schema_id

Questo è legale

Modificare: o in alternativa sintassi più breve: (di ErikE)

select F.* from sys.objects O  
cross apply dbo.myTableFun(O.name) F
where F.schema_id= O.schema_id

Modificare:

Nota: Informix 12.10 xC2 + ha tabelle derivate laterali e Postgresql (9.3+) ha subquery laterali che possono essere utilizzate per un effetto simile.


11
Penso che questo sia il ragionamento alla base del motivo per cui abbiamo applicato il cross. Se dai un'occhiata al link in basso, questa è la prima cosa che MS dice su cross applicare. Potrebbe avere altri usi, ma penso che questo sia il motivo per cui è stato introdotto. Senza di essa le funzioni di tabella non sarebbero utilizzabili in molte situazioni. technet.microsoft.com/en-us/library/ms175156.aspx
MikeKulls

cross apply produce anche un bel piano di esecuzione se accoppiato con le funzioni di tabella in linea pur mantenendo la necessaria modularità.
Nurettin,

14
Non è SELECTnecessario all'interno di CROSS APPLY. Per favore, prova CROSS APPLY dbo.myTableFun(O.name) F.
ErikE,

1
@ErikE certo, puoi sempre usare la sintassi meno flessibile per applicare trasversalmente. Stavo mostrando la versione più generalizzata che a volte puoi usare per evitare di portare colonne difficili da calcolare nella query.
Nurettin,

2
Il join interno @Bolu non funzionerà se il parametro della funzione di tabella dipende dalla colonna di un'altra tabella (ovvero riferimento esterno) nella selezione esterna. Funzionerà se il parametro della funzione di tabella è un valore letterale o una variabile. L'applicazione incrociata funzionerà in entrambi i casi.
Nurettin,

175

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

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

1. Unire due tabelle in base ai TOP nrisultati

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, che è errata. Questo dovrebbe restituire sia Ids1 che 2 ma ha restituito solo 1 perché 1 ha le ultime due date. 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 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     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
x------x---------x--------------x-------x

Ecco come funziona. La query interna CROSS APPLYpuò fare riferimento alla tabella esterna, dove INNER JOINnon è possibile farlo (genera un 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à utilizzando 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

VANTAGGIO AGGIUNTIVO DI APPLICAZIONE CROCE

APPLYpuò essere usato in sostituzione di UNPIVOT. Sia CROSS APPLYo OUTER APPLYpuò essere utilizzato qui, che sono intercambiabili.

Considera di avere la tabella seguente (denominata MYTABLE).

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

La query è di seguito.

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

che ti porta il risultato

  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

4
Ottimo esempio con i record 2 vs 4 e mi ha aiutato a capire il contesto in cui questo sarebbe necessario.
Trnelson,

13
Questa risposta dimostra che vale davvero la pena scorrere verso il basso la pagina invece di scegliere quella accettata.
Mostafa Armandi,

2
Il miglior esempio finora per spiegare l'uso di APPLY ... Ho letto molti post e mi rendo conto che questa spiegazione cancella l'immagine come acqua. Mille grazie fratello.
AG7,

1
Per il punto 1 in cui abbiamo 2 righe per ID 1 invece di 4 righe per ID 1, 2. Non utilizzeremmo invece solo un join sinistro.
Joseph Cho,

43

Mi sembra che CROSS APPLY possa colmare una certa lacuna quando si lavora con campi calcolati in query complesse / nidificate e renderle più semplici e più leggibili.

Esempio semplice: hai un DoB e vuoi presentare più campi relativi all'età che si baseranno anche su altre fonti di dati (come il lavoro), come Età, Gruppo di età, Età di assunzione, Data di riposo minima, ecc. Da utilizzare nell'applicazione per l'utente finale (Ad esempio tabelle pivot di Excel).

Le opzioni sono limitate e raramente eleganti:

  • Le subquery JOIN non possono introdurre nuovi valori nel set di dati in base ai dati nella query principale (deve essere autonomo).

  • Le UDF sono pulite, ma lente perché tendono a prevenire operazioni parallele. Ed essere un'entità separata può essere una cosa buona (meno codice) o cattiva (dov'è il codice).

  • Tavoli di giunzione. A volte possono funzionare, ma abbastanza presto ti unirai alle sottoquery con tonnellate di UNION. Gran casino.

  • Crea ancora un'altra visualizzazione per singolo scopo, supponendo che i tuoi calcoli non richiedano dati ottenuti a metà strada attraverso la tua query principale.

  • Tabelle intermedie. Sì ... di solito funziona, e spesso è una buona opzione in quanto può essere indicizzata e veloce, ma le prestazioni possono anche diminuire a causa del fatto che le istruzioni UPDATE non sono parallele e non consentono di mettere in cascata le formule (riutilizzare i risultati) per aggiornare diversi campi all'interno del stessa affermazione. E a volte preferiresti semplicemente fare le cose in un solo passaggio.

  • Query di nidificazione. Sì, in qualsiasi momento è possibile inserire la parentesi sull'intera query e utilizzarla come sottoquery su cui è possibile manipolare i dati di origine e i campi calcolati. Ma puoi farlo solo così tanto prima che diventi brutto. Molto brutto.

  • Codice ripetuto. Qual è il valore più grande di 3 dichiarazioni lunghe (CASE ... ELSE ... END)? Sarà leggibile!

    • Di 'ai tuoi clienti di calcolare loro stessi le dannata cose.

Ho dimenticato qualcosa? Probabilmente, quindi sentiti libero di commentare. Ma hey, CROSS APPLY è come una manna dal cielo in tali situazioni: basta aggiungere un sempliceCROSS APPLY (select tbl.value + 1 as someFormula) as crossTbl e voilà! Il tuo nuovo campo è ora pronto per l'uso praticamente come se fosse sempre stato lì nei tuoi dati di origine.

I valori introdotti tramite CROSS APPLY possono ...

  • essere utilizzato per creare uno o più campi calcolati senza aggiungere al mix problemi di prestazioni, complessità o leggibilità
  • come con JOINs, diverse istruzioni CROSS APPLY successive possono riferirsi a se stesse: CROSS APPLY (select crossTbl.someFormula + 1 as someMoreFormula) as crossTbl2
  • puoi usare i valori introdotti da una CROSS APPLY nelle successive condizioni JOIN
  • Come bonus, c'è l'aspetto della funzione con valori di tabella

Dang, non c'è niente che non possano fare!


1
Questo è un grande +1 da parte mia, poiché sono sorpreso che non sia menzionato più spesso. Forse potresti estendere questo esempio per mostrare come eseguire calcoli "procedurali" sulla catena di valori derivati? Ad esempio: CROSS APPLY (selezionare crossTbl.value * tbl.multiplier come Moltiplicato) multiTbl - CROSS APPLY (selezionare multiTbl.Multiplied / tbl.DerivativeRatio as Derived) derivatoTbl - etc ...
mrmillsy

1
Altre informazioni / esempi su come utilizzare Cross Apply in sostituzione di CASE..ELSE..END?
przemo_li,

3
@przemo_li APPLY può essere utilizzato per memorizzare il risultato di un'istruzione case (tra le altre cose) al fine di fare riferimento ad esso. Una struttura potrebbe essere qualcosa del tipo: SELEZIONA CASO quando subquery.intermediateResult> 0 POI "sì" ELSE "no" FINE DA qualche Tabella ESTERNO APPLICAZIONE (selezionare CASO ... FINE ... ELSO come risultato intermedio) come sottoquery.
mtone,

14

Cross Apply funziona bene anche con un campo XML. Se si desidera selezionare i valori dei nodi in combinazione con altri campi.

Ad esempio, se si dispone di una tabella contenente alcuni file XML

<root>
    <subnode1>
       <some_node value="1" />
       <some_node value="2" />
       <some_node value="3" />
       <some_node value="4" />
    </subnode1>
</root>

Utilizzando la query

SELECT
       id as [xt_id]
      ,xmlfield.value('(/root/@attribute)[1]', 'varchar(50)') root_attribute_value
  ,node_attribute_value = [some_node].value('@value', 'int')
  ,lt.lt_name   
FROM dbo.table_with_xml xt
CROSS APPLY xmlfield.nodes('/root/subnode1/some_node') as g ([some_node])
LEFT OUTER JOIN dbo.lookup_table lt
ON [some_node].value('@value', 'int') = lt.lt_id

Restituirà un risultato

xt_id root_attribute_value node_attribute_value lt_name
----------------------------------------------------------------------
1     test1            1                    Benefits
1     test1            4                    FINRPTCOMPANY

13

Tecnicamente è già stato risposto molto bene, ma lasciatemi fare un esempio concreto di come sia estremamente utile:

Diciamo che hai due tavoli, cliente e ordine. I clienti hanno molti ordini.

Voglio creare una vista che mi dia dettagli sui clienti e sull'ordine più recente che hanno effettuato. Con solo JOINS, ciò richiederebbe alcuni self-join e aggregazioni che non è carino. Ma con Cross Apply, è super facile:

SELECT *
FROM Customer
CROSS APPLY (
  SELECT TOP 1 *
  FROM Order
  WHERE Order.CustomerId = Customer.CustomerId
  ORDER BY OrderDate DESC
) T

7

L'applicazione incrociata può essere utilizzata per sostituire le sottoquery in cui è necessaria una colonna della sottoquery

subquery

select * from person p where
p.companyId in(select c.companyId from company c where c.companyname like '%yyy%')

qui non sarò in grado di selezionare le colonne della tabella aziendale quindi, usando cross apply

select P.*,T.CompanyName
from Person p
cross apply (
    select *
    from Company C
    where p.companyid = c.companyId and c.CompanyName like '%yyy%'
) T

5

Immagino che dovrebbe essere leggibile;)

CROSS APPLY sarà in qualche modo unico per le persone che leggono per dire loro che viene utilizzato un UDF che verrà applicato a ciascuna riga della tabella a sinistra.

Naturalmente, ci sono altre limitazioni in cui una CROSS APPLY viene utilizzata meglio di JOIN che altri amici hanno pubblicato sopra.


4

Ecco un articolo che spiega tutto, con la differenza di prestazioni e l'utilizzo rispetto a JOINS.

SQL Server CROSS APPLY e OUTER APPLY su JOINS

Come suggerito in questo articolo, non vi è alcuna differenza di prestazioni tra loro per le normali operazioni di join (INNER AND CROSS).

inserisci qui la descrizione dell'immagine

La differenza di utilizzo arriva quando devi fare una query come questa:

CREATE FUNCTION dbo.fn_GetAllEmployeeOfADepartment(@DeptID AS INT)  
RETURNS TABLE 
AS 
RETURN 
   ( 
   SELECT * FROM Employee E 
   WHERE E.DepartmentID = @DeptID 
   ) 
GO 
SELECT * FROM Department D 
CROSS APPLY dbo.fn_GetAllEmployeeOfADepartment(D.DepartmentID)

Cioè, quando devi relazionarti con la funzione. Questo non può essere fatto usando INNER JOIN, che darebbe l'errore "Impossibile identificare l'identificatore in più parti" D.DepartmentID "." Qui il valore viene passato alla funzione mentre viene letta ogni riga. Mi sembra bello. :)


3

Beh, non sono sicuro se questo si qualifica come un motivo per usare Cross Apply contro Inner Join, ma questa domanda mi ha risposto in un post sul forum usando Cross Apply, quindi non sono sicuro che esista un metodo equivalente usando Inner Join:

Create PROCEDURE [dbo].[Message_FindHighestMatches]

-- Declare the Topical Neighborhood
@TopicalNeighborhood nchar(255)

COME INIZIARE

-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON

Create table  #temp
(
    MessageID         int,
    Subjects          nchar(255),
    SubjectsCount    int
)

Insert into #temp Select MessageID, Subjects, SubjectsCount From Message

Select Top 20 MessageID, Subjects, SubjectsCount,
    (t.cnt * 100)/t3.inputvalues as MatchPercentage

From #temp 

cross apply (select count(*) as cnt from dbo.Split(Subjects,',') as t1
             join dbo.Split(@TopicalNeighborhood,',') as t2
             on t1.value = t2.value) as t
cross apply (select count(*) as inputValues from dbo.Split(@TopicalNeighborhood,',')) as t3

Order By MatchPercentage desc

drop table #temp

FINE


3

L'essenza dell'operatore APPLY è consentire la correlazione tra lato sinistro e destro dell'operatore nella clausola FROM.

Contrariamente a JOIN, la correlazione tra input non è consentita.

Parlando di correlazione nell'operatore APPLY, intendo sul lato destro possiamo mettere:

  • una tabella derivata - come una sottoquery correlata con un alias
  • una funzione con valori di tabella: una vista concettuale con parametri, in cui il parametro può fare riferimento al lato sinistro

Entrambi possono restituire più colonne e righe.


2

Questa è forse una vecchia domanda, ma adoro ancora il potere di CROSS APPLY di semplificare il riutilizzo della logica e di fornire un meccanismo "concatenato" per i risultati.

Di seguito ho fornito un violino SQL che mostra un semplice esempio di come è possibile utilizzare CROSS APPLY per eseguire complesse operazioni logiche sul set di dati senza che le cose si sporchino. Da qui non è difficile estrapolare calcoli più complessi.

http://sqlfiddle.com/#!3/23862/2


1

Mentre la maggior parte delle query che utilizzano CROSS APPLY possono essere riscritte utilizzando un INNER JOIN, CROSS APPLY può produrre un piano di esecuzione e prestazioni migliori, poiché può limitare l'insieme del set ancora prima che si verifichi il join.

Rubato da qui

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.