SQL SELECT: объединение и группировка данных между тремя таблицами с использованием подзапросов - PullRequest
1 голос
/ 01 мая 2009

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

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

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.

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

EDIT: Извините, что я не был достаточно конкретен. Я хочу получить CID из таблицы C.

EDIT: Я посчитал возвращенные строки с различными решениями. Результаты были очень интересные - и разнообразные:
HAINSTECH: 298 473 строки
JMUCCHIELLO: 298 473 строк
RUSS CAM: 290 121 ряд
КРИС: 344 093 строки
ТИРАННОЗАВРЫ: 290 119 строк

У меня еще не было времени для глубокого анализа возвращаемых строк, но я очень ценю мнения о том, какие из запросов "разбиты" и почему.

Ответы [ 6 ]

4 голосов
/ 01 мая 2009

Примерно так, если я вас правильно понял

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:

Теперь я проверил следующее в SQL Server (я бы описал тот же результат в Oracle), и он возвращает CID для записи C с Максимальным CREATIONTIME, где STATUS для связанной записи в 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

Демонстрируется со следующим 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

Я бы порекомендовал выполнить каждый из приведенных ответов для меньшего числа записей, чтобы вы могли убедиться, является ли возвращенный набор результатов ожидаемым.

1 голос
/ 02 мая 2009

Выберите поле, которое вы ищете, используя объединение всех 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);
1 голос
/ 01 мая 2009
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.
1 голос
/ 01 мая 2009

РЕДАКТИРОВАТЬ: Мой предыдущий ответ был ерундой. Теперь это полная перезапись

На самом деле это проблема, которая мучила меня всю жизнь в SQL. Решение, которое я собираюсь дать вам, чертовски грязное, но оно работает, и я был бы признателен любому, кто сказал бы: «Да, это чертовски грязно, но это единственный способ сделать это» или сказать «нет, сделайте это ... ».

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

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

1) Во-первых, нужно вернуть набор результатов [AID], [самое раннее 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 в представлении к столбцу 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'

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

Однако это не касается возможности двух одинаковых CreationTimes в таблице 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

(я только что встроил представление в запрос, в противном случае принципал точно такой же).

0 голосов
/ 02 мая 2009

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

0 голосов
/ 02 мая 2009

Я что-то упустил? Что не так с:

РЕДАКТИРОВАТЬ: Хорошо, я вижу, что вы на самом деле хотите группировать по помощи.

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...