【问题标题】:SQL where joined set must contain all values but may contain moreSQL where join set 必须包含所有值,但可能包含更多
【发布时间】:2016-11-30 03:43:06
【问题描述】:

我有三个表offerssports 和连接表offers_sports

class Offer < ActiveRecord::Base
  has_and_belongs_to_many :sports
end

class Sport < ActiveRecord::Base
  has_and_belongs_to_many :offers
end

我想选择包含给定运动名称数组的报价。它们必须包含所有sports,但可能有更多。

假设我有这三个报价:

light:
  - "Yoga"
  - "Bodyboarding"
medium:
  - "Yoga"
  - "Bodyboarding"
  - "Surfing"
all:
  - "Yoga"
  - "Bodyboarding"
  - "Surfing"
  - "Parasailing"
  - "Skydiving"

给定数组["Bodyboarding", "Surfing"],我想得到mediumall,但不是light

我尝试了类似this answer 的方法,但结果中的行数为零:

Offer.joins(:sports)
     .where(sports: { name: ["Bodyboarding", "Surfing"] })
     .group("sports.name")
     .having("COUNT(distinct sports.name) = 2")

翻译成SQL:

SELECT "offers".* 
FROM "offers" 
INNER JOIN "offers_sports" ON "offers_sports"."offer_id" = "offers"."id"     
INNER JOIN "sports" ON "sports"."id" = "offers_sports"."sport_id" 
  WHERE "sports"."name" IN ('Bodyboarding', 'Surfing') 
GROUP BY sports.name 
HAVING COUNT(distinct sports.name) = 2;

一个 ActiveRecord 答案会很好,但我会满足于 SQL,最好是兼容 Postgres。

数据:

offers
======================
id | name
----------------------
1  | light
2  | medium
3  | all
4  | extreme

sports
======================
id | name
----------------------
1  | "Yoga"
2  | "Bodyboarding"
3  | "Surfing"
4  | "Parasailing"
5  | "Skydiving"

offers_sports
======================
offer_id | sport_id
----------------------
1        | 1
1        | 2
2        | 1
2        | 2
2        | 3
3        | 1
3        | 2
3        | 3
3        | 4
3        | 5
4        | 3
4        | 4
4        | 5

【问题讨论】:

  • SQL 是否有效?看起来是正确的。
  • 返回 0 行。 @GordonLinoff
  • 你快到了:按“优惠”字段分组(用明确​​的字段名称替换 * 并重复分组依据)。您现在按 sports.name 分组,自然,每个 sports.name 的不同数量的 sports.name 始终为 1。
  • @HenkKok 仍然给出 0 行:SELECT offers.name, offers.id, sports.id FROM "offers" INNER JOIN "offers_sports" ON "offers_sports"."offer_id" = "offers"."id" INNER JOIN "sports" ON "sports"."id" = "offers_sports"."sport_id" WHERE "sports"."name" IN ('Bodyboarding', 'Surfing') GROUP BY offers.name, offers.id, sports.name HAVING COUNT(distinct sports.name) = 2
  • 那是因为你还在group by中加入了sports.name,那一栏必须从group by中去;此外,sports.id 必须从 SELECT 子句中删除。如果您在最终结果中需要优惠和运动,请首先选择优惠(与所有 2 项运动相关联),然后将该选择用作内联视图以加入运动。

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


【解决方案1】:

offer.id 分组,而不是按sports.name(或sports.id):

SELECT o.*
FROM   sports        s
JOIN   offers_sports os ON os.sport_id = s.id
JOIN   offers        o  ON os.offer_id = o.id
WHERE  s.name IN ('Bodyboarding', 'Surfing') 
GROUP  BY o.id  -- !!
HAVING count(*) = 2;

假设典型实现:

  • offer.idsports.id 被定义为主键。
  • sports.name 被定义为唯一的。
  • offers_sports 中的 (sport_id, offer_id) 被定义为唯一(或 PK)。

计数中不需要DISTINCT。而count(*) 甚至更便宜一些。

与可能技术库的相关答案:


由@max(OP)添加 - 这是上述查询进入 ActiveRecord:

class Offer < ActiveRecord::Base
  has_and_belongs_to_many :sports
  def self.includes_sports(*sport_names)
    joins(:sports)
      .where(sports: { name: sport_names })
      .group('offers.id')
      .having("count(*) = ?", sport_names.size)
  end
end

【讨论】:

  • 太棒了。完全按照宣传的方式工作!
【解决方案2】:

一种方法是使用数组和array_agg 聚合函数。

SELECT "offers".*, array_agg("sports"."name") as spnames 
FROM "offers" 
INNER JOIN "offers_sports" ON "offers_sports"."offer_id" = "offers"."id"     
INNER JOIN "sports" ON "sports"."id" = "offers_sports"."sport_id" 
GROUP BY "offers"."id" HAVING array_agg("sports"."name")::text[] @> ARRAY['Bodyboarding','Surfing']::text[];

返回:

 id |  name  |                      spnames                      
----+--------+---------------------------------------------------
  2 | medium | {Yoga,Bodyboarding,Surfing}
  3 | all    | {Yoga,Bodyboarding,Surfing,Parasailing,Skydiving}
(2 rows)

@&gt; 运算符意味着左边的数组必须包含右边的所有元素,但可能包含更多。 spnames 列仅供展示,但您可以安全地删除它。

有两点你必须非常注意这一点。

    1234563可以在示例中看到我使用::text[] 手动投射两边。
  1. 我不知道 Ruby 和 RoR 框架对数组参数的支持程度如何,因此您最终可能不得不手动转义字符串(如果由用户输入)并使用ARRAY[] 语法。

【讨论】:

  • 它确实有效,而且答案还不错 - 但是 Erwin 的集成要容易得多。您基本上需要将 having 子句生成为字符串并绑定值以对其进行转义。不是很难,但要让关联正确加载到这个上仍然很棘手。谢谢!
猜你喜欢
  • 1970-01-01
  • 2015-05-15
  • 2018-05-22
  • 2017-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多