Làm cách nào để tôi viết phương thức tìm Rails trong đó không có mục has_many nào có trường không phải là số không?


8

Tôi đang sử dụng Rails 5. Tôi có mô hình sau ...

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

Mô hình LineItem có một thuộc tính, "discount_applied." Tôi muốn trả lại tất cả các đơn hàng trong trường hợp không có trường hợp nào của một mục hàng có trường "discount_applied" không phải là không. Làm thế nào để tôi viết một phương pháp tìm như vậy?


Bạn đang sử dụng RDBMS nào? Có phải nó sử dụng SQL "thô" là một tùy chọn không?
Sebastian Palma

Câu hỏi hơi khó hiểu. Vì vậy, về cơ bản, bạn muốn tất cả các Đơn đặt hàng mà LineOrder được liên kết có giảm giá không?
bwalshy

@bwalshy, tôi muốn tất cả các đơn hàng không có chi tiết đơn hàng trong đó trường discount_applied không phải là không. Điều này sẽ bao gồm các đơn hàng không có chi tiết đơn hàng, đơn hàng với một chi tiết đơn hàng trong đó discount_applied là không hoặc đơn hàng có hai chi tiết đơn hàng trong đó cả hai trường discount_applied đều không hoặc đơn hàng có ba mục hàng ... Tôi nghĩ bạn hiểu ý tưởng đó.
Dave

Câu trả lời:


0

Không hiệu quả nhưng tôi nghĩ nó có thể giải quyết vấn đề của bạn:

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

Cập nhật :

Thay vì tìm các đơn hàng mà tất cả các chi tiết đơn hàng không giảm giá, chúng tôi có thể loại trừ tất cả các đơn hàng có chi tiết đơn hàng có chiết khấu được áp dụng từ kết quả đầu ra. Điều này có thể được thực hiện với truy vấn con bên trong mệnh đề:

# 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)

Bạn có thể kết hợp chúng trong một phương pháp tìm duy nhất:

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

Hi vọng điêu nay co ich


Cảm ơn vì điều này @Mosaaleb - logic dường như hoạt động khi tôi kiểm tra nó. Liên quan đến bản cập nhật bạn đã thực hiện, có cách nào để kết hợp hai điều đó vào một phương thức tìm kiếm duy nhất không?
Dave

Chào Dave. Có chắc chắn bạn có thể kết hợp chúng, tôi đã cập nhật câu trả lời của tôi.
Mosaaleb

2

Trước hết, điều này thực sự phụ thuộc vào việc bạn có muốn sử dụng cách tiếp cận Arel thuần túy hay không nếu sử dụng SQL là tốt. Trước đây, IMO chỉ được khuyến khích nếu bạn có ý định xây dựng thư viện nhưng không cần thiết nếu bạn đang xây dựng một ứng dụng trong thực tế, rất khó có khả năng bạn thay đổi DBMS của mình trên đường đi (và nếu bạn làm vậy, thay đổi một số ít truy vấn thủ công có thể sẽ là ít rắc rối nhất của bạn).

Giả sử sử dụng SQL là tốt, giải pháp đơn giản nhất có thể hoạt động trên hầu hết các cơ sở dữ liệu là:

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

Điều này cũng sẽ hoạt động khá nhiều ở mọi nơi (và có thêm một chút Arel và ít SQL thủ công hơn):

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

Tùy thuộc vào DBMS cụ thể của bạn (cụ thể hơn: trình tối ưu hóa truy vấn tương ứng của nó), cái này hay cái kia có thể hiệu quả hơn.

Mong rằng sẽ giúp.


Tôi đang sử dụng PostGres nhưng có cách nào để biến RDBMS này thành trung tính không?
Dave

Để tôi nói lại câu trả lời trước cho rõ ràng: Cả hai ví dụ của tôi nên hoạt động trong tất cả các DBMS tuân thủ SQL - nhưng chúng có thể không chạy theo cách tối ưu nhất vì đôi khi Arel thực hiện các phép thuật trong trình duyệt để sắp xếp lại các truy vấn cho từng cá nhân DBMS. Đối với Postgres, theo kinh nghiệm của tôi thì không vấn đề gì vì trình tối ưu hóa truy vấn của nó đủ thông minh để thực hiện công việc của mình.
Clemens Kofler

Oh gotcha @ClemensKofler - vâng, SQL bạn có vẻ chuẩn đối với tôi và hiệu suất không thành vấn đề ở giai đoạn này. Một câu hỏi, trong các giải pháp bạn có ở trên, bạn tính đến ràng buộc nào mà tất cả các trường discount_applied từ LineItems phải không?
Dave

Tôi đã thêm các điều kiện cho cả hai hương vị. Lưu ý rằng tôi đã không kiểm tra điều này và nó chỉ được gõ từ kiến ​​thức về cách nó nên hoạt động.
Clemens Kofler

0

Một mã có thể

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

Tôi khuyên bạn nên làm quen với tài liệu AR cho Phương thức truy vấn.

Cập nhật

Điều này dường như được quan tâm nhiều hơn tôi ban đầu mặc dù. Và phức tạp hơn, vì vậy tôi sẽ không thể cung cấp cho bạn một mã làm việc. Nhưng tôi sẽ xem xét một giải pháp bằng cách sử dụng LineItem.group(order_id).having(discount_applied: nil), nó sẽ cung cấp cho bạn một bộ sưu tập line_items và sau đó sử dụng nó làm truy vấn phụ để tìm các đơn hàng liên quan.


Cảm ơn nhưng điều này không hiệu quả. Đó là kết quả trả về trong đó Đơn hàng sẽ có một số chi tiết đơn hàng có trường giảm giá và không có giá trị. Lý tưởng nhất là các đơn hàng duy nhất được trả lại sẽ có tất cả các chi tiết đơn hàng với discount_applied là không.
Dave

