SQL SELECT: combinazione e raggruppamento di dati tra tre tabelle mediante subquery
Domanda
Ci scusiamo per una lunga domanda e non un titolo molto descrittivo, ma il mio problema è molto difficile da spiegare brevemente.
Ho tre tabelle di database:
TABLE A:
AID PK
STATUS VARCHAR
TABLE B:
BID PK
AID FK
CID FK
TABLE C:
CID PK
CREATIONTIME DATE
Per ogni STATUS = 'OK' riga nella tabella A Voglio trovare la riga corrispondente in C che ha l'ora di creazione più recente.
Per prima cosa posso recuperare tutte le righe dalla tabella A dove STATUS = 'OK'.
Quindi posso recuperare tutte le righe corrispondenti dalla tabella B.
Ma come continuare da lì?
Ad esempio:
select AID, CID from B where AID in (select AID from A where STATUS = 'OK')
potrebbe restituire qualcosa del tipo:
AID, CID
1 1
2 2
2 3
3 4
4 5
4 6
Supponiamo che CID 2 abbia un tempo di creazione successivo rispetto a CID 3 e CID 6 sia più recente di CID 5. Ciò significa che il risultato corretto sarebbe le righe 1, 2, 4 e 6 nella tabella C.
C'è un modo per esprimerlo con una query?
EDIT: Mi dispiace di non essere stato abbastanza specifico. Quello che voglio ottenere sono i CID dalla tabella C.
EDIT:
Ho contato le righe restituite con le diverse soluzioni. I risultati sono stati molto interessanti e diversificati:
HAINSTECH: 298 473 file
JMUCCHIELLO: 298 473 file
RUSS CAM: 290 121 file
CHRIS: 344 093 righe
TYRANNOSAURS: 290 119 righe
Non ho ancora avuto il tempo di analizzare in profondità le righe restituite, ma apprezzerei molto le visualizzazioni su quali delle query sono "interrotte". e perché.
Soluzione
Qualcosa del genere, se ti ho capito correttamente
SELECT
MAX(CREATIONTIME),
A.AID
FROM
A
INNER JOIN
B
ON
A.AID = B.AID
INNER JOIN
C
ON
B.CID = C.CID
WHERE
A.STATUS = 'OK'
GROUP BY
A.AID
Modifica
Ora ho verificato quanto segue in SQL Server (vorrei ottenere lo stesso risultato in Oracle) e restituisce il CID
per il record C
con il massimo STATUS
per il record correlato in A
id 'OK'
.
SELECT C.CID
FROM
C C
INNER JOIN
B B
ON
C.CID = B.CID
INNER JOIN
(
SELECT
MAX(C.CREATIONTIME) CREATIONTIME,
A.AID
FROM
A A
INNER JOIN
B B
ON
A.AID = B.AID
INNER JOIN
C C
ON
B.CID = C.CID
WHERE
A.STATUS = 'OK'
GROUP BY
A.AID
) ABC
ON B.AID = ABC.AID
AND C.CREATIONTIME = ABC.CREATIONTIME
Dimostrato con il seguente T-SQL
DECLARE @A TABLE(AID INT IDENTITY(1,1), STATUS VARCHAR(10))
DECLARE @B TABLE(BID INT IDENTITY(1,1), AID INT, CID INT)
DECLARE @C TABLE(CID INT IDENTITY(1,1), CREATIONTIME DATETIME)
INSERT INTO @A VALUES ('OK')
INSERT INTO @A VALUES ('OK')
INSERT INTO @A VALUES ('NOT OK')
INSERT INTO @A VALUES ('OK')
INSERT INTO @A VALUES ('NOT OK')
INSERT INTO @C VALUES ('10 MAR 2008')
INSERT INTO @C VALUES ('13 MAR 2008')
INSERT INTO @C VALUES ('15 MAR 2008')
INSERT INTO @C VALUES ('17 MAR 2008')
INSERT INTO @C VALUES ('21 MAR 2008')
INSERT INTO @B VALUES (1,1)
INSERT INTO @B VALUES (1,2)
INSERT INTO @B VALUES (1,3)
INSERT INTO @B VALUES (2,2)
INSERT INTO @B VALUES (2,3)
INSERT INTO @B VALUES (2,4)
INSERT INTO @B VALUES (3,3)
INSERT INTO @B VALUES (3,4)
INSERT INTO @B VALUES (3,5)
INSERT INTO @B VALUES (4,5)
INSERT INTO @B VALUES (4,1)
INSERT INTO @B VALUES (4,2)
SELECT C.CID
FROM
@C C
INNER JOIN
@B B
ON
C.CID = B.CID
INNER JOIN
(
SELECT
MAX(C.CREATIONTIME) CREATIONTIME,
A.AID
FROM
@A A
INNER JOIN
@B B
ON
A.AID = B.AID
INNER JOIN
@C C
ON
B.CID = C.CID
WHERE
A.STATUS = 'OK'
GROUP BY
A.AID
) ABC
ON B.AID = ABC.AID
AND C.CREATIONTIME = ABC.CREATIONTIME
Risultati nel seguente
CID
-----------
3
4
5
MODIFICA 2:
In risposta al tuo commento su ciascuna delle affermazioni che danno risultati diversi, ho eseguito alcune delle diverse risposte qui tramite SQL Server 2005 usando i miei dati di test sopra (apprezzo che tu stia usando Oracle). Ecco i risultati
--Expected results for CIDs would be
--CID
-----------
--3
--4
--5
--As indicated in the comments next to the insert statements
DECLARE @A TABLE(AID INT IDENTITY(1,1), STATUS VARCHAR(10))
DECLARE @B TABLE(BID INT IDENTITY(1,1), AID INT, CID INT)
DECLARE @C TABLE(CID INT IDENTITY(1,1), CREATIONTIME DATETIME)
INSERT INTO @A VALUES ('OK') -- AID 1
INSERT INTO @A VALUES ('OK') -- AID 2
INSERT INTO @A VALUES ('NOT OK')
INSERT INTO @A VALUES ('OK') -- AID 4
INSERT INTO @A VALUES ('NOT OK')
INSERT INTO @C VALUES ('10 MAR 2008')
INSERT INTO @C VALUES ('13 MAR 2008')
INSERT INTO @C VALUES ('15 MAR 2008')
INSERT INTO @C VALUES ('17 MAR 2008')
INSERT INTO @C VALUES ('21 MAR 2008')
INSERT INTO @B VALUES (1,1)
INSERT INTO @B VALUES (1,2)
INSERT INTO @B VALUES (1,3) -- Will be CID 3 For AID 1
INSERT INTO @B VALUES (2,2)
INSERT INTO @B VALUES (2,3)
INSERT INTO @B VALUES (2,4) -- Will be CID 4 For AID 2
INSERT INTO @B VALUES (3,3)
INSERT INTO @B VALUES (3,4)
INSERT INTO @B VALUES (3,5)
INSERT INTO @B VALUES (4,5) -- Will be CID 5 FOR AID 4
INSERT INTO @B VALUES (4,1)
INSERT INTO @B VALUES (4,2)
-- Russ Cam
SELECT C.CID, ABC.CREATIONTIME
FROM
@C C
INNER JOIN
@B B
ON
C.CID = B.CID
INNER JOIN
(
SELECT
MAX(C.CREATIONTIME) CREATIONTIME,
A.AID
FROM
@A A
INNER JOIN
@B B
ON
A.AID = B.AID
INNER JOIN
@C C
ON
B.CID = C.CID
WHERE
A.STATUS = 'OK'
GROUP BY
A.AID
) ABC
ON B.AID = ABC.AID
AND C.CREATIONTIME = ABC.CREATIONTIME
-- Tyrannosaurs
select A.AID,
max(AggC.CREATIONTIME)
from @A A,
@B B,
( select C.CID,
max(C.CREATIONTIME) CREATIONTIME
from @C C
group by CID
) AggC
where A.AID = B.AID
and B.CID = AggC.CID
and A.Status = 'OK'
group by A.AID
-- jmucchiello
SELECT c.cid, max(c.creationtime)
FROM @B b, @C c
WHERE b.cid = c.cid
AND b.aid IN (SELECT a.aid FROM @A a WHERE status = 'OK')
GROUP BY c.cid
-- hainstech
SELECT agg.aid, agg.cid
FROM (
SELECT a.aid
,c.cid
,max(c.creationtime) as maxcCreationTime
FROM @C c INNER JOIN @B b ON b.cid = c.cid
INNER JOIN @A a on a.aid = b.aid
WHERE a.status = 'OK'
GROUP BY a.aid, c.cid
) as agg
--chris
SELECT A.AID, C.CID, C.CREATIONTIME
FROM @A A, @B B, @C C
WHERE A.STATUS = 'OK'
AND A.AID = B.AID
AND B.CID = C.CID
AND C.CREATIONTIME =
(SELECT MAX(C2.CREATIONTIME)
FROM @C C2, @B B2
WHERE B2.AID = A.AID
AND C2.CID = B2.CID);
i risultati sono i seguenti
--Russ Cam - Correct CIDs (I have added in the CREATIONTIME for reference)
CID CREATIONTIME
----------- -----------------------
3 2008-03-15 00:00:00.000
4 2008-03-17 00:00:00.000
5 2008-03-21 00:00:00.000
--Tyrannosaurs - No CIDs in the resultset
AID
----------- -----------------------
1 2008-03-15 00:00:00.000
2 2008-03-17 00:00:00.000
4 2008-03-21 00:00:00.000
--jmucchiello - Incorrect CIDs in the resultset
cid
----------- -----------------------
1 2008-03-10 00:00:00.000
2 2008-03-13 00:00:00.000
3 2008-03-15 00:00:00.000
4 2008-03-17 00:00:00.000
5 2008-03-21 00:00:00.000
--hainstech - Too many CIDs in the resultset, which CID has the MAX(CREATIONTIME) for each AID?
aid cid
----------- -----------
1 1
1 2
1 3
2 2
2 3
2 4
4 1
4 2
4 5
--chris - Correct CIDs, it is the same SQL as mine
AID CID CREATIONTIME
----------- ----------- -----------------------
1 3 2008-03-15 00:00:00.000
2 4 2008-03-17 00:00:00.000
4 5 2008-03-21 00:00:00.000
Consiglio di eseguire ciascuna delle risposte fornite su un numero inferiore di record, in modo da poter verificare se il set di risultati restituito è quello previsto.
Altri suggerimenti
SQL> create table a (aid,status)
2 as
3 select 1, 'OK' from dual union all
4 select 2, 'OK' from dual union all
5 select 3, 'OK' from dual union all
6 select 4, 'OK' from dual union all
7 select 5, 'NOK' from dual
8 /
Tabel is aangemaakt.
SQL> create table c (cid,creationtime)
2 as
3 select 1, sysdate - 1 from dual union all
4 select 2, sysdate - 2 from dual union all
5 select 3, sysdate - 3 from dual union all
6 select 4, sysdate - 4 from dual union all
7 select 5, sysdate - 6 from dual union all
8 select 6, sysdate - 5 from dual
9 /
Tabel is aangemaakt.
SQL> create table b (bid,aid,cid)
2 as
3 select 1, 1, 1 from dual union all
4 select 2, 2, 2 from dual union all
5 select 3, 2, 3 from dual union all
6 select 4, 3, 4 from dual union all
7 select 5, 4, 5 from dual union all
8 select 6, 4, 6 from dual union all
9 select 7, 5, 6 from dual
10 /
Tabel is aangemaakt.
SQL> select a.aid
2 , max(c.cid) keep (dense_rank last order by c.creationtime) cid
3 , max(c.creationtime) creationtime
4 from a
5 , b
6 , c
7 where b.aid = a.aid
8 and b.cid = c.cid
9 and a.status = 'OK'
10 group by a.aid
11 /
AID CID CREATIONTIME
---------- ---------- -------------------
1 1 30-04-2009 09:26:00
2 2 29-04-2009 09:26:00
3 4 27-04-2009 09:26:00
4 6 26-04-2009 09:26:00
4 rijen zijn geselecteerd.
Seleziona il campo che stai cercando usando un join di tutte e 3 le tabelle e quindi limita i risultati a quelli in cui CREATIONDATE è il più recente.
SELECT A.AID, C.CID, C.CREATIONTIME
FROM A A, B B, C C
WHERE A.STATUS = 'OK'
AND A.AID = B.AID
AND B.CID = C.CID
AND C.CREATIONTIME =
(SELECT MAX(C2.CREATIONTIME)
FROM C C2, B B2
WHERE B2.AID = A.AID
AND C2.CID = B2.CID);
EDIT: la mia risposta precedente era una sciocchezza. Questa è ora una riscrittura completa
Questo è in realtà un problema che mi ha infastidito per tutta la mia vita SQL. La soluzione che sto per darti è disordinata ma funziona e apprezzerei chiunque dica "sì, è disordinata ma è l'unico modo per farlo" oppure dì " no, fai questo ... " ;.
Penso che il disagio derivi dall'unione di due date. Il modo in cui succede qui non è un problema in quanto saranno una corrispondenza esatta (hanno esattamente gli stessi dati di root) ma sembra ancora sbagliato ...
Ad ogni modo, suddividendolo, devi farlo in due fasi.
1) Il primo è quello di restituire un set di risultati [AID], [primo tempo di creazione] che ti dà il primo tempo di creazione per ciascun AID.
2) È quindi possibile utilizzare latestCreationTime per estrarre il CID desiderato.
Quindi, per la parte (1), creerei personalmente una vista per farlo solo per mantenere le cose pulite. Ti consente di testare questa parte e farla funzionare prima di fonderla con le altre cose.
create view LatestCreationTimes
as
select b.AID,
max(c.CreationTime) LatestCreationTime
from TableB b,
TableC c
where b.CID = c.CID
group by b.AID
Nota, a questo punto non abbiamo preso in considerazione lo stato.
È quindi necessario unirlo a TableA (per ottenere lo stato) e TableB e TableC (per ottenere il CID). Devi fare tutti i collegamenti ovvi (AID, CID) e anche unirti alla colonna LatestCreationTime nella vista alla colonna CreationTime in TableC. Non dimenticare anche di unirti alla vista su AID, altrimenti dove due record sono stati creati contemporaneamente per diversi record A, otterrai problemi.
select A.AID,
C.CID
from TableA a,
TableB b,
TableC c,
LatestCreationTimes lct
where a.AID = b.AID
and b.CID = c.CID
and a.AID = lct.AID
and c.CreationTime = lct.LatestCreationTime
and a.STATUS = 'OK'
Sono certo che funzioni: l'ho testato, ottimizzato i dati, riprovato e si comporta. Almeno fa quello che credo debba fare.
Tuttavia non tratta la possibilità di due identici tempi di creazione nella tabella C per lo stesso record. Immagino che ciò non dovrebbe accadere, tuttavia, a meno che tu non abbia scritto qualche volta che lo limiti assolutamente, deve essere preso in considerazione.
Per fare questo ho bisogno di fare un presupposto su quale preferiresti. In questo caso, dirò che se ci sono due CID corrispondenti, preferiresti avere quello più alto (molto probabilmente è più aggiornato).
select A.AID,
max(C.CID) CID
from TableA a,
TableB b,
TableC c,
LatestCreationTimes lct
where a.AID = b.AID
and b.CID = c.CID
and c.CreationTime = lct.LatestCreationTime
and a.STATUS = 'OK'
group by A.AID
E credo che dovrebbe funzionare per te. Se lo desideri come una query anziché con la vista, quindi:
select A.AID,
max(C.CID) CID
from TableA a,
TableB b,
TableC c,
(select b.AID,
max(c.CreationTime) LatestCreationTime
from TableB b,
TableC c
where b.CID = c.CID
group by b.AID) lct
where a.AID = b.AID
and b.CID = c.CID
and c.CreationTime = lct.LatestCreationTime
and a.STATUS = 'OK'
group by A.AID
(Ho appena incorporato la vista nella query, altrimenti l'entità è esattamente la stessa).
Non è necessaria una sottoquery, l'aggregazione per determinare il tempo di creazione dell'ultimo cid è semplice:
SELECT a.aid
,c.cid
,max(c.creationtime) as maxcCreationTime
FROM c INNER JOIN b ON b.cid = c.cid
INNER JOIN a on a.aid = b.aid
WHERE a.status = 'OK'
GROUP BY a.aid, c.cid
Se davvero non vuoi il tempo di creazione nel tuo set di righe, puoi semplicemente inserirlo in una sottoquery e rilasciarlo dalla proiezione:
SELECT agg.aid, agg.cid
FROM (
SELECT a.aid
,c.cid
,max(c.creationtime) as maxcCreationTime
FROM c INNER JOIN b ON b.cid = c.cid
INNER JOIN a on a.aid = b.aid
WHERE a.status = 'OK'
GROUP BY a.aid, c.cid
) as agg
Codifica nella pagina Web, scusa eventuali errori di sintassi. Inoltre, sono un ragazzo mssql, quindi spero che non ci sia nulla di diverso nel mondo Oracle per questo ..
Nota che lo schema che hai fornito non impone l'unicità di CREATIONTIME per cid. Se ci sono mai due valori cid associati a un determinato valore di aiuto con lo stesso tempo di creazione, verranno entrambi emessi. Se fai affidamento sulla coppia di cid, il momento della creazione è unico, dovresti applicarlo in modo dichiarativo con un vincolo.
Mi sto perdendo qualcosa? Cosa c'è di sbagliato in:
EDIT: Okay, vedo che in realtà vuoi raggruppare per aiuto.
SELECT c.cid FROM b, c,
(SELECT b.aid as aid, max(c.creationtime) as creationtime
FROM b, c
WHERE b.cid = c.cid
AND b.aid IN (SELECT a.aid FROM a WHERE status = 'OK')
GROUP BY b.aid) as z
WHERE b.cid = c.cid
AND z.aid = b.aid
AND z.creationtime = c.creationtime