Pregunta

Perdón por una pregunta larga y un título no muy descriptivo, pero mi problema es muy difícil de explicar brevemente.

Tengo tres tablas de base de datos:

TABLE A:  
AID PK  
STATUS VARCHAR

TABLE B:  
BID PK  
AID FK  
CID FK

TABLE C:  
CID PK  
CREATIONTIME DATE

Para cada fila STATUS = 'OK' en la tabla A, quiero encontrar la fila correspondiente en C que tiene el último tiempo de creación.

Primero puedo recuperar todas las filas de la tabla A donde STATUS = 'OK'.
A continuación, puedo buscar todas las filas correspondientes de la tabla B.
¿Pero cómo continuar desde allí?

Por ejemplo:

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

podría devolver algo como:

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

Digamos que el CID 2 tiene un tiempo de creación posterior al CID 3 y el CID 6 es más nuevo que el CID 5. Esto significa que el resultado correcto sería las filas 1, 2, 4 y 6 en la tabla C.

¿Hay alguna forma de expresar esto con una consulta?

EDITAR: Perdón por no haber sido lo suficientemente específico. Lo que quiero obtener es el CID de la tabla C.

EDITAR: Conté las filas devueltas con las diferentes soluciones. Los resultados fueron muy interesantes y diversificados:
HAINSTECH: 298 473 filas
JMUCCHIELLO: 298 473 filas
RUSS CAM: 290 121 filas
CHRIS: 344 093 filas
TYRANNOSAURS: 290 119 filas

Todavía no he tenido tiempo de analizar las filas devueltas en profundidad, pero realmente agradecería las vistas sobre cuáles de las consultas están "rotas". y por qué.

¿Fue útil?

Solución

Algo así, si te he entendido bien

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:

Ahora he verificado lo siguiente en SQL Server (expondría el mismo resultado en Oracle) y devuelve el CID para el registro C con el código máximo < > CREATIONTIME donde STATUS para el registro relacionado en 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

Demostrado con lo siguiente 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

Resultados en lo siguiente

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

EDIT 2:

En respuesta a su comentario sobre cada una de las declaraciones que dan resultados diferentes, he ejecutado algunas de las diferentes respuestas aquí a través de SQL Server 2005 usando mis datos de prueba anteriores (aprecio que esté usando Oracle). Aquí están los 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);

los resultados son los siguientes

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

Recomendaría ejecutar cada una de las respuestas dadas con un número menor de registros, para que pueda determinar si el conjunto de resultados devuelto es el esperado.

Otros consejos

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.

Seleccione el campo que está buscando usando una combinación de las 3 tablas y luego limite los resultados a aquellos donde CREATIONDATE es el más reciente.

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

EDITAR: Mi respuesta anterior fue una tontería. Esto es ahora una reescritura completa

Este es realmente un problema que me ha molestado a lo largo de mi vida SQL. La solución que te voy a dar es desordenada como el infierno, pero funciona y agradecería a cualquiera que diga "sí, esto es desordenado como el infierno, pero es la única forma de hacerlo". o di " no, haz esto ... " ;.

Creo que la inquietud proviene de unir dos fechas. La forma en que sucede aquí no es un problema, ya que serán una coincidencia exacta (tienen exactamente los mismos datos de raíz) pero aún se siente mal ...

De todos modos, desglosando esto, debes hacerlo en dos etapas.

1) El primero es devolver un conjunto de resultados [AID], [early CreationTime] que le proporciona el tiempo de creación más temprano para cada AID.

2) Luego puede usar latestCreationTime para obtener el CID que desee.

Entonces, para la parte (1), personalmente crearía una vista para hacerlo solo para mantener las cosas ordenadas. Te permite probar esta parte y hacer que funcione antes de combinarla con otras cosas.

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, no hemos tenido en cuenta el estado en este momento.

Luego debes unir eso a TableA (para obtener el estado) y TableB y TableC (para obtener el CID). Debe hacer todos los enlaces obvios (AID, CID) y también unirse a la columna LatestCreationTime en la vista de la columna CreationTime en TableC. No olvide unirse a la vista en AID, de lo contrario, cuando se hayan creado dos registros al mismo tiempo para diferentes registros A, tendrá 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'

Estoy seguro de que funciona: lo probé, modifiqué datos, lo volví a probar y se comporta. Al menos hace lo que creo que debe hacer.

Sin embargo, no trata con la posibilidad de dos CreationTimes idénticos en la tabla C para el mismo registro. Supongo que esto no debería suceder, sin embargo, a menos que hayas escrito algo que lo limite por completo, debe tenerse en cuenta.

Para hacer esto, debo hacer una suposición sobre cuál preferiría. En este caso, voy a decir que si hay dos CID que coinciden, preferiría tener el más alto (es muy probable que esté más actualizado).

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

Y eso, creo que debería funcionar para ti. Si lo desea como una consulta en lugar de con la vista, entonces:

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

(Acabo de incrustar la vista en la consulta, de lo contrario, el principal es exactamente el mismo).

No hay necesidad de una subconsulta, la agregación para determinar el último tiempo de creación de cid es sencilla:

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

Si realmente no quieres el tiempo de creación en tu conjunto de filas, puedes envolverlo en una subconsulta y eliminarlo de la proyección:

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

Codificación en la página web, disculpe los errores de sintaxis. Además, soy un tipo mssql, así que espero que no haya nada diferente en el mundo de Oracle para esto ...

Tenga en cuenta que el esquema que ha proporcionado no impone la exclusividad de CREATIONTIME por cid. Si alguna vez hay dos valores cid que se asignan a un valor de ayuda dado con el mismo tiempo de creación, se emitirán ambos. Si confía en el par de cid, el tiempo de creación para ser único, debe imponerlo de forma declarativa con una restricción.

¿Me estoy perdiendo algo? Lo que está mal con:

EDITAR: está bien, veo que realmente quieres agrupar por ayuda.

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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top