Xin lỗi, tôi đã hiểu nhầm câu hỏi của bạn. Phần đó về 0 trường hợp không rõ ràng với tôi.
adass

Đừng lo lắng, cảm ơn vì đã cho nó một shot và bao gồm cả bản cập nhật để tôi suy nghĩ thêm về điều này.
Dave

0

Nếu bạn muốn tất cả các bản ghi trong đó discount_applied là không thì:

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

(sử dụng bao gồm để tránh vấn đề n + 1) hoặc

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

Xin chào, Đây cũng là câu trả lời được gửi bởi @adass, tuy nhiên không thành công vì nó trả về các Đơn hàng có ít nhất một chi tiết đơn hàng có chiết khấu không phải là số không trong khi tôi chỉ tìm kiếm Đơn hàng với tất cả các LineItems có giảm giá không phải là không (hoặc Đơn hàng hoàn toàn không có LineItems).
Dave

0

Đây là giải pháp cho vấn đề của bạn

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

Truy vấn đầu tiên sẽ trả về id của Ordersít nhất một line_itemdiscount_applied. Truy vấn thứ hai sẽ trả về tất cả những ordersnơi không có trường hợp nào line_itemdiscount_applied.


Cảm ơn, nhưng giải pháp này khác với giải pháp mà Mosaaleb đưa ra ở trên như thế nào?
Dave

Thật tệ, tôi đã bỏ lỡ câu trả lời đó, câu trả lời của Mossaleb là một câu trả lời hay.
Naveed

0

Tôi sẽ sử dụng NOT EXISTStính năng từ SQL, ít nhất là có sẵn trong cả MySQL và PostgreSQL

Nó sẽ giống như thế này

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

0

Nếu tôi hiểu chính xác, bạn muốn nhận tất cả các đơn hàng mà không có mục hàng nào (nếu có) được giảm giá.

Một cách để có được những đơn đặt hàng sử dụng ActiveRecordsẽ là như sau:

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

Dưới đây là một lời giải thích ngắn gọn về cách thức hoạt động:

  • Giải pháp sử dụng left_outer_joins, giả sử bạn sẽ không truy cập vào chi tiết đơn hàng cho mỗi đơn hàng. Bạn cũng có thể sử dụng left_joins, đó là một bí danh.
  • Nếu bạn cần khởi tạo các chi tiết đơn hàng cho từng Orderphiên bản, hãy thêm .eager_load(:line_items)vào chuỗi sẽ ngăn thực hiện một truy vấn bổ sung cho mỗi đơn hàng (N + 1), nghĩa là thực hiện order.line_items.eachtrong chế độ xem.
  • Sử dụng distinctlà điều cần thiết để đảm bảo rằng các đơn đặt hàng chỉ được bao gồm một lần trong kết quả.

Cập nhật

Giải pháp trước đây của tôi chỉ kiểm tra rằng discount_applied IS NULLít nhất một chi tiết đơn hàng, không phải tất cả chúng. Các truy vấn sau đây sẽ trả về các đơn đặt hàng bạn cần.

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

Đây là những gì đang xảy ra:

  • Giải pháp vẫn cần sử dụng nối ngoài ( orders LEFT OUTER JOIN line_items) bên trái để các đơn hàng không có bất kỳ mục liên quan nào được đưa vào.
  • Nhóm các mục hàng để nhận một Orderđối tượng bất kể có bao nhiêu mục ( GROUP BY recipes.id).
  • Nó đếm số lượng chi tiết đơn hàng được giảm giá cho mỗi đơn hàng, chỉ chọn những mục có mục giảm giá được áp dụng ( HAVING (COUNT(line_items.discount_applied) = 0)).

Tôi hy vọng điều đó sẽ giúp.


"Các bước" được cho là gì? Ở trên đưa ra lỗi 'thiếu mục nhập TỪ-mệnh đề cho bảng "các bước"'
Dave

Tôi xin lỗi @Dave đó là một lỗi đánh máy. Bây giờ nó đã được sửa.
Sebastian Sogamoso

Np @SebastianSogamoso, nhưng điều này dường như không hoạt động. Cái này, 'Order.distinc.left_outer_joins (: line_items) .where (line_items: {discount_applied: nil}). Chọn {| o | o.line_items.any? {| li | li.discount_applied! = nil}}. Count 'trả về một số lớn hơn 0. Nếu tất cả các đơn đặt hàng được tìm thấy chỉ có các chi tiết đơn hàng với discount_applied là không, tôi tin rằng tuyên bố của tôi sẽ trả về 0.
Dave

@Dave đúng vậy. Tôi đã bỏ lỡ điều đó ban đầu vì vậy tôi đã cập nhật câu trả lời của mình.
Sebastian Sogamoso

-1

Bạn không thể thực hiện điều này một cách hiệu quả với một đường ray cổ điển left_joins, nhưng phép nối trái sql được xây dựng để xử lý các trường hợp bị mất

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")

Tham gia bên trong đơn giản sẽ trả về tất cả các đơn đặt hàng, được tham gia với tất cả line_items,
nhưng nếu không có line_items cho đơn hàng này, đơn hàng sẽ bị bỏ qua (như sai trong đó)
Với tham gia trái, nếu không tìm thấy line_items, sql sẽ tham gia vào một mục trống để giữ nó

Vì vậy, chúng tôi đã tham gia line_items mà chúng tôi không muốn và tìm thấy tất cả các đơn hàng được tham gia với một line_items trống

Và tránh tất cả các mã có where(id: pluck(:id))hoặc having("COUNT(*) = 0"), vào ngày này sẽ giết cơ sở dữ liệu của bạn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.