我有三个表offers
,sports
和联接表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"]
,我希望得到medium
和all
,而不是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
答案 0 :(得分:2)
按<div style="white-space: nowrap;">
Texttttttttttttttttttttttttttttttttttttttttttttttttttttttttt Float text
</div>
分组,而不是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.id
被定义为主键。sports.id
定义为唯一。sports.name
中的(sport_id, offer_id)
被定义为唯一(或PK)。您在计算中不需要offers_sports
。 DISTINCT
甚至更便宜了。
相关答案与可能的技术武器:
由@max(OP)添加 - 这是上面的查询转入ActiveRecord:
count(*)
答案 1 :(得分:1)
一种方法是使用数组和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)
@>
运算符意味着左侧的数组必须包含右侧数组中的所有元素,但可能包含更多元素。 spnames
列仅供展示,但您可以安全地将其删除。
有两件事你必须非常注意这一点。
即使使用Postgres 9.4(我还没有试过9.5),比较数组的类型转换很草率而且经常出错,告诉你它无法找到将它们转换为可比值的方法,正如您在示例中所见,我使用::text[]
手动投射双方。
我不知道对数组参数的支持级别是Ruby,还是RoR框架,所以你可能最终必须手动转义字符串(如果用户输入)并使用ARRAY[]
语法。