连接集必须包含所有值但可能包含更多值的SQL
我有三个表格, sports
和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"
鉴于arrays["Bodyboarding", "Surfing"]
我想要得到medium
而不是light
。
我尝试过这个答案,但结果是零行:
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
按offer.id
,而不是sports.name
(或sports.name
):
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
和offer.id
被定义为主键。 -
sports.name
被定义为唯一的。 -
(sport_id, offer_id)
被定义为唯一(或PK)。
您在计数中不需要DISTINCT
。 而count(*)
甚至更便宜了。
相关答案与可能的技术武器:
- 如何以多次通过关系过滤SQL结果
由@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
一种方法是使用数组和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
列仅用于show,但您可以安全地删除它。
有两件事你必须非常注意这一点。
-
即使使用Postgres 9.4(我还没有尝试9.5),比较数组的类型转换是草率的,经常出错,告诉你它无法找到将它们转换为可比值的方法,因此你可以在示例中看到我使用
::text[]
手动施放两边。 -
我不知道对数组参数的支持级别是Ruby,还是RoR框架,所以你可能最终必须手动转义字符串(如果用户输入)并使用
ARRAY[]
语法形成数组。
- Active_support / dependencies.rb:247`require’:无法加载此类文件 – 2.1 / pg_ext(LoadError)
- 为PostgreSQL 8.3安装PL / Ruby
- Rails 4 postgres bug – 无法创建数据库,因为pg gem“缺失”,但事实并非如此
- 如何从SQLite迁移到PostgreSQL(Rails)
- Rails postgresql数据库连接失败
- Ruby on Rails / PostgreSQL – 启动server-libq.5.dylib时出现Library not Loaded错误
- 如何创建rails迁移以删除/更改精度并按小数缩放?
- 为什么此ActiveRecord语句在Server中失败但在Console中失败
- 在数据库中上传图像