SQL SELECT:サブクエリを使用して3つのテーブル間でデータを結合およびグループ化する

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

  •  03-07-2019
  •  | 
  •  

質問

説明的なタイトルではなく長い質問で申し訳ありませんが、私の問題は簡単に説明するのは非常に困難です。

3つのデータベーステーブルがあります:

TABLE A:  
AID PK  
STATUS VARCHAR

TABLE B:  
BID PK  
AID FK  
CID FK

TABLE C:  
CID PK  
CREATIONTIME DATE

テーブルAの各STATUS = 'OK'行ごとに、Cで対応する最新の作成時刻を持つ行を検索します。

最初に、STATUS = 'OK'であるテーブルAからすべての行をフェッチできます。
次に、対応するすべての行をテーブルBから取得できます。
しかし、そこから続行するにはどうすればよいですか?

例:

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

次のようなものを返すことができます:

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

CID 2はCID 3よりも作成時間が遅く、CID 6はCID 5よりも新しいとしましょう。これは、正しい結果が表Cの行1、2、4、6になることを意味します。

クエリでこれを表現する方法はありますか?

編集: 申し訳ありませんが、私は十分に具体的ではありませんでした。取得したいのは、テーブルCのCIDです。

編集: さまざまなソリューションで返された行をカウントしました。結果は非常に興味深いものでしたが、多様化されました:
HAINSTECH:298 473行
JMUCCHIELLO:298 473行
RUSS CAM:290 121行
クリス:344 093行
ティラノサウルス:290 119行

返された行を詳細に分析する時間はまだありませんが、どのクエリが「壊れている」かについての意見を本当に感謝します。そしてその理由。

役に立ちましたか?

解決

あなたのことを正しく理解していれば、このようなもの

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

編集:

SQL Serverで次の項目を確認しました(Oracleでも同じ結果になります)。 C レコードの CID を最大 CREATIONTIME ここで、 A id 'OK' の関連レコードの STATUS

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

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

次の結果

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

編集2:

さまざまな結果を与える各ステートメントについてのコメントに応えて、上記のテストデータを使用してSQL Server 2005でさまざまな回答をいくつか実行しました(Oracleを使用していることに感謝します)。結果は次のとおりです

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

結果は次のとおりです

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

返された結果セットが期待されるものであるかどうかを確認できるように、指定された各回答を少数のレコードに対して実行することをお勧めします。

他のヒント

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.

3つのテーブルすべての結合を使用して探しているフィールドを選択し、CREATIONDATEが最新のものに結果を制限します。

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

編集:以前の答えはナンセンスでした。これは完全な書き換えです

これは実際、SQLライフ全体を通して私を悩ませてきた問題です。私があなたに与える解決策は地獄のように面倒ですが、それは機能し、「これは地獄のように面倒ですが、それがそれを行う唯一の方法です」と言っている人に感謝しますまたは、「いいえ、これを行います...」と言います。

不安は2つのデートに参加することから来ると思います。ここで発生する方法は完全に一致するため問題ではありません(それらはまったく同じルートデータを持っています)が、それでも間違っていると感じます...

とにかく、これを分解するには、2段階でこれを行う必要があります。

1)1つ目は、結果セット[AID]、[earliest CreationTime]を返し、各AIDの最も早い作成時間を提供することです。

2)その後、latestCreationTimeを使用して、必要なCIDを取得できます。

そのため、パート(1)では、物事を整理するためだけにそれを行うビューを個人的に作成します。この部分をテストして、他の要素とマージする前に動作させることができます。

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

注、現時点ではステータスを考慮していません。

次に、TableA(ステータスを取得するため)とTableBおよびTableC(CIDを取得するため)にそれを結合する必要があります。すべての明らかなリンク(AID、CID)を実行し、ビューのLatestCreationTime列をTableCのCreationTime列に結合する必要があります。また、AIDのビューに参加することを忘れないでください。そうしないと、異なるAレコードに対して同時に2つのレコードが作成され、問題が発生します。

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'

動作することは確かです-テストし、データを調整し、再テストし、動作しました。少なくとも、私はそれが意図されていると思うことをします。

ただし、同じレコードのテーブルCにある2つの同一のCreationTimesの可能性は処理しません。ただし、説明する必要のある絶対的な制約を書いているのでない限り、これは発生しないはずです。

これを行うには、どちらを優先するかを推測する必要があります。この場合、一致するCIDが2つある場合は、高い方を使用することをお勧めします(最も可能性が高いのは最新です)。

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

そして、それはあなたのために働くべきだと思います。ビューではなく1つのクエリとして使用する場合:

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

(クエリにビューを埋め込みましたが、それ以外はプリンシパルはまったく同じです。)

サブクエリは必要ありません。最新のcid作成時間を決定するための集約は簡単です:

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

行セットに作成時間を本当に必要としない場合は、サブクエリで作成時間をラップし、プロジェクションからドロップするだけです。

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

ウェブページのコーディングは、構文の間違いを許してください。また、私はmssqlの男なので、このためにOracleの世界で何も変わらないことを願っています。

指定したスキーマは、CIDごとのCREATIONTIMEの一意性を強制しません。同じ作成時間で特定のエイド値にマップするcid値が2つある場合、それらは両方とも出力されます。 cid、creationtimeのペアが一意であることに依存している場合は、制約を使用して宣言的に強制する必要があります。

何か不足していますか?問題点:

編集:さて、あなたは実際に援助によってグループ化したいと思います。

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
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top