仅使用具有“in”子句中所有值的列加入查询

Posted

技术标签:

【中文标题】仅使用具有“in”子句中所有值的列加入查询【英文标题】:Join query with only columns that have all values in `in` clause 【发布时间】:2019-02-08 09:37:47 【问题描述】:

我正在为我的网站创建一个简单的过滤系统。我在场地和设施之间有多对多的关系。这是我的桌子。

注意:所有 id 都是 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 aaabbb

当传入的便利设施 ID 为 aaabbb 时,我正在寻找输出。

| 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 aaabbb 的场所

| 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 aaabbb 在仅场地 aaa 中传递,则返回,因为它是唯一与这两个设施有关系的场地。此外,便利设施的数量在查询之间是动态的。

【问题讨论】:

分享你的输出 有了那个样本表数据,预期的结果是什么? (顺便说一句,'aaa' 很容易阅读,但 '0cbe0352-89b6-4ed5-8a4e-b8127d32b5b3' 不是。) 在 WHERE IN(设施 ID 列表)中执行。 GROUP BY, HAVING COUNT = 便利设施数量。 @jarlh 你能用这个例子分享答案吗? @user10457989 现在人们已经为您提供了可行的解决方案,请务必接受答案。 【参考方案1】:

您可以通过将 ID 聚合到一个数组中来做到这一点,然后将其与预期 ID 列表进行比较:

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)。

如果您只想传递便利设施名称,则实际上不需要在查询中对 ID 进行硬编码:

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

【讨论】:

我需要为 uuid 进行类型转换吗? @user10457989:对于数组字面量是:array['...', '...']::uuid[] @user10457989:我已经更新了在线示例以使用 UUID【参考方案2】:

我想你正在寻找

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
               )
      );

这应该与有多少便利设施无关。

您可以在我指出的地方添加条件,以将查询限制为仅限设施或场所的某个子集。

【讨论】:

我在哪里传递amenity_ids 我已经扩展了答案以显示可以添加条件的位置。 我试过这个查询。与没有 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 中的所有场地。 如果您传递的是 UUID 而不是名称,请使用 a.id IN ... 而不是 a.name IN ...【参考方案3】:

这就是你要找的吗?

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
  )

Working Solution

【讨论】:

感谢您的回答;但是,这仍然会返回所有具有“aaa”或“bbb”的场所。这个查询的一个升级是它“分组”了场地。 我相应地更新了我的查询。你能再检查一下吗? 现在不返回场地。【参考方案4】:
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。 您需要 group by 以方便使用 have。 Having 是 where 子句的聚合形式。 拥有 2 确保 aaa AND bbb 都存在以在内部选择中返回场地 ID,而不是默认的 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

【讨论】:

这将返回所有与便利设施aaabbb 有关系的场所 count(distinct venue_id ) = 2 我已经相应地修改了 SQL 查询还有更多内容吗?我收到此错误ERROR: syntax error at or near ")" LINE 11: ) Sorry drop second ) 已修复

以上是关于仅使用具有“in”子句中所有值的列加入查询的主要内容,如果未能解决你的问题,请参考以下文章

具有许多不使用部分索引的值的 Postgres IN 子句

使用子查询添加具有不同 where 子句的列

如何为 where 子句中的列编写具有不连续值的 Cassandra 查询

仅使用 WHERE 子句组合 LIKE 和 IN

使用 Spark 执行“WHERE IN”子句,如何仅重新训练第一个数据集的列?

在主查询的“IN”子句中使用逗号分隔列表的子查询结果