2016-02-07 25 views
7

Come creare un join tra due tabelle ma limitandosi alla prima riga che soddisfa la condizione di join?SQL: come limitare un join sulla prima riga trovata?

In questo semplice esempio, vorrei ottenere per ogni riga table_A prima linea table_B che soddisfa la condizione:

select table_A.id, table_A.name, table_B.city 
from table_A join table_B 
on table_A.id = table_B.id2 
where .. 

table_A (id, name) 
1, John 
2, Marc 

table_B (id2, city) 
1, New York 
1, Toronto 
2, Boston 

The output would be: 
1, John, New York 
2, Marc, Boston 

Può essere Oracle fornisce una tale funzione (prestazioni è una preoccupazione).

+0

sel ect * da table_A join (selezionare * feom table_B group da id2) b su table_A.id = b.id2 dove .. –

+0

Partecipare a un sottoquery con row_number e nella condizione join aggiungere e row_number = 1 – Mihai

+0

Fornire un esempio di output previsto da un dato dato (dati della tabella fittizia) ci aiuterà a comprendere le tue esigenze. – saurav

risposta

1
select table_A.id, table_A.name, 
FIRST_VALUE(table_B.city) IGNORE NULLS 
     OVER (PARTITION BY table_B.id2 ORDER BY table_B.city) AS "city" 
from table_A join table_B 
on table_A.id = table_B.id2 
where .. 
3

Se si vuole il valore solo singolo una subquery scalare può essere utilizzato:

SELECT 
    id, name, (SELECT city FROM table_B WHERE id2 = table_A.id AND ROWNUM = 1) city 
FROM 
    table_A 
0

Query:

SELECT a.id, 
     a.name, 
     b.city 
FROM table_A a 
     INNER JOIN 
     (SELECT id2, 
       city 
     FROM (
      SELECT id2, 
        city, 
        ROW_NUMBER() OVER (PARTITION BY id2 ORDER BY NULL) rn 
      FROM Table_B 
     ) 
     WHERE rn = 1 
     ) b 
     ON (a.id = b.id2) 
--WHERE ... 

Uscite:

 ID NAME CITY 
---------- ---- -------- 
     1 John New York 
     2 Marc Boston 
3

La parola chiave qui è FIRST. È possibile utilizzare la funzione analitica FIRST_VALUE o il costrutto aggregato FIRST.
Per FIRST o LAST le prestazioni non è mai peggio e spesso migliore rispetto alla equivalente FIRST_VALUE o LAST_VALUE costrutto, perché non abbiamo un superfluo finestra di sorta e di conseguenza un costo di esecuzione inferiore:

select table_A.id, table_A.name, firstFromB.city 
from table_A 
join (
    select table_B.id2, max(table_B.city) keep (dense_rank first order by table_B.city) city 
    from table_b 
    group by table_B.id2 
    ) firstFromB on firstFromB.id2 = table_A.id 
where 1=1 /* some conditions here */ 
; 

Dal 12c introdotto operatore LATERAL, nonché CROSS/OUTER APPLY join, permettono di utilizzare un subquery correlato sul lato destro del JOIN clausola:

select table_A.id, table_A.name, firstFromB.city 
from table_A 
cross apply (
    select max(table_B.city) keep (dense_rank first order by table_B.city) city 
    from table_b 
    where table_B.id2 = table_A.id 
    ) firstFromB 
where 1=1 /* some conditions here */ 
; 
1

Su Oracle12c c'è finalmente il nuovo cross/outer apply operator che permetterà ciò che hai chiesto senza alcuna soluzione.

il seguente è un esempio che si affaccia sul viste del dizionario per solo uno dei (probabilmente) molti oggetti di proprietà di quegli utenti che hanno il loro nome che iniziano con 'SYS':

select * 
from (
     select USERNAME 
     from ALL_USERS 
     where USERNAME like 'SYS%' 
    ) U 
    cross apply (
     select OBJECT_NAME 
     from ALL_OBJECTS O 
     where O.OWNER = U.USERNAME 
      and ROWNUM = 1 
    ) 

su Oracle 11g e le versioni precedenti si dovrebbero usare solo soluzioni alternative che in genere eseguono la scansione completa della seconda tabella in base agli ID della seconda tabella per ottenere gli stessi risultati, ma per i test delle pupose è possibile abilitare lo lateral operator (disponibile anche su 12c senza necessità di abilitare nuove risorse) e utilizzare quest'altra uno

-- Enables some new features 
alter session set events '22829 trace name context forever'; 

select * 
from (
     select USERNAME 
     from ALL_USERS 
     where USERNAME like 'SYS%' 
    ) U, 
    lateral (
     select OBJECT_NAME 
     from ALL_OBJECTS O 
     where O.OWNER = U.USERNAME 
      and ROWNUM = 1 
    );