SQL ВЫБОР:объединение и группировка данных между тремя таблицами с помощью подзапросов

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

  •  03-07-2019
  •  | 
  •  

Вопрос

Извините за длинный вопрос и не очень информативное название, но мою проблему очень сложно объяснить вкратце.

У меня есть три таблицы базы данных:

TABLE A:  
AID PK  
STATUS VARCHAR

TABLE B:  
BID PK  
AID FK  
CID FK

TABLE C:  
CID PK  
CREATIONTIME DATE

Для каждой строки STATUS = 'OK' в таблице A я хочу найти соответствующую строку в C, которая имеет самое позднее время создания.

Сначала я могу получить все строки из таблицы A, где STATUS = 'OK'.
Затем я могу получить все соответствующие строки из таблицы 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.Это означает, что правильным результатом будут строки 1, 2, 4 и 6 в таблице C.

Есть ли способ выразить это с помощью запроса?

РЕДАКТИРОВАТЬ:Извините, что я был недостаточно конкретен.Я хочу получить CID из таблицы C.

РЕДАКТИРОВАТЬ:Я посчитал возвращенные строки с разными решениями.Результаты были очень интересными и разнообразными:
ХЕЙНСТЕХ:298 473 строки
ДЖМУКЬЕЛЛО:298 473 строки
РУССКАЯ КАМЕРА: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), и он возвращает CID для C запись с максимальным CREATIONTIME где STATUS для соответствующей записи в A идентификатор '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

Продемонстрировано следующим образом Т-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.

Выберите поле, которое вы ищете, используя объединение всех трех таблиц, а затем ограничьте результаты теми, в которых 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.Решение, которое я собираюсь вам предложить, чертовски запутано, но оно работает, и я был бы признателен, если бы кто-нибудь сказал: «Да, это чертовски запутанно, но это единственный способ сделать это» или сказал: «Нет, сделай это... ".

Я думаю, беспокойство возникает из-за совмещения двух свиданий.То, как это происходит здесь, не является проблемой, поскольку они будут точно совпадать (у них точно такие же корневые данные), но это все равно кажется неправильным...

В любом случае, разбивая это, вам нужно сделать это в два этапа.

1) Первый — вернуть набор результатов [AID], [earliest CreationTime], предоставляющий самое раннее время создания для каждого AID.

2) Затем вы можете использовать lateCreationTime, чтобы получить нужный 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 в представлении к столбцу CreationTime в TableC.Не забудьте также присоединиться к представлению AID, иначе, если одновременно будут созданы две записи для разных записей 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'

Я уверен, что это работает — я проверял это, настраивал данные, перепроверял, и все работает.По крайней мере, он делает то, для чего, по моему мнению, он предназначен.

Однако он не учитывает возможность наличия двух идентичных времен создания в таблице C для одной и той же записи.Я предполагаю, что этого не должно произойти, однако, если вы не написали что-то, что абсолютно ограничивает это, это необходимо учитывать.

Для этого мне нужно сделать предположение о том, какой из них вы бы предпочли.В этом случае я хочу сказать, что если есть два совпадающих CID, вы бы предпочли более высокий (скорее всего, он более актуальный).

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

И это, я считаю, должно сработать для вас.Если вы хотите, чтобы это был один запрос, а не представление, тогда:

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 для этого нет ничего особенного.

Обратите внимание, что предоставленная вами схема не обеспечивает уникальность CREATIONTIME для каждого идентификатора.Если когда-либо существуют два значения cid, которые соответствуют данному значению помощи с одинаковым временем создания, они оба будут выведены.Если вы полагаетесь на уникальность пары 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