SQL ВЫБОР:объединение и группировка данных между тремя таблицами с помощью подзапросов
Вопрос
Извините за длинный вопрос и не очень информативное название, но мою проблему очень сложно объяснить вкратце.
У меня есть три таблицы базы данных:
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