简体   繁体   中英

Selecting users from orders where all orders has the state “errored”

Sorry if the question is confusing, because I'm not sure on how to ask the question, but here I go:

I'm inside the rails console trying to find all users with orders where all orders has the state "errored", and ONLY errored. A user has many orders, and the order state on each of them can differ from "completed", "refunded" or "errored".

User.includes(:orders).where(orders: { state: "errored" })

This is returning all users who has one or more errored orders as it's suppose to, whether or not the user have orders with other states as well. But I'm trying to fetch the users who ONLY has errored orders.

I've tried a lot of things, from iterating through every order in every user, to trying to manually find them. But it got to be a better way.

My SQL isn't what it used to be but I believe that for a pure SQL solution it would need to look something like:

SELECT  "users".*
  FROM "users"
  LEFT JOIN orders on orders.user_id = users.id
  LEFT JOIN orders non_errored_orders on non_errored_orders.user_id = users.id and non_errored_orders.state <> 'errored'
  WHERE "non_errored_orders"."id" IS NULL AND "orders"."id" IS NOT NULL

So, we left join the orders table with the alias non_errored_orders and that will make it so that if there is an order where the user id matches and the state is not equal to errored , a row will appear (and non_errored_orders.id will end up as NOT NULL ). In the where clause we then filter down to only users whose non_errored_orders.id IS NULL , filtering out all users who matched an order that was not errored .

We then left join the orders table again with no alias only matching on users.id = orders.user_id . If there is no orders.id , that means the user does not have any orders in the table at all, so we want to filter down to only users where orders.user_id IS NOT NULL , meaning they had an order.

You can do a query like this in rails by doing something like:

User.
  joins(:orders).
  joins("LEFT JOIN orders non_errored_orders on non_errored_orders.user_id = users.id and non_errored_orders.state <> 'errored'").
  where(non_errored_orders: { id: nil }).
  where.not(orders: { id: nil }).distinct
#  User Load (0.3ms)  SELECT  DISTINCT "users".* FROM "users" INNER JOIN "orders" ON "orders"."user_id" = "users"."id" LEFT JOIN orders non_errored_orders on non_errored_orders.user_id = users.id and non_errored_orders.state <> 'errored' WHERE "non_errored_orders"."id" IS NULL AND ("orders"."id" IS NOT NULL) LIMIT ?  [["LIMIT", 11]]
# => #<ActiveRecord::Relation [#<User id: 1, ...>]>

In my very limited test set this seems to be working.

test data was

User.find(1).orders.create([ { state: 'errored' }, { state: 'errored' } ])
User.find(2).orders.create([ { state: 'errored' }, { state: 'completed' }])
User.find(3).orders.create([ { state: 'refunded' } ])

How about little long but simplified path?

users_with_other_order_states = User.join(:orders).where.not(orders: { state: "errored" }).pluck(:id)

User.joins(:orders).where('orders.state = "errored" AND users.id NOT IN (?)', users_with_other_order_states)

I think you can try define an errored_orders relation:

class User < ApplicationRecord
  has_many errored_orders, ->{where state: 'errored'}
end

User.includes(:errored_orders).where(...)

Hope it helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM