How to use joins with `where` condition - sql

I want to get users with their assets, which have type 'Google'.
I tried
User.joins(:assets).where(assets: { assetable_type: 'Google' })
or
User.joins(:assets).where("assets.assetable_type = 'Google'")
But that scopes work identically and return nothing.
SQL, which scopes generate:
SELECT "users".* FROM "users" INNER JOIN "assets" ON "assets"."assetable_id" = "users"."id" AND "assets"."assetable_type" = 'User' WHERE (assets.assetable_type = 'Google')
It doesn't seem to look right
What am I doing wrong?

You didn't gave any details about your models and associations
So, I can give you a temporary solution to use raw sql
sql = "SELECT users.* FROM users INNER JOIN assets ON assets.assetable_id = users.id WHERE assets.assetable_type = 'Google'"
result = ActiveRecord::Base.connection.execute(sql).to_a
Note: If you are using relation as per rails standards it should be
sql = "SELECT users.* FROM users INNER JOIN assets ON assets.assetable_id = users.id WHERE assets.assetable_type = 'Google'"
result = ActiveRecord::Base.connection.execute(sql).to_a
But I think you are using another name i.e., assetable_id instead of user_id.

Related

SQL Postgre to show 1 data if get same some multiple data and how to implement to laravel query

i want to ask about sql in postgresql, i got data from join with 3 table, i got the result but i got multiple data like this image
result
and here my sql code in postgresql
select users.* from users inner join model_has_roles on model_has_roles.model_id = users.id
left join roles on roles.id = model_has_roles.role_id where roles.name not in ('job-seeker') order by users.name asc
how to fix this query where i got the multiple data only 1 data to show.
and i want this sql to implement to laravel query and here my code now
public function getAccountList(){
$req = app(Request::class);
// $getAccount = User::query();
$getAccount = User::join('model_has_roles', function($join) {
$join->on('users.id', '=', 'model_has_roles.model_id');
})->leftJoin('roles', function($join){
$join->on('model_has_roles.role_id', '=', 'roles.id');
});
$getAccount->whereNotIn('roles.name', ['job-seeker']);
if ($q = $req->query('q')) {
$searchTerm = trim(strtolower($q));
$getAccount->whereRaw(
'LOWER(users.name) like (?) or LOWER(users.email) like (?)',
["%{$searchTerm}%", "%{$searchTerm}%"]
);
}
// $getAccount->get()->unique('name');
$getAccount->select(['users.*']);
$paginator = $this->pagination($getAccount);
return $this->paginate($paginator, new UserTransformer);
}
how to fix the query only 1 data to show not the multiple same data. thank you for helping me. God Bless You
use distinct()
$data = DB::table('test')->[your query builder]->distinct()->get();
Laravel Query Builder Docs
Just change a bit to make it related to your query builder

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!

Get Users with params and without others

