我有以下客户的示例数据结构,它可以使用连接表和数据成为多个组的一部分:
CREATE TABLE customer(id) AS VALUES (0),(1),(2),(3);
CREATE TABLE groups(id) AS VALUES (1),(3),(5),(6);
CREATE TABLE customers_to_groups(customer_id, group_id) AS
VALUES (0, 1)--customer 0 is in group (5 OR 6) AND (1 OR 3)
,(0, 5)--customer 0 is in group (5 OR 6) AND (1 OR 3)
,(1, 1)
,(1, 90)
,(2, 1)
,(3, 3)--customer 3 is in group (5 OR 6) AND (1 OR 3)
,(3, 5)--customer 3 is in group (5 OR 6) AND (1 OR 3)
,(3, 90);
我需要获取属于特定组的客户,并且需要获取属于多个组列表中至少 1 个组的所有客户列表。例如,我想获取组中所有客户(5 OR 6) AND (1 OR 3)
,因此例如组 5 和 1 中的客户将被返回,但组 1 和 90 或仅组 1 中的客户则不会被返回。使用提供的样本数据,我们将仅获取 ID 为 0 和 3 的客户,因为它们符合上述给定的规则。
仅仅这样做WHERE group_id IN (5,6) AND group_id IN (1,3)
似乎不起作用,所以我正在寻找替代方案。
到目前为止我得到了这个有效的方法:
SELECT DISTINCT c.id
FROM customer c
INNER JOIN customers_to_groups at1 ON c.id = at1.customer_id
INNER JOIN customers_to_groups at2 ON c.id = at2.customer_id
WHERE at1.group_id IN (5, 6)
AND at2.group_id IN (1, 3);
预期成绩:
ID |
---|
0 |
3 |
有没有更有效的方法来实现这一点?
您可以使用 GROUP BY 和 HAVING 子句通过更优化的查询获得所需结果。这种方法避免了多次自连接的需要
索引注意事项:为了进一步提高性能,请确保在 customers_to_groups 表中的 customer_id 和 group_id 列上有一个索引:
我们可以使用
GROUP BY
客户的 ID 并使用子句。如果您的 RDBMS 支持它,HAVING
我们可以使用CASE
或。Postgres 应该。FILTER
您的条件将在那里设置。
查询将是:
或者
注意:以上查询假设您确实需要同时包含
customers
和customers_to_groups
表并将它们连接起来。如果您不需要包含表customers
,只需将其删除并仅从表中选择customers_to_groups
即可提高性能:此演示包含您的示例数据和大量附加行,展示了性能差异。
使用
FILTER
或CASE
要快得多(因为它避免了JOIN
在表 customers_to_groups 上花费一秒钟的时间)。确切的性能差异取决于表中的实际数据以及您使用的索引。这是关系除法的问题,不同之处在于您有多个除数。从效率的角度来看,您得到的结果是不错的。
但是如果您希望能够动态地执行此操作,并传递不同数量的组和组块,那么您需要一个不同的解决方案。
您可以将其作为 JSONB 数组参数传递
或者,如果你有表中的输入数据,则可以使用完整的关系划分
db<>小提琴
执行
GROUP BY
, useHAVING
子句以确保 (5, 6) 中至少有一个,以及 (1, 3) 中至少有一个存在。您还可以使用
INTERSECT
:如果这个任务是重复性的,你可以把它放在一个函数中
由于
customer_id
很可能是一个外键,customer.id
意味着两个表都具有相同的值,所以我完全没有理由去追求它customer
。一种非常 SQL 标准的方法是使用@jarlh
INTERSECT
已经建议的:ALL
关键字跳过重复数据删除,这是INTERSECT
(DISTINCT
) 的默认行为。您还可以运行自连接:
如果你喜欢@Rahul jangid和Jonas Metzler的
having
想法,那么你正在寻找的条件可以简化为bool_or()
:或者您可以使用
EXISTS
:在这个db<>fiddle 测试中,
19 000
客户被1 900 000
随机分配到一些19 000
组,该变体通过Hash Semi JoinEXISTS
赢得了最快的计划。所有这些都最好通过覆盖索引来实现,从而实现仅索引扫描:
如果您打算动态地重新使用它,请将其包装在参数化的准备好的语句中:
然后