2012-11-21 19 views
5

Ho una domanda relativa a newbyl.sql seleziona query nella stessa tabella

diciamo che ho questo semplice tabella:

A  B 
------ ------ 
a  b 
a  c 
b  a 
m  n 
m  o 
n  m 

vorrei interrogare per i record che hanno "controparti" solo, cioè voglio ottenere a b solo se c'è b a nella tabella, ma i vuoi saltare i "collegamenti posteriori" (che è b a qui). Per riassumere mi piacerebbe ottenere il seguente risultato

A  B 
------ ------ 
a  b 
m  n 

questa query SQL non funziona in quanto nel caso b a viene gestito il a b viene rimosso dal mio set di risultati.

SELECT DISTINCT x1.A, x1.B 
FROM TEST x1, TEST x2 
WHERE x1.A = x2.B AND x1.B = x2.A -- all records /w counterparts only 
AND x1.A NOT IN (SELECT B from TEST where B = x1.A) -- skip the "back links" 

la seconda parte della clausola WHERE non funziona come desiderato.

avete qualche suggerimento? qualsiasi aiuto con questo sarebbe molto apprezzato.

riguarda Peter

P.S. sto usando il derby db.

+4

'b a' è un collegamento a ritroso di' a b'. ma anche 'a b' è un collegamento posteriore di' b a'. come vuoi scegliere quale tenere e quale saltare? – fthiella

+0

Ma come sapere quali sono le risposte corrette (ordine)? Perché non un b e n m OR b a e n m? – Justin

risposta

4

si potrebbe cambiare la vostra ultima riga a:

AND x1.A < x1.B 

Questo presuppone che sia le colonne non sono mai auto-referenziale (per esempio: a, a) o che non si desidera vengano visualizzati i riferimenti circolari. Se lo fai, allora:

AND x1.A <= x1.B 

EDIT:

probabilmente stai meglio utilizzare esplicito si unisce così:

SELECT DISTINCT 
    x1.A 
    , x1.B 
FROM 
    TEST x1 
JOIN 
    TEST x2 
    ON x1.A = x2.B 
     AND x1.B = x2.A -- all records /w counterparts only 
WHERE x1.A < x1.B --Skip backreference 
+0

questa è la soluzione più elegante, credo ... e funziona come fascino ... grazie mille jason! :) –

0

Non ho un derby db da testare ma penso che questo dovrebbe funzionare.
Come non hai specificato come possiamo decidere quale tra a/b eb/a è il collegamento posteriore ho usato la prima occorrenza trovata per essere la direzione corretta.

L'idea dietro la query sta unendo insieme allo stesso tavolo per ottenere il link e una row_number che rappresenta la posizione del elemento trovato. Quindi unire le tabelle con la posizione e prendere quella con la prima occorrenza trovata.

select TOT1.A, TOT2.B 
(select distinct t1.A, t1.B, row_number() over() as num 
from test t1 
join test t2 
on t1.A = t2.B and t1.B = t2.A) as TOT1 
join 
(select distinct t1.A, t1.B, row_number() over() as num 
from test t1 
join test t2 
on t1.A = t2.B and t1.B = t2.A) as TOT2 
on TOT1.A = TOT2.B and TOT1.B = TOT2.A and TOT1.NUM < TOT2.NUM 
+0

non importa quale sia il collegamento posteriore "a/b" o "b/a". ok, sembra essere un'anima. la tua soluzione è ok, ma non è indipendente dal sistema db. per esempio. mysql non fornisce la funzione row_number(). ci sono soluzioni alternative, però. –

+0

Hai specificato che stai utilizzando un DerbyDb quindi fornisco una soluzione utilizzando la funzionalità DerbyDb –

1
SELECT Distinct 
    case when tab1.A < tab1.B then tab1.A else tab1.B end as A, 
    case when tab1.A > tab1.B then tab1.A else tab1.B end as B 
FROM 
    tab inner join tab tab1 on tab.B = tab1.A 
WHERE 
    tab1.B = tab.A 

EDIT: in base alla risposta aggiornato, credo che avete bisogno di questo:

select distinct 
    (case when tab1.A < tab1.B then tab1.A else tab1.B end) as A, 
    (case when tab1.A > tab1.B then tab1.A else tab1.B end) as B 
from TEST tab1 left join TEST tab2 on tab1.B = tab2.A 

dà lo stesso risultato di query, ma è molto più veloce dato che non c'è cartesiano aderire.

+0

grazie, questa soluzione funziona anche –

+0

@ P.Pete grazie ... devo ammettere che la tua risposta accettata è più elegante della mia :) – fthiella

+0

sì funziona ed è più veloce senza la clausola WHERE ... thx –

