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.