仅包含这些产品的订单(GROUP BY和HAVING)

时间:2014-04-07 19:01:51

标签: sql ruby-on-rails postgresql activerecord relational-division

我想选择只有ID为1,2,3的产品的订单,是否可能:

这是我的实际SQL查询:

SELECT COUNT(*) FROM "orders"
INNER JOIN "line_items" ON "line_items"."order_id" = "orders"."id"
INNER JOIN "products" ON "products"."id" = "line_items"."product_id"
WHERE "products"."id" IN (1, 2, 3)
GROUP BY orders.id
HAVING (only line items with these products ids ?)

有什么想法吗?

例如:

Order with products 1, 2       => OK
Order with products 1, 3       => OK
Order with products 1, 2, 3    => OK
Order with products 1, 4       => NOT OK
Order with products 1, 2, 3, 4 => NOT OK

3 个答案:

答案 0 :(得分:1)

我最终使用PostgreSQL中的array_agg并且<@包含函数:

SELECT orders.id FROM "orders"
INNER JOIN "line_items" ON "line_items"."order_id" = "orders"."id"
GROUP BY orders.id
HAVING array_agg(line_items.product_id) <@ ARRAY[1, 2, 3];

答案 1 :(得分:1)

虽然your presented solution有效(假设UNIQUE约束你保守秘密),但更大的表格会 痛苦地慢 。它不能使用索引,因此必须在Postgres应用过滤器之前聚合整个表line_items

改为使用:

SELECT o.*
FROM  (
   SELECT DISTINCT i.order_id
   FROM   line_items i
   WHERE  i.product_id IN (1,2,3)
   AND    NOT EXISTS (
      SELECT 1 FROM line_items
      WHERE  order_id = i.order_id
      AND    product_id NOT IN (1,2,3)
      )
   ) i
JOIN   orders o ON o.id = i.order_id;

这可以使用索引,并且通常会快几个数量级(差异随着表的大小而增长)。通常,product_idorder_id为整数列,这两个多列索引为perfect

CREATE INDEX foo1_idx ON line_items (product_id, order_id);
CREATE INDEX foo2_idx ON line_items (order_id, product_id);

其中一个可能已经是主键,那么你只需要添加另一个。假设orders.id也被编入索引。所有这一切都应该在你的问题中。

为什么这些指数?关于dba.SE的相关答案的详细解释:
Is a composite index also good for queries on the first field?

这是 relational division 的情况。我们在这个问题下汇集了一系列技术:
How to filter SQL results in a has-many-through relation

这里的特殊困难是允许各种组合。结果更容易通过 not 允许来定义。

除此之外:不要不必要地双重引用合法的小写identifiers。使代码嘈杂且难以阅读。

答案 2 :(得分:0)

添加如下WHERE子句:

SELECT COUNT(*) FROM "orders"
INNER JOIN "line_items" ON "line_items"."order_id" = "orders"."id"
INNER JOIN "products" ON "products"."id" = "line_items"."product_id"
WHERE "products"."id" IN (1, 2, 3)
      AND "line_items"."product_id" IN (1,2,3)
GROUP BY orders.id