SQL SELECT: combinaison et regroupement de données entre trois tables à l'aide de sous-requêtes

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

  •  03-07-2019
  •  | 
  •  

Question

Désolé pour une longue question et un titre pas très descriptif, mais mon problème est très difficile à expliquer brièvement.

J'ai trois tables de base de données:

TABLE A:  
AID PK  
STATUS VARCHAR

TABLE B:  
BID PK  
AID FK  
CID FK

TABLE C:  
CID PK  
CREATIONTIME DATE

Pour chaque ligne STATUS = "OK" de la table A, je souhaite trouver la ligne correspondante en C qui a la dernière heure de création.

Tout d'abord, je peux extraire toutes les lignes de la table A où STATUS = 'OK'.
Ensuite, je peux extraire toutes les lignes correspondantes de la table B.
Mais comment continuer à partir de là?

Par exemple:

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

pourrait renvoyer quelque chose comme:

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

Supposons que CID 2 ait un délai de création supérieur à CID 3 et que CID 6 soit plus récent que CID 5. Cela signifie que le résultat correct serait les lignes 1, 2, 4 et 6 du tableau C.

Y a-t-il un moyen d'exprimer cela avec une requête?

EDIT: Désolé que je n'ai pas été assez précis. Ce que je veux obtenir, ce sont les CID de la table C.

EDIT: J'ai compté les lignes retournées avec les différentes solutions. Les résultats étaient très intéressants - et diversifiés:
HAINSTECH: 298 473 lignes
JMUCCHIELLO: 298 473 lignes
RUSS CAM: 290 121 rangées
CHRIS: 344 093 rangées
TYRANNOSAURES: 290 119 lignes

Je n'ai pas encore eu le temps d'analyser en profondeur les lignes renvoyées, mais j'apprécierais vraiment le point de vue des requêtes "cassées". et pourquoi.

Était-ce utile?

La solution

Quelque chose comme ça, si je vous ai bien compris

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:

J'ai maintenant vérifié les éléments suivants dans SQL Server (le résultat serait identique pour Oracle) et renvoie le CID pour l'enregistrement C avec le code Maximum < > CREATIONTIME où le STATUS de l'enregistrement associé est 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

Démonstration avec le T-SQL

suivant
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

Résultats dans ce qui suit

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

EDIT 2:

En réponse à votre commentaire à propos de chacune des instructions donnant des résultats différents, j’ai passé ici différentes réponses à travers SQL Server 2005 en utilisant les données de test ci-dessus (je vous remercie d’avoir utilisé Oracle). Voici les résultats

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

les résultats sont les suivants

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

Je vous recommanderais d'exécuter chacune des réponses données avec un plus petit nombre d'enregistrements, afin que vous puissiez vérifier si le jeu de résultats renvoyé est celui attendu.

Autres conseils

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.

Sélectionnez le champ que vous recherchez en utilisant une jointure des 3 tables, puis limitez les résultats à ceux où CREATIONDATE est la plus récente.

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: Ma réponse précédente était un non-sens. C'est maintenant une réécriture complète

C’est en fait un problème qui m’a posé des problèmes tout au long de ma vie SQL. La solution que je vais vous donner est désordonnée, mais cela fonctionne et j'apprécierais que quiconque dise: "oui, c'est désordonné, mais c'est la seule façon de le faire". ou dites "non, faites ceci ...".

Je pense que le malaise vient de la réunion de deux dates. La façon dont les choses se passent ici n’est pas un problème car ce sera une correspondance exacte (elles ont exactement les mêmes données racine), mais on se sent toujours mal ...

Quoi qu’il en soit, vous devez procéder en deux étapes.

1) La première consiste à renvoyer un jeu de résultats [AID], [tout premier heure de la création] vous indiquant l'heure de création la plus ancienne pour chaque aide-mémoire.

2) Vous pouvez ensuite utiliser latestCreationTime pour extraire le CID souhaité.

Donc pour la partie (1), je créerais personnellement une vue pour le faire juste pour garder les choses en ordre. Cela vous permet de tester cette partie et de la faire fonctionner avant de la fusionner avec les autres éléments.

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

Notez que nous n'avons pas pris en compte l'état à ce stade.

Vous devez ensuite joindre cela à TableA (pour obtenir le statut) et à TableB et TableC (pour obtenir le CID). Vous devez faire tous les liens évidents (AID, CID) et joindre également la colonne LatestCreationTime de la vue à la colonne CreationTime de TableC. N'oubliez pas également de vous connecter à la vue sur AID, sinon vous rencontrerez des problèmes lorsque deux enregistrements ont été créés simultanément pour différents enregistrements A.

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'

Je suis certain que cela fonctionne - je l’ai testé, modifié des données, soumis à un nouveau test et il s’est comporté. Au moins, il fait ce que je crois qu’il est censé faire.

Cependant, il ne traite pas de la possibilité de deux CreationTimes identiques dans la table C pour le même enregistrement. J'imagine que cela ne devrait pas arriver, à moins que vous n'écriviez un texte qui le contraint absolument, il faut en rendre compte.

Pour ce faire, je dois supposer lequel vous préférez. Dans ce cas, je vais dire que s'il y a deux CID qui correspondent, vous préférez le plus élevé (il est probablement plus à jour).

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

Et cela, je pense que cela devrait fonctionner pour vous. Si vous le souhaitez comme une requête plutôt qu'avec la vue, alors:

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

(Je viens d'intégrer la vue dans la requête, sinon le principe est exactement le même).

Il n’est pas nécessaire de créer une sous-requête, l’agrégation permettant de déterminer la dernière heure de création du CID est simple:

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 vous ne voulez vraiment pas l'heure de création dans votre ensemble de lignes, vous pouvez simplement l'envelopper dans une sous-requête et la supprimer de la projection:

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

Codage dans la page Web, veuillez excuser les erreurs de syntaxe. De plus, je suis un gars mssql, donc j'espère qu'il n'y a rien de différent dans le monde Oracle pour cela.

Notez que le schéma que vous avez fourni n’impose pas l’unicité de CREATIONTIME par cid. S'il y a toujours deux valeurs CID qui correspondent à une valeur d'aide donnée avec le même temps de création, elles seront toutes les deux sorties. Si vous vous fiez à la paire de cid, création au moment de la création, vous devez l’appliquer de manière déclarative avec une contrainte.

Est-ce que je manque quelque chose? Quel est le problème avec:

EDIT: D'accord, je vois que vous voulez réellement grouper par aide.

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top