I have a Rails app with a complex query I can't seem to solve. I have 2 tables, clubs and selections. Table clubs is just like this:
id,name
1,A
2,B
3,C
4,D
Table selections contains the selected clubs from table clubs by all the users:
id,club_id,user_id
1,1,1
2,1,2
3,2,3
4,3,1
5,3,3
Now I want a select box with all items from table clubs without the elements already chosen by the current user (he can't chose the same club twice). So in the case of user 1, it should only show clubs B and D, because he already has chosen A and C.
So I created this as a scope in the model:
scope :selectable, ->(current_user) {
joins('LEFT OUTER JOIN selections ON selections.club_id = clubs.id').
where('selections.id IS NULL OR selections.user_id != ?', current_user.id).
group('clubs.id')
}
This works fine when there is only one user making selections, but if more users chose the same club as the current user, these clubs still show up. What can I improve to show the correct clubs?
I found a solution which seems to work, but I don't know if it is the most elegant one:
scope :selectable, ->(current_user) {
joins('LEFT OUTER JOIN selections ON selections.club_id = clubs.id').
where('selections.id IS NULL OR selections.user_id != ?', current_user.id).
where('clubs.id NOT IN (?)', current_user.clubs).
group('clubs.id')
}
Related
This is task table:
This is user table:
I want to select user tasks.
I would give from backend ("given_to_user) id.
But The thing is I want that SELECTED data would have usernames instead of Id which is (created_by_user and given_to_user).
SELECTED table would look like this.
Example:
How to achieve what I want?
Or maybe I designed poorly my tables that It is difficult to select data I need? :)
task table has to id values that are foreign keys to user table.
I tried many thinks but couldn't get desired result.
You did not design poorly the tables.
In fact this is common practice to store the ids that reference columns in other tables. You just need to learn to implement joins:
SELECT
task.id, task.title, task.information, user.usename AS created_by, user2.usename AS given_to
FROM
(task INNER JOIN user ON task.created_by_user = user.id)
INNER JOIN user AS user2 ON task.created_by_user = user2.id;
Do you just want two joins?
select t.*, uc.username as created_by_username,
ug.username as given_to_username
from task t left join
users uc
on t.created_by_user = uc.id left join
users ug
on t.given_to_user = ug.id;
This uses left join in case one of the user ids is missing.
Sorry about this dreadful title. Imagine tables like this: http://sqlfiddle.com/#!9/48d921/1
Here, we run a query for all users with the name "Bob", but we are also interested in all users in the same postcode as "Bob" and also all users of the same "type" as Bob.
You can see I joined the same tables twice to achieve this. The trouble with it is it doesn't scale; the more criteria I want to "explore" the more times I have to join the same tables, making the select statement more cumbersome.
So:
What's the best way to do this?
Does this type of query have a name?
The answer updated
SELECT
FROM user
where u.name="Bob"
OR (u.postcode in (SELECT a.postcode
FROM USER u
JOIN ADDRESS a on a.user=u.id
WHERE u.name="Bob")
)
OR (u.type in (SELECT ut.type
FROM USER u
JOIN USER_TYPE ut1 on u.id=ut1.user
WHERE u.name="Bob")
)
So you scan users table just once for each record checking the linking criteria
I will try simplify my example. I've got a table called friendship with two relations to user (lets call them user1 and user2). I would like to retrieve an alphabetical list of my friends. I can appear in the user1 or in the user2. The only way I can know is by using unions:
SELECT f.*
FROM
(SELECT "user1"."name" AS "name"
FROM "friendship" AS "friendship" LEFT JOIN "user" AS "user1"
ON "friendship"."user1_id" = "requestedUser"."id"
AND "user1"."id" != 'me'
WHERE ("friendship"."user2_id" = 'me')
UNION
SELECT "user2"."name" AS "name"
FROM "friendship" AS "friendship"
LEFT JOIN "user" AS "user2"
ON "friendship"."user2_id" = "user2"."id"
AND "user2"."id" != 'me'
WHERE ("friendship"."user1_id" = 'me')
) AS f
ORDER BY "name"
LIMIT ?;
Is there any easier way? Or maybe there is possibility to do it in single SELECT? Do you know how do it in sequelize?
in Sequelize you should be able to do user.getFriends() to get all of a users friends. If you aliased the associations to something like outbound and inbound it would be user.getInbound() and user.getOutbound().
Sequelize automatically creates and names methods on instances that allow you to get associated models.
I have an Adventure model, which is a join table between a Destination and a User (and has additional attributes such as zipcode and time_limit). I want to create a query that will return me all the Destinations where an Adventure between that Destination and the User currently trying to create an Adventure does not exist.
The way the app works when a User clicks to start a new Adventure it will create that Adventure with the user_id being that User's id and then runs a method to provide a random Destination, ex:
Adventure.create(user_id: current_user.id) (it is actually doing current_user.adventures.new ) but same thing
I have tried a few things from writing raw SQL queries to using .joins. Here are a few examples:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
Destination.joins('LEFT OUTER JOIN adventure ON destination.id = adventure.destination_id').where('adventure.user_id != ?', user.id)
The below should return all destinations that user has not yet visited in any of his adventures:
destinations = Destination.where('id NOT IN (SELECT destination_id FROM adventures WHERE user_id = ?)', user.id)
To select a random one append one of:
.all.sample
# or
.pluck(:id).sample
Depending on whether you want a full record or just id.
No need for joins, this should do:
Destination.where(['id not in ?', user.adventures.pluck(:destination_id)])
In your first attempt, I see the problem to be in the usage of equality operator with where.not. In your first attempt:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
you're doing where.not('adventures.user_id != ?'), user.id). I understand this is just the opposite of what you want, isn't it? Shouldn't you be calling it as where.not('adventures.user_id = ?', user.id), i.e. with an equals =?
I think the following query would work for the requirement:
Destination.joins(:adventures).where.not(adventures: { user_id: user.id })
The only problem I see in your second method is the usage of destinations and adventures table in both join and where conditions. The table names should be plural. The query should have been:
Destination
.joins('LEFT OUTER JOIN adventures on destinations.id = adventures.destination_id')
.where('adventures.user_id != ?', user.id)
ActiveRecord doesn't do join conditions but you can use your User destinations relation (eg a has_many :destinations, through: adventures) as a sub select which results in a WHERE NOT IN (SELECT...)
The query is pretty simple to express and doesn't require using sql string shenanigans, multiple queries or pulling back temporary sets of ids:
Destination.where.not(id: user.destinations)
If you want you can also chain the above realation with additional where terms, ordering and grouping clauses.
I solved this problem with a mix of this answer and this other answer and came out with:
destination = Destination.where
.not(id: Adventure.where(user: user)
.pluck(:destination_id)
)
.sample
The .not(id: Adventure.where(user: user).pluck(:destination_id)) part excludes destinations present in previous adventures of the user.
The .sample part will pick a random destination from the results.
How can I find records that either miss an association, or matches a criteria on a record association?
I have a Reservation model that can or cannot have a Booking (has_one) record associated to it. I'd like to find all records with no associations and if I do have a Booking associated, those that which booking_agent is not of class Venue::Promoter.
Here is my current scope to do so:
scope :not_requested_by_promoters, -> {
joins("LEFT JOIN
\"bookings\" ON \"reservations\".\"id\" = \"bookings\".\"reservation_id\"
AND \"bookings\".\"booking_agent_type\" != 'Venue::Promoter'")
}
Another developer wrote the following scope, but it didn't match those records without a booking associated to it:
scope :not_requested_by_promoters, -> {
joins(:booking).where('booking_agent_type != ?', "Venue::Promoter")
}
Both do not fulfill my requirement. What am I missing here? I've done some research and the left join should work.
Database is PostgreSQL.
You need a LEFT JOIN to show all Reservations, even those without Bookings. After you make the join you just need to check the records that have bookings.reservation_id IS NULL. This will bring only the desired results.
scope :not_requested_by_promoters, -> {
joins("LEFT JOIN
bookings ON reservations.id = bookings.reservation_id
AND bookings.reservation_id IS NULL
AND bookings.booking_agent_type != 'Venue::Promoter'")
}
Thanks do Dolph at Learn Thoughtbot chat room:
joins('LEFT JOIN bookings ON bookings.reservation_id = reservations.id').
where("(bookings.reservation_id IS NULL)
OR (bookings.booking_agent_type != ?)", 'Venue::Promoter').