ANDではなくORでスコープクエリをチェーンする方法は?


137

Rails3、ActiveRecordを使用しています

ANDではなくORステートメントを使用してスコープをチェーンするにはどうすればよいのでしょうか。

例えば

Person.where(:name => "John").where(:lastname => "Smith")

それは通常戻ります:

name = 'John' AND lastname = 'Smith'

しかし、私は欲しい:

`name = 'John' OR lastname = 'Smith'

役立つかもしれません:ActiveRecord ORクエリハッシュ表記
ポタシン

回答:


107

あなたはするだろう

Person.where('name=? OR lastname=?', 'John', 'Smith')

現在、新しいAR3構文による他のORサポートはありません(つまり、サードパーティのgemを使用していません)。


21
安全のために、これをPerson.where( "name =?OR lastname =?"、 'John'、 'Smith')として表現することは価値があります
CambridgeMike

6
これはまだRails 4に適用できますか?Rails 4でこれ以上良いことはありませんか?
Donato 2015

11
Rails 4には変更はありませんが、Rails 5にはが.or組み込まれます。
ライム

3
これはスコープではなく、2つのwhere句、または非常に具体的なスコープのケースです。たとえば結合など、より複雑なスコープをチェーンしたい場合はどうすればよいでしょうか。
miguelfg 2015年

2
Rails 5では、Person.where( "name = 'John'")。or(Person.where( "lastname = 'Smith'"))
shyam


48

ARelを使用する

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)

1
このタイプのクエリがPostgresで失敗するかどうかについて誰かがコメントできますか?
Olivier Lacan、2011年

ARelはDBに依存しないと想定されています。
Simon Perepelitsa

これは良い考えのように見えますが、ARelはパブリックAPIではないため、これを使用するとアプリが予期せず壊れる可能性があります。そのため、ARel APIの進化に細心の注意を払わない限り、お勧めしません。
Olivier Lacan 2014

7
@OlivierLacan ArelはパブリックAPIです。より正確には、それを公開しています。Active Recordは、内部でそれを使用する便利なメソッドを提供するだけです。それ自体でそれを使用することは完全に有効です。これはセマンティックバージョニングに準拠しているため、メジャーバージョン(3.xx => 4.xx)を変更しない限り、変更を壊すことを心配する必要はありません。
グレイ

41

覗き見に役立つように、同じ列またはクエリの配列構文をポストするだけです。

Person.where(name: ["John", "Steve"])

2
名前とスミスに対する質問チェックジョン
steven_noble

11
@steven_noble-orクエリで「同じ列」を太字にしたのはそのためです。コメントは完全に削除できますが、「または」クエリのSO質問を閲覧する人に役立つと思いました。
daino3 2015

38

Rails4の更新

サードパーティの宝石は必要ありません

a = Person.where(name: "John") # or any scope 
b = Person.where(lastname: "Smith") # or any scope 
Person.where([a, b].map{|s| s.arel.constraints.reduce(:and) }.reduce(:or))\
  .tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }

古い答え

サードパーティの宝石は必要ありません

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)

5
これは、リソースと方法の点でOPの質問に対する唯一の純粋な答えです。他の回答には、(a)サードパーティのAPIが必要、または(b)orテキストブロック内の補間またはその他の演算子が必要で、どちらもOPのコードには反映されていません。
allenwlee 2015


4
...where_values.join(' OR ')私の場合に使用
サッシュ

2
.tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }スコープにバインドが必要な変数がない場合は、この部分を省略できます。
ファトホク


22

これに対する更新された回答を誰かが探している場合、これをRailsに取り込むための既存のプルリクエストがあるようです:https : //github.com/rails/rails/pull/9052

ActiveRecordの@ j-mcnallyのモンキーパッチ(https://gist.github.com/j-mcnally/250eaaceef234dd8971b)のおかげで、次のことができます。

Person.where(name: 'John').or.where(last_name: 'Smith').all

さらに価値があるのは、スコープをOR次のようにチェーンする機能です。

scope :first_or_last_name, ->(name) { where(name: name.split(' ').first).or.where(last_name: name.split(' ').last) }
scope :parent_last_name, ->(name) { includes(:parents).where(last_name: name) }

次に、姓または名を持つ、または姓を持つ親を持つすべての人物を検索できます

Person.first_or_last_name('John Smith').or.parent_last_name('Smith')

これを使用するための最良の例ではありませんが、それを質問に合わせようとしています。


2
これにはどのバージョンまたはRailsが必要ですか?
サッシュ

3
これをRailsコアにする最新のディスカッションとプルリクエストは、次の場所にあります。github.com / rails / rails / pull / 16052 Rails 5.0は、.orチェーン機能の公式リリースを対象としています。Rails> = 3.2で言及したサルのパッチを使用することができました。ただし、Rails 5がコードに長期にわたる変更を導入するのを待つこともできます。
codenamev

1
うん、それ Rails 5とマージされてリリースされました
amoebe

10

私(Rails 4.2.5)では、次のようにしか機能しません。

{ where("name = ? or name = ?", a, b) }

8

Rails 3.0以降を使用している場合、これはMetaWhereの良い候補になりますが、Rails 3.1では機能しません。代わりにsqueelを試してみてください。同じ作者によるものです。ORベースのチェーンを実行する方法は次のとおりです。

Person.where{(name == "John") | (lastname == "Smith")}

あなたは他の多くの間で、ミックスして一致するAND / ORできる素晴らしいもの


8

Rails / ActiveRecordの更新バージョンは、この構文をネイティブでサポートする場合があります。次のようになります。

Foo.where(foo: 'bar').or.where(bar: 'bar')

このプルリクエストに記載されているようにhttps://github.com/rails/rails/pull/9052

とりあえず、次のことを続けるだけでうまくいきます。

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')

5
Rails 4.2.5ではサポートされていません
fatuhoku 2016

1
Foo.where(foo: 'bar1').or.where(bar: 'bar2')動作しません。Foo.where(foo: 'bar1')。or.where(Foo.where(bar: 'bar2'))である必要があります
AnaMaríaMartínezGómez

6

Rails 4 +スコープ+ Arel

class Creature < ActiveRecord::Base
    scope :is_good_pet, -> {
        where(
            arel_table[:is_cat].eq(true)
            .or(arel_table[:is_dog].eq(true))
            .or(arel_table[:eats_children].eq(false))
        )
    }
end

名前付きスコープを.orで連鎖させてみましたが、これはこれらのブール値セットで何かを見つけるのに役立ちました。のようなSQLを生成します

SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))

4

Rails 4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }

4

(データセット全体を明示的に操作するのではなく)スコープを提供する場合は、Rails 5で次のことを行う必要があります。

scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }

または:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end

これは正しいですが、stackoverflow.com / a / 42894753/1032882が技術的に行う同じことよりも文字どおりの答えです。
RudyOnRails

3

where句を手動で記述して「or」ステートメントを含めることができない場合(つまり、2つのスコープを結合したい場合)、ユニオンを使用できます。

Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")

(ソース:ActiveRecord Query Union

これは、いずれかのクエリに一致するすべてのレコードを返します。ただし、これは配列ではなく配列を返します。本当にアールを返したい場合は、この要点をチェックアウトしてくださいhttps ://gist.github.com/j-mcnally/250eaaceef234dd8971b

モンキーパッチレールを気にしない限り、これでうまくいきます。


2

これらの関連する質問もご覧ください:ここここここ

Rails 4の場合、この記事この元の回答に基づく

Person
  .unscoped # See the caution note below. Maybe you want default scope here, in which case just remove this line.
  .where( # Begin a where clause
    where(:name => "John").where(:lastname => "Smith")  # join the scopes to be OR'd
    .where_values  # get an array of arel where clause conditions based on the chain thus far
    .inject(:or)  # inject the OR operator into the arels 
    # ^^ Inject may not work in Rails3. But this should work instead:
    .joins(" OR ")
    # ^^ Remember to only use .inject or .joins, not both
  )  # Resurface the arels inside the overarching query

なお、最後に記事の注意を:

Rails 4.1以降

Rails 4.1はdefault_scopeを通常のスコープと同様に扱います。デフォルトのスコープ(ある場合)はwhere_valuesの結果に含まれ、inject(:or)はデフォルトのスコープとwheresの間にステートメントを追加または追加します。それは良くないね。

これを解決するには、クエリのスコープを解除するだけです。


1

これは非常に便利な方法であり、Rails 5では問題なく機能します。

Transaction
  .where(transaction_type: ["Create", "Correspond"])
  .or(
    Transaction.where(
      transaction_type: "Status",
      field: "Status",
      newvalue: ["resolved", "deleted"]
    )
  )
  .or(
    Transaction.where(transaction_type: "Set", field: "Queue")
  )

0

squeel宝石は、これを実現するために、信じられないほど簡単な方法(この前、私にはcoloradoblueのメソッド@のようなものを使用)提供しています。

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}

0

したがって、元の質問への答えは、スコープを「または」ではなく「または」で結合できますか、そして「できません」のようです。しかし、完全に異なるスコープまたはクエリを実行するクエリを手作業でコーディングするか、ActiveRecordとは異なるフレームワーク、たとえばMetaWhereやSqueelを使用できます。私の場合は役に立たない

私はpg_searchによって生成されたスコープを「or」しています。これは、selectよりも少し多く、ASCによる順序が含まれているため、クリーンなユニオンを混乱させます。pg_searchでは実行できないことを行う手作りのスコープを使用して「または」したいと思います。だから私はこのようにそれをしなければならなかった。

Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")

つまり、スコープをsqlに変換し、それぞれを角かっこで囲み、それらを結合して、生成されたsqlを使用してfind_by_sqlにします。少しゴミですが、機能します。

いいえ、pg_searchで "against:[:name、:code]"を使用できるとは言わないでください。そのようにしたいのですが、 'name'フィールドはpg_searchが処理できないhstoreですまだ。そのため、名前によるスコープは手動で作成し、pg_searchスコープと結合する必要があります。


-2
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.