SQL-запрос для получения записей родительской таблицы, имеющих список дочерних записей - PullRequest
3 голосов
/ 29 января 2010

У меня есть две таблицы в базе данных MS SQL Server 2005, родительская и дочерняя, где родитель может быть связан со многими дочерними записями. [Child.parent_id] связан с [parent.id]. Дочерняя таблица также имеет столбец [foo]. Мне нужно вернуть все записи в родительской таблице, где [child.foo] соответствует каждому из нескольких параметров. Например, я хотел бы, чтобы все родительские записи имели значение [child.foo] 'fizz' и значение [child.foo] 'buzz.' Я пробовал приведенный ниже запрос, но он возвращает записи, которые соответствуют только одному.

SELECT     Parent.ID
FROM         Parent 
INNER JOIN Child ON Parent.ID = Child.parent_id
WHERE     (Child.foo = 'fizz')
UNION ALL
SELECT     Parent_1.ID
FROM         Parent AS Parent_1 
INNER JOIN Child AS Child_1 ON Parent_1.ID = Child_1.parent_id
WHERE     (Child_1.foo = 'buzz')

Ответы [ 4 ]

7 голосов
/ 30 января 2010

Это вернет все родительские записи, у которых есть [по крайней мере] один ребенок с foo 'fizz' И [по крайней мере] один ребенок с foo 'buzz'. Что, я думаю, требуется в вопросе.

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

   SELECT DISTINCT Parent.ID
    FROM Parent
    JOIN Child C1 ON Parent.ID = C1.parent_Id
    JOIN Child C2 ON Parent.ID = C2.parent_id
    WHERE C1.foo = 'fizz'
      AND C2.foo = 'buzz'

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

  • структура запроса не изменяется, когда мы добавляем или удаляем целевые значения для столбца foo.
  • запрос, вероятно, легче оптимизировать [самим сервером]
  • структура запроса позволяет ему обрабатывать изменения в определении фильтра. Например, мы можем запросить всех родителей, у которых есть дети, например, что присутствуют 2 из 5 возможных значений foo.

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

SELECT Parent.Id
FROM Parent
INNER JOIN Child on Parent.Id = child.parent_id
WHERE Child.foo IN ('fizz', 'buzz')  -- or say, ... IN ('fizz', 'buzz', 'bang', 'dong')
GROUP BY Parent.Id
HAVING COUNT(DISTINCT Child.foo) = 2  -- or 4 ...  
6 голосов
/ 30 января 2010

Полагаю, вы хотите что-то подобное.

Select 
    Parent.Id
From Parent
    inner join Child on Parent.Id = child.parent_id
Where 
    Child.foo = 'fizz' or Child.foo = 'buzz'
Group By
    Parent.Id
Having
    count(distinct Child.foo) > 1

Вот тестовый скрипт:

Create Table #parent ( id int )
Create Table #child ( parent_id int, foo varchar(32) )

insert into #parent (id) values (1)
insert into #parent (id) values (2)
insert into #parent (id) values (3)

insert into #child (parent_id, foo) values (1, 'buzz')
insert into #child (parent_id, foo) values (2, 'buzz')
insert into #child (parent_id, foo) values (3, 'buzz')
insert into #child (parent_id, foo) values (1, 'fizz')


Select 
    #parent.Id
From #parent
    inner join #child on #parent.id = #child.parent_id
Where 
    #child.foo = 'fizz' or #child.foo = 'buzz'
Group By
    #parent.Id
Having
    count(distinct #child.foo) > 1        

drop table #parent
drop table #child

Возвращает только Id 1.

0 голосов
/ 12 июля 2016

Я хотел бы поделиться этим простым обобщением превосходного ответа Джоэла. Идея заключается в том, чтобы иметь возможность передавать произвольную таблицу «целевых» потомков в процедуру в виде параметра с табличным значением или строки с разделителями. Хотя это хорошо, было бы также неплохо иметь аналогичный запрос, который соответствовал бы с использованием LIKE вместо IN.

--Parents whose children contain a subset of children

--setup
create table #parent ( id int )
create table #child ( parent_id int, foo varchar(32) )

insert into #parent (id) values (1)
insert into #parent (id) values (2)
insert into #parent (id) values (3)

insert into #child (parent_id, foo) values (1, 'buzz')
insert into #child (parent_id, foo) values (1, 'buzz')
insert into #child (parent_id, foo) values (1, 'fizz')
insert into #child (parent_id, foo) values (2, 'buzz')
insert into #child (parent_id, foo) values (2, 'fizz')
insert into #child (parent_id, foo) values (2, 'bang')
insert into #child (parent_id, foo) values (3, 'buzz')

--create in calling procedure
declare @tblTargets table (strTarget varchar(10))
insert into @tblTargets (strTarget) values ('fizz')
insert into @tblTargets (strTarget) values ('buzz')

--select query to be called in procedure; 
--  pass @tblTargets in as TVP, or create from delimited string via splitter function
select #parent.id       --returns 1 and 2
    from #parent
       inner join #child on #parent.id = #child.parent_id
    where #child.foo in (select strTarget from @tblTargets)
    group by #parent.id
    having count(distinct #child.foo) = (select COUNT(*) from @tblTargets)        

--cleanup
drop table #parent
drop table #child
0 голосов
/ 30 января 2010

Это должно получить желаемые результаты:

SELECT p.ID
FROM Parent p
WHERE EXISTS
(
    SELECT 1 FROM Child c WHERE c.parent_id = p.ID AND c.foo IN ('fizz','buzz')
)
...