どのhas_many項目にも非nilフィールドがないRails finderメソッドを作成するにはどうすればよいですか?


8

Rails 5を使用しています。次のモデルがあります...

class Order < ApplicationRecord
    ...
    has_many :line_items, :dependent => :destroy

LineItemモデルには「discount_applied」という属性があります。「discount_applied」フィールドがnilでないラインアイテムのインスタンスがゼロであるすべての注文を返したいのですが。そのようなファインダーメソッドをどのように書くのですか?


どのRDBMSを使用していますか?「生の」SQLを使用することはオプションですか?
セバスチャンパルマ

質問は少し混乱しています。したがって、関連付けられているLineOrdersにnilのdiscount_appliedが設定されているすべての注文が本質的に必要ですか?
bwalshy

@ bwalshy、discount_appliedフィールドがnilでない場合、ラインアイテムのないすべての注文が必要です。これには、ラインアイテムのないオーダー、discount_appliedがnilである単一のラインアイテムのオーダー、discount_appliedフィールドが両方ともnilである2つのラインアイテムのオーダー、または3つのラインアイテムのあるオーダーが含まれます...
デイブ

回答:


0

効率的ではありませんが、あなたの問題を解決するかもしれないと思いました:

orders = Order.includes(:line_items).select do |order|
  order.line_items.all? { |line_item| line_item.discount_applied.nil? }
end

更新

すべてのラインアイテムに割引がない注文を見つける代わりに、割引が適用されたラインアイテムを持つすべての注文を出力結果から除外できます。これはwhere句内のサブクエリで実行できます。

# Find all ids of orders which have line items with a discount applied:
excluded_ids = LineItem.select(:order_id)
                       .where.not(discount_applied: nil)
                       .distinct.map(&:order_id)

# exclude those ids from all orders:
Order.where.not(id: excluded_ids)

これらを1つの検索メソッドで組み合わせることができます。

Order.where.not(id: LineItem
                    .select(:order_id)
                    .where.not(discount_applied: nil))

お役に立てれば


この@Mosaalebに感謝します-私がテストしたところ、ロジックは機能しているようです。あなたが行った更新に関して、これら2つのことを単一のfinderメソッドに組み合わせる方法はありますか?
デイブ

@Daveさん、こんにちは。はい、あなたがそれらを組み合わせることができることを確認してください、私は私の答えを更新しました。
Mosaaleb

2