0

grazie per tutte le risposte finora

i' ho una versione leggermente più semplice (più facile) del mio primo problema. Non ho bisogno del controllo in righe "controparte", ho solo bisogno di saltare i riferimenti all'indietro.

ho modificato la soluzione di fthiellas finora (vedi sotto) e funziona. in qualche modo, penso che ci deve essere una soluzione più semplice.

select distinct 
(case when tab1.A < tab1.B then tab1.A else tab1.B end) as A, 
(case when tab1.A > tab1.B then tab1.A else tab1.B end) as B 
from TEST tab1, TEST tab2 

dato che non sono limitato a derby, mi domando se ci sarà un problema con la funzione CASE quando si passa ad un altro sistema db. una soluzione "universale" sarebbe bella che funzioni con tutti i diversi dialemi sql (mysql, postgres, oracle, mssql, ecc.)

qualche idea?

+0

vedi la mia risposta aggiornata ... ti darà lo stesso risultato ma sarà molto più veloce della tua query. caso in cui è standard sql quindi dovrebbe funzionare sulla maggior parte dei dbms. – fthiella

0
SELECT * 
FROM ztable t 
WHERE EXISTS (
    SELECT * FROM ztable x 
    WHERE x.a = t.b AND x.b = t.a 
    AND x.a > x.b -- tie breaker 
    ); 

L'esiste ha il vantaggio che la sottoquery (corellata) non è visibile alla query esterna; quindi lo select * si espanderà solo alle colonne della tabella t.

+0

non funziona come previsto –

0

qui di seguito troverete il codice SQL per creare il mio tavolo insieme con i dati inseriti

CREATE TABLE TEST (A varchar(4), B varchar(4)); 
INSERT INTO TEST (ID,A,B) VALUES ('1','d','a'); 
INSERT INTO TEST (ID,A,B) VALUES ('1','c','a'); 
INSERT INTO TEST (ID,A,B) VALUES ('1','b','a'); 
INSERT INTO TEST (ID,A,B) VALUES ('1','a','xxx'); 
INSERT INTO TEST (ID,A,B) VALUES ('1','a','d'); 
INSERT INTO TEST (ID,A,B) VALUES ('1','a','c'); 
INSERT INTO TEST (ID,A,B) VALUES ('1','a','b'); 
INSERT INTO TEST (ID,A,B) VALUES ('2','g','a'); 
INSERT INTO TEST (ID,A,B) VALUES ('2','a','g'); 
INSERT INTO TEST (ID,A,B) VALUES ('3','f','b'); 
INSERT INTO TEST (ID,A,B) VALUES ('3','b','f'); 
INSERT INTO TEST (ID,A,B) VALUES ('4','s','r'); 
INSERT INTO TEST (ID,A,B) VALUES ('4','r','s'); 
INSERT INTO TEST (ID,A,B) VALUES ('5','r','t'); 
INSERT INTO TEST (ID,A,B) VALUES ('7','h','g'); 

come descritto in precedenza, utilizzando questa query:

select distinct tab1.ID, 
    (case when tab1.A < tab1.B then tab1.A else tab1.B end) as A, 
    (case when tab1.A > tab1.B then tab1.A else tab1.B end) as B 
from TEST tab1, TEST tab2 

... ho avuto l'desiderato risultato:

ID A B 
-- -- -- 
1 a b 
1 a c 
1 a d 
1 a xxx 
2 a g 
3 b f 
4 r s 
5 r t 
7 g h 

scusate ragazzi, forse mi manca qui som ma sembra che le tue soluzioni non funzionino come previsto.

@fthiella: ho provato la soluzione:

SELECT tab1.* 
FROM TEST tab1 LEFT JOIN TEST tab2 on tab1.B=tab2.A 
WHERE tab1.A<tab1.B OR tab2.A is null 

risultato (a/b duplicato, g/h mancante):

ID A B 
-- -- -- 
1 a b 
1 a b 
1 a c 
1 a xxx 
2 a g 
3 b f 
4 r s 
5 r t 

@wildplasser: sembra questa soluzione non funziona, troppo

SELECT * FROM TEST t 
WHERE EXISTS (
    SELECT * FROM TEST x 
    WHERE x.a = t.b AND x.b = t.a 
    AND x.a > x.b -- tie breaker 
    ); 

risultato (a/xxx e r/t):

ID A B 
-- -- -- 
1 a b 
1 a c 
1 a d 
2 a g 
3 b f 
4 r s 
+1

ho modificato la mia risposta di nuovo, dovrebbe essere identica alla tua query ora, solo più veloce perché non c'è un join cartesiano – fthiella

Problemi correlati