SQL-запрос: EXISTS в подтаблике
У меня есть две таблицы tabData и tabDataDetail.
Я хочу, чтобы все idData (PK) из Parent-Table (tabData) имели строки только в Child-Table (tabDataDetail, FK is fiData):
- Только fiActionCode = 11
или
- fiactionCode = 11 и fiActionCode = 34
Любая другая комбинация недействительна. Как их получить?
Что я пробовал без успеха (медленный и дает мне также строки с только fiActioncode 34):
alt text http://www.bilder-hochladen.net/files/4709-l0.jpg
Спасибо за ваше время.
EDIT: Спасибо всем за их ответы. Теперь у меня, к сожалению, недостаточно времени, чтобы проверить, какой из них лучше или работает вообще. В качестве ответа я назвал первый рабочий.
EDIT2: я считаю, что отмеченный ответ действительно является самым эффективным и компактным решением.
EDIT3: ответ на Codesleuth интересен тем, что он возвращает только строки, чем только один файл fiActionCode = 11. Трудно видеть, потому что это единственно верно для 20 tabDataDetail-строк от 41524189 total-rows, которые имеют два. Во всяком случае, это не было на 100%, что я спросил или, скорее, то, что я искал.
Ответы
Ответ 1
Select ...
From tabData As T1
Where Exists (
Select 1
From tabDataDetail As TDD1
Where TDD1.fiData = T1.idData
And TDD1.fiactionCode = 11
)
And Not Exists (
Select 1
From tabDataDetail As TDD1
Where TDD1.fiData = T1.idData
And TDD1.fiactionCode Not In(11,34)
)
Чтобы расширить мою логику, первая проверка (коррекция) состоит в том, чтобы убедиться, что существует строка с fiActionCode = 11. Вторая проверка работает, сначала определяя набор строк, которые нам не нужны. Нам не нужно ничего, кроме fiActionCode = 11 или 34. Поскольку это набор элементов, которые нам не нужны, мы ищем все, что не существует в этом наборе.
Ответ 2
Рассуждение
-
LEFT OUTER JOIN
исключает все idData с идентификатором, отличным от 11 или 34
-
HAVING
исключает все idData, у которых есть только 34
- Оставшиеся записи (должны) удовлетворяют всем ограничениям
Данные тестирования
DECLARE @tabData TABLE (idData INTEGER)
DECLARE @tabDataDetail TABLE (fiData INTEGER, fiActionCode INTEGER)
INSERT INTO @tabData VALUES (1)
INSERT INTO @tabData VALUES (2)
INSERT INTO @tabData VALUES (3)
INSERT INTO @tabData VALUES (4)
INSERT INTO @tabData VALUES (5)
/* Only idData 1 & 2 should be returned */
INSERT INTO @tabDataDetail VALUES (1, 11)
INSERT INTO @tabDataDetail VALUES (2, 11)
INSERT INTO @tabDataDetail VALUES (2, 34)
INSERT INTO @tabDataDetail VALUES (3, 99)
INSERT INTO @tabDataDetail VALUES (4, 11)
INSERT INTO @tabDataDetail VALUES (4, 99)
INSERT INTO @tabDataDetail VALUES (5, 34)
Query
SELECT *
FROM @tabData d
INNER JOIN @tabDataDetail dd ON dd.fiData = d.idData
INNER JOIN (
SELECT idData
FROM @tabData d
INNER JOIN @tabDataDetail dd ON dd.fiData = d.idData
LEFT OUTER JOIN (
SELECT fiData
FROM @tabDataDetail
WHERE fiActionCode NOT IN (11, 34)
) exclude ON exclude.fiData = d.idData
WHERE exclude.fiData IS NULL
GROUP BY
idData
HAVING MIN(fiActionCode) = 11
) include ON include.idData = d.idData
Ответ 3
Изменить: Аполы. Я вижу, что вы имеете в виду с дочерними строками. Это не особенно эффективно. Спасибо также Ливену за данные.
SELECT idData FROM
tabData td
WHERE EXISTS
(
SELECT 1
FROM tabDataDetail tdd
WHERE tdd.fiData = td.idData AND fiActionCode = 11
)
AND NOT EXISTS
(
SELECT 1
FROM tabDataDetail tdd
WHERE tdd.fiData = td.idData AND fiActionCode <> 11
)
UNION
SELECT idData
FROM tabData td
WHERE EXISTS
(
SELECT 1
FROM tabDataDetail tdd
WHERE tdd.fiData = td.idData AND fiActionCode = 11
)
AND EXISTS
(
SELECT 1
FROM tabDataDetail tdd
WHERE tdd.fiData = td.idData AND fiActionCode = 34
)
AND NOT EXISTS
(
SELECT 1
FROM tabDataDetail tdd
WHERE tdd.fiData = td.idData AND fiActionCode NOT IN (11, 34)
)
Ответ 4
Отредактировал мой ответ на основе пояснений, приведенных в комментариях к другим ответам.
select td.idData
from tabData td
left join tabDataDetail tdd
on td.idData = tdd.fiData
and tdd.fiActionCode = 11
left join tabDataDetail tdd2
on td.idData = tdd2.fiData
and tdd2.fiActionCode = 34
left join tabDataDetail tdd3
on td.idData = tdd3.fiData
and tdd3.fiActionCode not in (11,34)
where (tdd.fiData is not null
or (tdd.fiData is not null and tdd2.fiData is not null))
and tdd3.fiData is null
group by td.idData
Ответ 5
Спасибо @Lieven за код данных, чтобы проверить это:
DECLARE @tabData TABLE (idData INTEGER)
DECLARE @tabDataDetail TABLE (idDataDetail int IDENTITY(1,1),
fiData INTEGER, fiActionCode INTEGER)
INSERT INTO @tabData VALUES (1)
INSERT INTO @tabData VALUES (2)
INSERT INTO @tabData VALUES (3)
INSERT INTO @tabData VALUES (4)
INSERT INTO @tabData VALUES (5)
/* Only idData 1 & 2 should be returned */
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (1, 11)
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (2, 11)
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (2, 34)
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (3, 99)
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (4, 11)
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (4, 99)
INSERT INTO @tabDataDetail (fiData,fiActionCode) VALUES (5, 34)
Query:
SELECT td.idData
FROM @tabData td
INNER JOIN @tabDataDetail tdd ON td.idData = tdd.fiData
WHERE tdd.fiActionCode = 11 -- check 11 exists
AND NOT EXISTS ( SELECT * FROM @tabDataDetail WHERE fiData = td.idData
AND idDataDetail <> tdd.idDataDetail )
-- ensures *only* 11 exists (0 results from subquery)
UNION
SELECT td.idData
FROM @tabData td
INNER JOIN @tabDataDetail tdd1 ON td.idData = tdd1.fiData
INNER JOIN @tabDataDetail tdd2 ON td.idData = tdd2.fiData
WHERE tdd1.fiActionCode = 11 -- check 11 exists
AND tdd2.fiActionCode = 34 -- check 34 exists
Возврат:
idData
-----------
1
2
(2 row(s) affected)
Здесь только один подзапрос (и это COUNT
вместо очень медленного NOT EXISTS
), это создает очень аккуратный план выполнения, который должен помочь, если у вас возникают проблемы со скоростью.
Ответ 6
Это делается с помощью одного прохода через данные, которые я думаю.
В зависимости от распределения данных, было бы предпочтительнее делать два отдельных поиска.
WITH matches AS
(
SELECT fiData
FROM tabDataDetail
GROUP BY fiData
HAVING COUNT(CASE WHEN fiactionCode = 11 THEN 1 END) > 0
AND COUNT(CASE WHEN fiactionCode NOT IN (11,34) THEN 1 END) = 0
)
SELECT ...
FROM idData i
JOIN matches m
ON m.fiData = i.idData