I'm trying to get group's users with specific ids that are not admin.
For the moment I have:
group.users
.joins(:roles)
.where(id: user_ids)
.where.not(roles: { role_type: Role::Type::ADMIN })
.pluck(:id)
In my log I have:
SQL to load the group:
(0.3ms) SELECT "users"."id" FROM "users" INNER JOIN "groups_users"
ON "users"."id" = "groups_users"."user_id"
WHERE "groups_users"."group_id" = $1 [["group_id", 137375]]
SQL for the query above:
(0.6ms) SELECT "users"."id" FROM "users" INNER JOIN "roles"
ON "roles"."user_id" = "users"."id" AND "roles"."is_destroyed" = $1
INNER JOIN "groups_users" ON "users"."id" = "groups_users"."user_id"
WHERE "groups_users"."group_id" = $2 AND "users"."id" IN (82884, 82885)
AND "roles"."role_type" != $3 [["is_destroyed", "f"],
["group_id", 137375], ["role_type", 1]]
The problem is I always get all the users of the group with matching user_ids. The where.not is not effective.
I had to do something like
users_in_group = group.users.where(id: user_ids).pluck(:id)
users_in_group -= group.users.joins(:roles).where
(roles: { role_type: Role::Type::ADMIN}).pluck(:id)
I don't understand why.
If you want to exclude Admins even if they have other roles, you might use SQL EXISTS:
group.users
.where(id: user_ids)
.where("NOT EXISTS (SELECT 1 FROM roles WHERE user_id = users.id AND role_type = ?", Role::Type::ADMIN)
.pluck(:id)
And, handling typical objection to such advice: it's perfectly fine to get your hands dirty by writing fragments of SQL when you are using ActiveRecord in Rails. You shouldn't limit yourself to the (not so broad) possibilities of its DSL.
UPD.
To simplify your code, you can use Where Exists gem (disclosure: I've written it recently).
Add gem 'where_exists' to your Gemfile, run bundle install, and then the following should work:
group.users
.where(id: user_ids)
.where_not_exists(:roles, role_type: Role::Type::ADMIN)
.pluck(:id)

Ransack no implicit conversion of Ransack::Search into Array

I am currently implementing Ransack for searching functionality.
I have a model Campaigns which collaborates campaigns that the user directly created as well as others so long as the user belongs to the same vendor.
I can combine the results as such:
#search = current_user.campaigns + current_user.vendor.campaigns.where.not(:user_id => current_user.id)
Problem with this is that Ransack will not accept this combination and spits out
no implicit conversion of Ransack::Search into Array
Can someone point me in the direction on how to refactor this code?
TIA
Adding Addition Data
When looking at my console I can see *current_user.campaigns*:
Campaign Load (0.3ms)
SELECT DISTINCT "campaigns".* FROM "campaigns"
WHERE "campaigns"."user_id" = ? [["user_id", 2]]
Running *current_user.vendor.campaigns* give me:
Campaign Load (0.4ms)
SELECT DISTINCT "campaigns".* FROM "campaigns"
INNER JOIN "weeks" ON "campaigns"."id" = "weeks"."campaign_id"
INNER JOIN "products" ON "weeks"."product_id" = "products"."id"
INNER JOIN "locations" ON "products"."location_id" = "locations"."id"
WHERE "locations"."vendor_id" = ? [["vendor_id", 2]]
I can get the first filter of current_user achieved with:
#search = Campaign.where("campaigns.user_id" => current_user.id).search(params[:q])
But I am lost of how I go about building the rest of the join tables to include both elements of data
Solved
#search = Campaign.includes(:weeks).where('(campaigns.user_id LIKE ?) OR (weeks.vendor_id LIKE ?)', current_user.id, current_user.vendor.id).search(params[:q])

rails 4 complex SQL scope

I have a model Users which has_many EventLogs.
I would like create a scope which will order Users by those with the most occurrences of EventLogs they have.
scope :highest_completed_events, .....
How can I count the number of EventLogs with a status of 2, and then order the users with the highest occurrence of that type of event.
User.joins(:event_logs).where("event_logs.status_id = 2")#... COUNT, then ORDER BY
Hope that makes sense.
Here's a query you can execute to get your users ordered by the number of events they have:
#users = User.
select("users.*, COUNT(event_logs.id) as event_logs_count").
joins('LEFT JOIN event_logs ON event_logs.user_id = users.id').
group('users.id').
order('event_logs_count DESC')
You should use a LEFT JOIN since you'll want to include users who don't have any events.
If you were to write it as a scope:
scope(:highest_completed_events, {
select: 'users.*, COUNT(event_logs.id) as event_logs_count',
joins: 'LEFT JOIN event_logs ON event_logs.user_id = users.id',
group: 'users.id',
order: 'event_logs_count DESC'
})
#users = User.highest_completed_events
In order to filter the events by a particular status, simply use a where().
#users = User.
select("users.*, COUNT(event_logs.id) as event_logs_count").
joins('LEFT JOIN event_logs ON event_logs.user_id = users.id').
where('event_logs.status = ?', STATUS_COMPLETE).
group('users.id').
order('event_logs_count DESC')
As an aside, sometimes you'll run into issues with ActiveRecord stripping out your custom select() statement when doing something like #users.count. What I normally do is nest this kind of thing in a custom from() statement.
_from = User.
select("users.*, COUNT(event_logs.id) as event_logs_count").
joins('LEFT JOIN event_logs ON event_logs.user_id = users.id').
group('users.id').
order('event_logs_count DESC').to_sql
#users = User.from("(#{_from}) as users")
#users.count # will work
Try:
User.all.sort_by{|u| u.event_logs.select{|l| l.status_id = 2}.count}.reverse
Or is it 'eventlogs'? Schouldn't your line be has_many :event_logs ?
BTW, my solution is not very efficient but DB-agnostic.