SQL SELECT: combinando e agrupamento de dados entre três tabelas usando subconsultas

StackOverflow https://stackoverflow.com/questions/809056

  •  03-07-2019
  •  | 
  •  

Pergunta

Sorry for uma pergunta longa e não um título muito descritivo, mas o meu problema é muito difícil de explicar brevemente.

Eu tenho três tabelas de banco de dados:

TABLE A:  
AID PK  
STATUS VARCHAR

TABLE B:  
BID PK  
AID FK  
CID FK

TABLE C:  
CID PK  
CREATIONTIME DATE

Para cada STATUS = 'OK' linha na tabela Um Quero encontrar a linha correspondente em C que tem o tempo mais recente criação.

Em primeiro lugar eu posso ir buscar todas as linhas da tabela A, onde STATUS = 'OK'.
Em seguida eu posso para buscar todas as linhas correspondentes da tabela B.
Mas como continuar a partir daí?

Por exemplo:

select AID, CID from B where AID in (select AID from A where STATUS = 'OK')

poderia retornar algo como:

AID, CID  
1    1  
2    2  
2    3  
3    4  
4    5  
4    6  

Vamos dizer que CID 2 tem tempo de criação mais tarde do CID 3 e CID 6 é mais recente do CID 5. Isto significa que o resultado correto seria linhas 1, 2, 4 e 6 na tabela C.

Existe uma maneira de expressar isso com uma consulta?

EDIT: Pena que eu não era o bastante específico. O que eu quero chegar é os CIDs da tabela C.

EDIT: Contei linhas retornadas com as diferentes soluções. Os resultados foram muito interessantes - e diversificou:
HAINSTECH: 298 473 linhas
JMUCCHIELLO: 298 473 linhas
RUSS CAM: 290 121 linhas
CHRIS: 344 093 linhas
Tiranossauros: 290 119 linhas

Eu ainda não tive tempo para analisar linhas retornadas em profundidade, mas eu realmente aprecio pontos de vista sobre qual das consultas são "quebrados" e por quê.

Foi útil?

Solução

Algo como isso, se eu entendi bem

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

EDIT:

Eu já verificado o seguinte no SQL Server (eu epxect o mesmo resultado em Oracle) e retorna o CID para o registro C com o CREATIONTIME máxima onde o STATUS para o registro relacionado na A 'OK' id.

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

demonstrada com o seguinte 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

resulta na seguinte

CID
-----------
3
4
5

EDIT 2:

Em resposta a seus comentários sobre cada uma das afirmações que dão resultados diferentes, eu corri algumas das diferentes respostas aqui através do SQL Server 2005 usando meus dados de teste acima (eu aprecio você estiver usando o Oracle). Aqui estão os resultados

--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);

Os resultados são os seguintes

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

Eu recomendaria executando cada uma das respostas dadas contra um menor número de registros, para que você possa verificar se o conjunto de resultados retornado é o esperado.

Outras dicas

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.

Selecione o campo que você está procurando usando uma junção de todas as 3 mesas e depois limitar os resultados para aqueles onde o CreationDate é o mais 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: A minha resposta anterior era um disparate. Esta é agora uma reescrita completa

Este é realmente um problema que tem me incomodado ao longo da minha vida SQL. A solução que eu estou indo dar-lhe é confuso como o inferno, mas ele funciona e eu apreciaria qualquer pessoa quer dizer "sim este é confuso como o inferno, mas é a única maneira de fazê-lo" ou dizer "não, fazer isso ... ".

Eu acho que o desconforto vem de unir duas datas. A forma como isso acontece aqui não é uma questão como eles vão ser uma correspondência exata (eles têm exatamente os mesmos dados raiz), mas ainda se sente mal ...

De qualquer forma, quebrar este para baixo, você precisa fazer isso em duas etapas.

1) A primeira é retornar um conjunto de resultados [AJUDA], [mais antiga CreationTime] dando-lhe a primeira CreationTime para cada AID.

2) Você pode então usar latestCreationTime para puxar o CID quiser.

Assim, por parte (1), eu pessoalmente criar uma visão para fazê-lo apenas para manter as coisas limpas. Ele permite que você para testar esta parte e começar a trabalhar antes de mesclá-lo com as outras coisas.

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

Note, que já não é levado em conta o estatuto neste momento.

Você precisa então juntar-se que a TableA (para obter o status) e TableB e TableC (para obter o CID). Você precisa fazer todas as ligações óbvias (AID, CID) e também juntar-se a coluna LatestCreationTime na vista à coluna CreationTime em TableC. Não esquecer também para se juntar à vista em matéria de auxílios de outra forma em que dois registros foram criados ao mesmo tempo para diferentes registros A você vai ter problemas.

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'

Estou certo de que as obras - Eu testei isso, os dados tweaked, reanalisada-lo e ele se comporta. Pelo menos ele faz o que eu acredito que está destinado a fazer.

Ele não no entanto lidar com a possibilidade de duas CreationTimes idênticos na tabela C para o mesmo registro. Eu estou supondo que isso não deve acontecer no entanto a menos que você tenha escrito algures que absolutamente constrange-lo ele precisa ser contabilizados.

Para fazer essa necessidade I de fazer uma suposição sobre qual deles você prefere. Neste caso, eu vou dizer que, se há duas CIDs que jogo, você teria um pouco maior (um é mais provável mais atualizado).

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 isso, eu acredito que deve funcionar para você. Se você quer que ele como uma consulta em vez de com a visão então:

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

(Eu apenas incorporado a visão na consulta, caso contrário, o principal é exatamente o mesmo).

Não há necessidade de uma subconsulta, a agregação para determinar o tempo mais recente criação cid é simples:

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 você realmente não quer que o CreationTime em seu conjunto de linhas, você pode simplesmente envolvê-la em uma subconsulta e soltá-lo a partir da projeção:

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ção na página web, por favor, desculpe os erros de sintaxe. Além disso, eu sou um cara mssql então eu espero que não há nada de diferente no mundo Oracle para isso ..

Note que o esquema que você forneceu não impõe exclusividade de CreationTime por cid. Se há sempre dois valores cid que mapeiam para um determinado valor do auxílio com o mesmo CreationTime, ambos irão ser emitidas. Se você confiar no par de cid, CreationTime de ser único, você deve aplicá-la de forma declarativa com uma restrição.

Estou faltando alguma coisa? O que está errado com:

EDIT: Ok., Vejo que você realmente deseja agrupar pela ajuda

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top