1

私は3つのテーブル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" 

は、私がmediumallではなくlightを取得したい配列["Bodyboarding", "Surfing"]を考える:

は、私はこれら三つのオファーを持っているとしましょう。

私はthis answerの線に沿って何かを試してみましたが、私は結果にゼロ行を取得:

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

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の答えはいいだろうが、私'LL Postgresと互換性があるSQLだけを解決してください。

データ:offer.idによって、ないsports.name(又はsports.id)によって

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

SQLは機能しますか?それは正しいように見えます。 –

+0

0行を返します。 @GordonLinoff – max

+0

あなたはすでにそこにいます:「オファー」フィールドでグループ化してください(明示的なフィールド名で*を置き換え、グループ内のフィールド名を繰り返す)。あなたは今、sports.nameでグループ分けしています。もちろん、sports.nameごとのsports.nameの個数は常に1です。 –

答えて

2

グループ:

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は一意に定義されます。
  • (sport_id, offer_id)offers_sportsは、ユニーク(または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 
+0

ブリリアント。広告された通りに正確に動作します! – max

+0

良いキャッチ。 。 。 –

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の列は表示用ですが、安全に取り外すことができます。

これには非常に注意する必要がある2つのことがあります。でも、Postgresの9.4と

  1. ように、それは同等の値に変換する方法を見つけることができないあなたに言って、配列を比較するための変換を入力し、多くの場合、ずさんなとエラーを出している(私はまだ9.5試していません)この例では、::text[]を使用して手動で両側をキャストしています。

  2. 配列パラメータのサポートレベルがRubyでもRoRフレームワークなのか分かりませんので、手動で文字列をエスケープして(ユーザが入力した場合)最終的にARRAY[]構文。

+0

それはうまくいきますが、悪い答えではありません。しかし、アーウィンは統合がはるかに容易でした。基本的には、having句を文字列として生成し、それをエスケープするために値をバインドする必要があります。それほど難しいことではありませんが、これを直ちに読み込むことは難しいです。ありがとう! – max

関連する問題