まず、これは本当に純粋なArelアプローチを使用するかどうか、またはSQLの使用が問題ないかどうかに依存します。前者は、ライブラリを作成する場合にのみ推奨されるIMOですが、実際に途中でDBMSを変更する可能性が非常に低いアプリを作成する場合は不要です(その場合、一部の手動のクエリはおそらくあなたのトラブルの中で最も少ないでしょう。

SQLの使用が問題ない場合、ほとんどすべてのデータベースで機能する最も簡単なソリューションは次のとおりです。

Order.where("(SELECT COUNT(*) FROM line_items WHERE line_items.order_id = orders.id AND line_items.discount_applied IS NULL) = 0")

これはほとんどどこでも機能するはずです(そして、Arelが少し増えて手動のSQLが減ります):

Order.left_joins(:line_items).where(line_items: { discount_applied: nil }).group("orders.id").having("COUNT(line_items.id) = 0")

特定のDBMS(具体的には、それぞれのクエリオプティマイザー)に応じて、どちらか一方の方がパフォーマンスが向上する場合があります。

お役に立てば幸いです。


PostGresを使用していますが、このRDBMSを中立にする方法はありますか?
デイブ

明確にするために以前の答えを言い換えましょう。私の例はどちらも、SQLに準拠しているすべてのDBMSで機能するはずです。 DBMS。Postgresの場合、私の経験では、クエリオプティマイザはどちらの方法でも機能するのに十分スマートであるため、問題にはなりません。
クレメンスコフラー

@ClemensKofler-ええ、あなたが持っているSQLは私には標準に見え、パフォーマンスはこの段階では問題ではありません。上記のソリューションで1つの質問ですが、LineItemsのすべてのdiscount_apppliedフィールドをnilにする必要があるという制約をどこで考慮しますか?
デイブ

両方のフレーバーに条件を追加しました。私はこれをテストしておらず、それがどのように機能するかの知識からタイプダウンされていることに注意してください
Clemens Kofler

0

可能なコード

Order.includes(:line_items).where.not(line_items: {discount_applied: nil})

クエリメソッドのARドキュメントに慣れることをお勧めします。

更新

これは最初よりも興味があるようです。さらに複雑なので、動作するコードを提供することはできません。しかし、私はを使用してソリューションを調べますLineItem.group(order_id).having(discount_applied: nil)。これにより、line_itemsのコレクションが得られ、それをサブクエリとして使用して、関連する注文を見つけることができます。


おかげで、これは機能していません。Orderには、discount_apppliedフィールドがnilとnon-nilのいくつかのラインアイテムがある結果が返されます。理想的には、shoudlが返される唯一の注文には、discount_apppliedがnilのすべてのラインアイテムがあるはずです。
デイブ

すみません、あなたの質問を誤解しました。ゼロインスタンスについてのその部分は私には明確ではありませんでした。
adass

心配する必要はありません。試してみて、これについてさらに考えるための更新を含めてくれてありがとう。
デイブ

0

discount_appliedがnilであるすべてのレコードが必要な場合:

Order.includes(:line_items).where.not(line_items: {discount_applied: nil})

(n + 1の問題を回避するためにincludeを使用)または

Order.joins(:line_items).where.not(line_items: {discount_applied: nil})

こんにちは、これは@adassによって上記で送信された回答でもありましたが、discount_appliedがnil以外のラインアイテムが少なくとも1つあるOrderを返すので失敗します。 (または、LineItemがまったくないOrders)。
デイブ

0

ここにあなたの問題の解決策があります

order_ids = Order.joins(:line_items).where.not(line_items: {discount_applied: nil}).pluck(:id)
orders = Order.where.not(id: order_ids)

最初のクエリは、のIDを返します。Orders少なくとも一方がでline_itemましたdiscount_applied。2番目のクエリはordersline_itemを持つのインスタンスが0のすべての場所を返しますdiscount_applied


おかげで、このソリューションは上記のMosaalebが提供するソリューションとどのように違うのですか?
デイブ

私の悪い、私はその答えを逃しました、モサレブの答えは良いものです。
Naveed

0

NOT EXISTS少なくともMySQLとPostgreSQLの両方で利用できるSQL の機能を使用します

このようになります

class Order
  has_many :line_items
  scope :without_discounts, -> {
    where("NOT EXISTS (?)", line_items.where("discount_applied is not null")
  }
end

0

私が正しく理解していれば、割引が適用されていないラインアイテム(ある場合)がないすべての注文を取得したいと思います。

を使用してこれらの注文を取得する1つの方法ActiveRecordは、次のようになります。

Order.distinct.left_outer_joins(:line_items).where(line_items: { discount_applied: nil })

これがどのように機能するかについて簡単に説明します:

  • ソリューションではを使用left_outer_joinsしていますが、各注文のラインアイテムにアクセスしないことを前提としています。left_joinsエイリアスであるを使用することもできます。
  • Orderインスタンスのラインアイテムをインスタンス化する必要がある場合は.eager_load(:line_items)、チェーンに追加します。これにより、すべての注文(N + 1)に対して追加のクエリを実行すること、つまりorder.line_items.eachビューで実行することが防止されます。
  • 使用distinctは、注文が結果に1回だけ含まれるようにするために不可欠です。

更新

私の以前の解決策はdiscount_applied IS NULL、すべてではなく、少なくとも1つのラインアイテムのみをチェックすることでした。次のクエリは、必要な注文を返します。

Order.left_joins(:line_items).group(:id).having("COUNT(line_items.discount_applied) = ?", 0)

これは起こっていることです:

  • ソリューションではorders LEFT OUTER JOIN line_items、関連するアイテムのない注文が含まれるように、左外部結合()を使用する必要があります。
  • アイテムの数にOrder関係なく、ラインアイテムをグループ化して単一のオブジェクトを取得します(GROUP BY recipes.id)。
  • 注文ごとに割引が適用されたラインアイテムの数をカウントし、割引が適用されていないアイテムのみを選択します(HAVING (COUNT(line_items.discount_applied) = 0))。

お役に立てば幸いです。


「ステップ」とは何ですか?上記は、「テーブル "steps"のFROM句エントリがありません」というエラーを示します
Dave

申し訳ありませんが、@ Daveはタイプミスでした。現在は修正されています。
セバスチャンソガモソ

Np @SebastianSogamoso、しかしこれはうまく機能していないようです。これ、 'Order.distinct.left_outer_joins(:line_items).where(line_items:{discount_applied:nil})。select {| o | o.line_items.any?{| li | li.discount_applied!= nil}}。count 'は、ゼロより大きい数値を返します。見つかったすべての注文に、discount_appliedがnilのラインアイテムしかなかった場合、ステートメントはゼロを返すはずです。
デイブ

@デイブそうです。最初はそれを逃したので、答えを更新しました。
セバスチャンソガモソ

-1

従来のrails left_joinでは効率的にこれを行うことはできませんが、SQL左結合はこれらのケースを処理するために構築されました

Order.joins("LEFT JOIN line_items AS li ON li.order_id = orders.id 
                                       AND li.discount_applied IS NOT NULL")
     .where("li.id IS NULL")

単純な内部結合は、すべての注文を返し、すべてのline_itemsで結合されます
が、この注文にline_itemsがない場合、その注文は無視されます(false whereなど)
。左結合では、line_itemsが見つからなかった場合、SQLはそれをそれを保持するために空のエントリ

したがって、不要なline_itemsに参加したままにし、空のline_itemsに参加したすべての注文を見つけます

そして、where(id: pluck(:id))またはhaving("COUNT(*) = 0")ですべてのコードを避けてください、これはあなたのデータベースを殺します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.