Соедините запрос только с теми столбцами, которые имеют все значения в предложении `in`

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

ПРИМЕЧАНИЕ: все идентификаторы являются uuid. Делаем их короткими для простоты

места:

| 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.

Выходные данные, которые я ищу, когда переданные идентификаторы удобств равны 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';

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


person user10457989    schedule 08.02.2019    source источник
comment
поделитесь своим результатом   -  person Zaynul Abadin Tuhin    schedule 08.02.2019
comment
Каков ожидаемый результат с этими примерами данных таблицы? (Кстати, «aaa» легко читается, а «0cbe0352-89b6-4ed5-8a4e-b8127d32b5b3» — нет.)   -  person jarlh    schedule 08.02.2019
comment
Сделайте WHERE IN (список идентификаторов удобств). ГРУППИРОВАТЬ ПО, СЧЕТЧИК = количество удобств.   -  person jarlh    schedule 08.02.2019
comment
@jarlh, не могли бы вы поделиться ответом с примером?   -  person user10457989    schedule 08.02.2019
comment
@user10457989 user10457989 обязательно примите ответ, теперь люди дали вам рабочие решения.   -  person JGFMK    schedule 08.02.2019


Ответы (4)


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

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

person a_horse_with_no_name    schedule 08.02.2019
comment
Нужно ли приводить типы для uuids? - person user10457989; 08.02.2019
comment
@ user10457989: да для литерала массива: array['...', '...']::uuid[] - person a_horse_with_no_name; 08.02.2019
comment
@ user10457989: я обновил онлайн-пример, чтобы использовать UUID. - person a_horse_with_no_name; 08.02.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
               )
      );

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

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

person Laurenz Albe    schedule 08.02.2019
comment
Где я могу пройти в amenity_ids? - person user10457989; 08.02.2019
comment
Я расширил ответ, чтобы показать, где можно добавить условия. - person Laurenz Albe; 08.02.2019
comment
Я попробовал этот запрос. То же, что и ваш запрос без предложения v.name in. SELECT v.* FROM venues v WHERE NOT EXISTS ( SELECT 1 FROM amenities AS a WHERE a.name IN ('0cbe0352-89b6-4ed5-8a4e-b8127d32b5b3', '80623c5b-f794-4c17-913a-88b8ac147f1c') AND NOT EXISTS ( SELECT 1 FROM amenity_venue AS av WHERE av.venue_id = v.id AND av.amenity_id = a.id ) ); это вернуло все места в db. - person user10457989; 08.02.2019
comment
Если вы передаете UUID, а не имена, используйте a.id IN ... вместо a.name IN .... - person Laurenz Albe; 08.02.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
  )

Рабочее решение

person Derviş Kayımbaşıoğlu    schedule 08.02.2019
comment
Спасибо за ответ; однако это по-прежнему возвращает все места, которые имеют либо «aaa», либо «bbb». Одним из улучшений этого запроса является то, что он группирует места проведения. - person user10457989; 08.02.2019
comment
Я обновил свой запрос соответственно. Можешь еще раз проверить? - person Derviş Kayımbaşıoğlu; 08.02.2019
comment
Теперь это не возвращает мест. - person user10457989; 08.02.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
)

Верно:

  • Существенной частью этого является возврат места проведения_id только для внешнего выбора.
  • Вам нужна группа, чтобы облегчить использование телеги.
  • Наличие — это совокупная форма предложения where.
  • Наличие 2 гарантирует, что оба aaa AND 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

person JGFMK    schedule 08.02.2019
comment
Это возвращает все места, которые имеют отношения с удобствами aaa или bbb. - person user10457989; 08.02.2019
comment
Я изменил SQL соответственно - person JGFMK; 08.02.2019
comment
Есть ли еще вопрос? Я получаю эту ошибку ERROR: syntax error at or near ")" LINE 11: ) - person user10457989; 08.02.2019
comment
Извините, упадите второй) исправил это - person JGFMK; 08.02.2019