SQL SELECT: Kombinieren und Gruppieren von Daten zwischen drei Tabellen mit Unterabfragen

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

  •  03-07-2019
  •  | 
  •  

Frage

Sorry für eine lange Frage und nicht sehr aussagekräftige Titel, aber mein Problem ist sehr schwierig, kurz zu erklären.

Ich habe drei Datenbanktabellen:

TABLE A:  
AID PK  
STATUS VARCHAR

TABLE B:  
BID PK  
AID FK  
CID FK

TABLE C:  
CID PK  
CREATIONTIME DATE

Für jeden STATUS = ‚OK‘ Zeile in Tabelle A I die entsprechende Zeile in C finden will, die die neueste Kreation Zeit hat.

Zuerst kann ich alle Zeilen aus Tabelle A holen, wo STATUS = ‚OK‘.
Weiter kann ich alle entsprechenden Zeilen aus Tabelle B.
holen Aber wie von dort weiter?

Zum Beispiel:

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

zurückkehren könnte so etwas wie:

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

Lassen Sie uns sagen, dass CID 2 hat später Erstellungszeit als CID 3 und CID 6 ist neuer als CID 5. Dies bedeutet, dass das richtige Ergebnis Reihen wäre 1, 2, 4 und 6 in Tabelle C.

Gibt es eine Möglichkeit, dies mit einer Abfrage auszudrücken?

EDIT: Sorry, dass ich nicht spezifisch genug war. Was will ich aus der Tabelle C ist die CIDs erhalten.

EDIT: Ich zählte Reihen mit den verschiedenen Lösungen zurück. Die Ergebnisse waren sehr interessant - und diversifiziert:
HAINSTECH: 298 473 Zeilen
JMUCCHIELLO: 298 473 Zeilen
RUSS CAM: 290 121 Zeilen
CHRIS: 344 093 Zeilen
Tyrannosaurier: 290 119 Zeilen

Ich hatte noch nicht die Zeit zurückgegebenen Zeilen in der Tiefe zu analysieren, aber ich würde wirklich zu schätzen, Ansichten, auf denen der Abfragen ist „gebrochen“ und warum.

War es hilfreich?

Lösung

So etwas, wenn ich Sie richtig verstanden habe

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:

ich jetzt überprüft haben, die in SQL Server folgende (ich würde das gleiche Ergebnis in Oracle epxect), und es gibt die CID für den C Datensatz mit dem Maximum CREATIONTIME wo die STATUS für den verknüpften Datensatz 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

Nachgewiesene mit der folgenden 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

Ergebnisse in der folgenden

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

EDIT 2:

Als Antwort auf Ihre Kommentare über jede der Aussagen zu unterschiedlichen Ergebnissen zu geben, ich habe einige der verschiedenen Antworten hier über SQL Server 2005 lief meine Testdaten unter Verwendung von oben (I schätzen Sie Oracle). Hier sind die Ergebnisse

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

Die Ergebnisse sind wie folgt

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

Ich würde empfehlen, jede der gegebenen Antworten gegen eine kleinere Anzahl von Datensätzen ausgeführt wird, so dass Sie, ob die Suchresultates zurück feststellen kann die erwartete ist.

Andere Tipps

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.

Wählen Sie das Feld, das Sie suchen eine Verknüpfung aller drei Tabellen und begrenzen die Ergebnisse dann zu denen, wo die creation die jüngste ist.

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: Meine bisherige Antwort war Unsinn. Dies ist nun eine komplette Neufassung

Dies ist tatsächlich ein Problem, das mich während meiner gesamten SQL Leben abgehört hat. Die Lösung, die ich werde euch geben, ist wie die Hölle chaotisch, aber es funktioniert und ich würde schätzen jemand entweder sagen „ja, das wie die Hölle ist etwas chaotisch, aber es ist der einzige Weg, es zu tun“, oder sagen: „Nein, dies zu tun ... “.

Ich denke, das Unbehagen von der Teilnahme an zwei Terminen kommt. So wie es hier geschieht, es ist kein Problem, da sie eine genaue Übereinstimmung sein (sie haben genau die gleichen Stammdaten), aber es fühlt sich immer noch falsch ...

Wie auch immer, diese nach unten zu brechen, müssen Sie dies in zwei Stufen tun.

1) Die erste ist eine Ergebnismenge [HILFE] zurückzukehren, [früheste Creation] geben Sie die früheste creation für jede AID.

2) Sie können dann latestCreationTime verwenden, um die CID Sie wollen ziehen.

So zum Teil (1), würde ich persönlich eine Ansicht erstellen, es zu tun nur Dinge ordentlich zu halten. Es ermöglicht Ihnen, diesen Teil zu testen und bekommen es funktioniert, bevor Sie es mit den anderen Sachen zusammenführen.

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

Beachten Sie, haben wir nicht berücksichtigt den Status an dieser Stelle.

Sie dann beitreten müssen, dass zu TableA (um den Status) und TableB und TableC (um die CID zu erhalten). Sie müssen alle offensichtlichen Verbindungen (AID, CID) zu tun und auch die LatestCreationTime Spalte in der Ansicht auf die Spalte in Creation TableC zu verbinden. Sie auch nicht vergessen, den Blick auf AID beizutreten sonst wo zwei Datensätze werden zur gleichen Zeit für verschiedene A-Datensätze erstellt werden Sie Probleme bekommen.

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'

Ich bin sicher, das funktioniert - ich habe es ausprobiert, gezwickt Daten, erneut getestet, und es verhält. Wenigstens tut es das, was ich es soll tun glauben.

Es ist jedoch nicht mit der Möglichkeit von zwei identischen CreationTimes in Tabelle C für denselben Datensatz beschäftigen. Ich vermute, dass dies jedoch nicht geschehen soll, wenn Sie irgendwann geschrieben haben, dass es absolut zwingt es braucht zu bilanzieren.

Um dies zu tun, muss ich eine Annahme machen, über die man Sie bevorzugen würden. In diesem Fall werde ich sagen, dass wenn es zwei CIDs sind, die übereinstimmen, würden Sie lieber die höhere haben (es ist höchstwahrscheinlich mehr auf dem neuesten Stand).

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

Und das, glaube ich, sollte für Sie arbeiten. Wenn Sie es als eine Abfrage wollen, anstatt mit der Ansicht, dann:

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

(Ich habe gerade die Ansicht in der Abfrage eingebettet, da sonst das Haupt ist genau das gleiche).

Es besteht keine Notwendigkeit für eine Unterabfrage, die Aggregation der neueste cid Erstellungszeit ist einfach zu bestimmen:

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

Wenn Sie wirklich nicht die creation in Ihrer Zeile setzen möchten, können Sie einfach wickeln in einer Unterabfrage und legen Sie es aus der Projektion:

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

Codierung in der Web-Seite, entschuldigen Sie bitte Syntaxfehler. Auch ich bin ein mssql Kerl so dass ich hoffe, es ist nichts anderes in der Oracle-Welt für diesen ..

Beachten Sie, dass das Schema, das Sie zur Verfügung gestellt haben, nicht Einzigartigkeit CREATION pro cid erzwingen. Wenn es je zwei cid Werte sind, die mit dem gleichen creation zu einem bestimmten Beihilfewert zuzuordnen, werden sie beide ausgegeben werden. Wenn Sie auf das Paar cid verlassen, creation einzigartig sein, können Sie es deklarativ mit einer Einschränkung erzwingen sollte.

Bin ich etwas fehlt? Was ist falsch an:

EDIT: Okay, ich sehe, Sie tatsächlich mit Hilfe gruppieren möchten

.
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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top