Соединить запрос только со столбцами, которые имеют все значения в предложении `in` - PullRequest
0 голосов
/ 08 февраля 2019

Я создаю простую систему фильтрации для моего сайта.У меня есть много-много отношений между местами и удобствами.Вот мои таблицы.

ПРИМЕЧАНИЕ: все идентификаторы - это uuids.Сокращая их для простоты

места проведения:

| id    |      name      |
_________________________
| 'aaa' |  'first venue' |
| 'bbb' | 'second venue' |
| 'ccc' | 'third venue'  |

удобства:

| id    |      name        |
___________________________
| 'aaa' |  'first amenity' |
| 'bbb' | 'second amenity' |
| 'ccc' | 'third amenity'  |

amenity_venue:

| amenity_id  |    venue_id  |
______________________________
| 'aaa'       |  'aaa'       |
| 'bbb'       | 'aaa'        |
| 'ccc'       | 'aaa'        |
| 'aaa'       | 'bbb'        |
| 'bbb'       | 'ccc'        |

Я пытаюсь написатьзапрос, чтобы вернуть места, которые имеют по крайней мере все переданные в amenity_ids.Например, передавая amenity_ids aaa и bbb.

Вывод Я ищу, когда переданные идентификаторы amenity равны aaa и bbb.

| id    |      name      |
_________________________
| 'aaa' |  'first venue' |

ИзначальноЯ пробовал этот запрос

select * from venues 
INNER JOIN amenity_venue ON amenity_venue.venue_id = venues.id
where amenity_id in ('aaa', 'bbb');

Это возвращает все места, которые имеют amenity_id aaa или bbb

| id    |      name      |
_________________________
| 'aaa' |  'first venue' |
| 'bbb' | 'second venue' |
| 'ccc' | 'third venue'  |

, поэтому наивно я пытался

select * from venues 
INNER JOIN amenity_venue ON amenity_venue.venue_id = venues.id
where amenity_id = 'aaa'
  and amenity_id = 'bbb';

Что ничего не возвращает.Я пытаюсь написать запрос, в котором если amenity_ids aaa и bbb передаются только в место, возвращается aaa, поскольку это единственное место, которое имеет отношение к обоим удобствам.Также количество удобств является динамическим от запроса к запросу.

Ответы [ 4 ]

0 голосов
/ 08 февраля 2019

Вы можете сделать это путем объединения идентификаторов в массив, а затем сравнить его со списком предполагаемых идентификаторов:

select v.*
from venues v
  join amenity_venue av ON av.venue_id = v.id
group by v.id
having array_agg(av.amenity_id) @> array['aaa', 'bbb'];

В приведенном выше предположении venue.id объявлено в качестве первичного ключа (из-заgroup by).

Вам на самом деле не нужно жестко кодировать идентификаторы в запросе, если вы просто хотите передать имена объектов:

select v.*
from venues v
  join amenity_venue av ON av.venue_id = v.id
group by v.id
having array_agg(av.amenity_id) @> array(select id 
                                         from amenities 
                                         where name in ('first amenity', 'second amenity'));

Онлайн пример: https://rextester.com/FNNVXO34389

0 голосов
/ 08 февраля 2019

Я думаю, что вы ищете

SELECT v.*
FROM venues v
WHERE v.name IN (/* list of venues names */)
  AND NOT EXISTS (
         SELECT 1
         FROM amenities AS a
         WHERE a.name IN (/* list of amenity names */)
           AND NOT EXISTS (
                  SELECT 1
                  FROM amenity_venue AS av
                  WHERE av.venut_id = v.id
                    AND av.amenity_id = a.id
               )
      );

Это должно работать независимо от количества удобств.

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

0 голосов
/ 08 февраля 2019
select * from venues where id in(
 select venue_id
 from amenity_venue
 where amenity_id in('aaa','bbb')
 group by venue_id
 having count(1) = 2
)

Справа:

  • Важной частью этого является возвращение только venue_ids для внешнего выбора.
  • Вам нужно сгруппировать, чтобы облегчить использование.
  • Наличие - это агрегированная форма предложения where.
  • Наличие 2 гарантирует, что оба aaa И bbb присутствуют для возврата идентификатора места во внутреннем выборе, а не по умолчанию OR .
  • count (1) - вы можете использовать номера столбцов вместо звездочки или имени столбца для агрегатных функций.

Доказательство того, что код работает:

https://rextester.com/TXQB38528

Я сделал несколько незначительных изменений и добавил эту версию.

with amenities as (select 'aaa' as amenity_id UNION select 'bbb'),
ac as (select count(amenity_id) as tally from amenities)
select * from venues where id in(
 select venue_id
 from amenity_venue
 where amenity_id in(select amenity_id from amenities)
 group by venue_id
 having count(1) = (select tally from ac)
);

Таким образом, вы не ограничены обслуживанием.Вы можете видеть это здесь.https://rextester.com/TKRF28879

0 голосов
/ 08 февраля 2019

Это то, что вы ищете?

select * from venues 
where  exists (
    select venue_id from amenity_venue 
    where venues.id = amenity_venue.venue_id and amenity_id in ('aaa', 'bbb')
    group by venue_id
    having count(*) = 2
  )

Рабочий раствор

...