Query using joins (many m:n relations) - sql

I have to do the following query either with Rails' active record or sql (sqlite).
Let's start with the description of the model.
User 1:n Interest
Interest m:n Category
Interest m:n Location
Event m:n Category
Event m:n Location
m:n relationships are being made possible through a third table with the pattern interests_categories, events_categories, etc.
I've got users and those users have interests (Categories -> e.g. Sport, Music and locations he likes). Events get tagged with locations and categories.
Now I'd like to list all the users that might fit to a specific events. For example, if there's an event that takes place in NYC and has something todo in sports, I'd like to get a list of users that might be interested in this event based on the location and categories.
I'd like to do it in a single database access instead of multiple.
How would that look like in ActiveRecord or sql? (The more efficient way would be prefered)
I'll probably have to join the Category/Location tables first, join the interests based on all the tables of the previously join and then select all the users based on the interests.
Unfortunately I have limited knowledge of joins sql. Or is there even a different approach available?

After some thought I think this join will work.
class Events
def get_users
User.joins(%"inner join interests on interests.user_id = users.id
inner join categories_interests
on categories_interests.interest_id = interests.id
inner join categories on categories_interests.category_id = categories.id
inner join interests_locations
on interests_locations.interest_id = interests.id
inner join locations on interests_locations.location_id = locations.id
inner join categories_events on categories_events.category_id = categories.id
inner join events_locations on events_locations.location_id = locations.id
inner join events
on categories_events.event_id = events.id
and events_locations.event_id = events.id").where("events.id = :eid", :eid => id)
end
end

Related

SQL query wrong index when where on join

I have a query with joins that is not using the index that would be the best match and I am looking for help to correct this.
I have the following query:
select
equipment.name,purchaselines.description,contacts.name,vendors.accountNumber
from purchaselines
left join vendors on vendors.id = purchaselines.vendorId
left join contacts on contacts.id = vendors.contactId
left join equipment on equipment.id = purchaselines.equipmentId
where contacts.id = 12345
The table purchaselines has an index on the column vendorId, which is the proper index to use. When the query is run, I know the value of contacts.id which is joined to vendors.contactId which is joined to purchaselines.vendorId.
What is the proper way to run this query? Currently, no index is used on the table purchaselines.
If you are intending to query a specific contact, I would put THAT first since that is the primary basis. Additionally, you had left-joins to the other tables (vendors, contacts, equipment). So by having a WHERE clause to the CONTACTS table forces the equation to become an INNER JOIN, thus REQUIRING.
That said, I would try to rewrite the query as (also using aliases for simplified readability of longer table names)
select
equipment.name,
purchaselines.description,
contacts.name,
vendors.accountNumber
from
contacts c
join vendors v
on c.id = v.contactid
join purchaselines pl
on v.id = pl.vendorid
join equipment e
on pl.equipmentid = e.id
where
c.id = 12345
Also notice the indentation of the JOINs helps readability (IMO) to see how/where each table gets to the next in a more hierarchical manner. They are all regular inner JOIN context.
So, the customer ID will be the first / fastest, then to vendors by that contact ID which should optimize the join to that. Then, I would expect the purchase lines to have an index on vendorid optimizing that. And finally, the equipment table on ITs PK.
FEEDBACK Basic JOIN clarification.
JOIN is just the explicit statement of how two tables are related. By listing them left-side and right-side and the join condition showing on what relationship is between them is all.
Now, in your data example, each table is subsequently nested under the one prior. It is quite common though that one table may link to multiple other tables. For example an employee. A customer could have an ethnicity ID linking to an ethnicity lookup table, but also, a job position id also linking to a job position lookup table. That might look something like
select
e.name,
eth.ethnicity,
jp.jobPosition
from
employee e
join ethnicitiy eth
on e.ethnicityid = eth.id
join jobPosition jp
on e.jobPositionID = jp.id
Notice here that both ethnicity and jobPosition are at the same hierarchical level to the employee table scenario. If, for example, you wanted to further apply conditions that you only wanted certain types of employees, you can just add your logical additional conditions directly at the location of the join such as
join jobPosition jp
on e.jobPositionID = jp.id
AND jp.jobPosition = 'Manager'
This would get you a list of only those employees who are managers. You do not need to explictily add a WHERE condition if you already include it directly at the JOIN/ON criteria. This helps keeping the table-specific criteria at the join if you ever find yourself needing LEFT JOINs.

Find user name for multiple user id columns

I am working with an existing database structure (I'm aware the structure might not be perfect, but it is out of my control). I have one table called Users with user id and name among other irrelevant columns.
My problem is with combining users and the table called settlements. The columns in settlements are as following: Case_ID, created_date, approve_date, approving_user_id, control_date, controlling_user_id
How do I interact between the two tables so that my end result shows the names of the ones approving/controlling and not the ID.
I have tried to work with joins but I don't know how to proceed when there are two different users that have approved and controlled.
select
u.user_id, u.agent_name, s.creating_date, s.approving_date,
s.approving_user, s.controlling_date, s.controlling_user
from USERS u
left join SETTLEMENTS s on u.user.id = s.approving_user
I know that I could join them in too two different tables, one called approve and one control but I would prefer to have the end result in the same table.
You want two joins, but you should start with settlements and then bring in the two users tables:
select s.approving_user, s.controlling_date, s.controlling_user,
s.creating_date, s.approving_date,
ua.user_id as approving_user_id, ua.agent_name as approving_agent_name,
uc.user_id as controlling_user_id, uc.agent_name as controlling_agent_name
from settlements s left join
users ua
on ua.user.id = s.approving_user left join
users uc
on uc.user_id = s.controlling_user_id

How to select records from database table which has to user id (created_by_user, given_to_user) and replace users id by usernames?

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.

SQL one to many , many to many, query Build

basically in order to process things quickly I need to be able to access the info from two tables linked by an intermediate reached by a one to many and then a many to many relationship. Specifically I have, the following relevant tables
users[id, ...]
trips[user_id,...]
trip_type[id,trip_id,type_id]
types[id,...]
i.e Users have many trips which have many types which themselves have many trips.
My aim is to get a list of every type id (with repetitions should they occur) associated with a particular user via every trip they have taken.
I'm sure this is trivial to the SQL inclined but I am not one of those individuals and as such am just writing nonsense at this point.
SELECT tt.type_id, u.user_id, tr.id
FROM trip_type tt
INNER JOIN trips tr ON tr.id = tt.trip_id
INNER JOIN types ty ON ty.id = tt.type_id
INNER JOIN users u ON u.id = tr.user_id
WHERE u.user_id = ?
This will return type_ids, user_id and trip ids for a particular user.
select users.id, trip_type.type_id, count(*) from users join trips on trips.user_id = users.id join trip_type on trips.id = trip_type.trip_id group by users.id, trip_type.type_id
This will also give you a count of how many trips of each type a user has taken. This query will not list users who have not taken any trips. If you need those users as well change the word 'join' to 'left join'

SQL Conditional / Case Joining / Polymorphic Associations?

I'm trying to implement something similar to Ruby on Rails' polymorphic relationships.
I have the following three tables :
Events
Users
Organisations
An event can be owner by either a user or an organisation, so my Events table includes the columns: owner_type and owner_id.
I can easily list all events that belong to either users or organisations through an inner join and where clause, however, is there a way to make the join table conditional based on the value of the owner_type column, allowing all events to be listed together, regardless of owner_type?
I hope that makes sense.
Any advice appreciated.
Thanks.
You can't make the join table conditional, so in this case you would have to join events to both users and organisations and use coalesce to merge the common fields (eg. name) together.
select e.id, coalesce(u.name, o.name) owner_name
from events e
left join users u on e.owner_id = u.id and e.owner_type = 'user'
left join organisations o on e.owner_id = o.id and e.owner_type = 'org'
However, you may consider creating an owners table, which contains both users and organisations, with a structure like (id, type, org_id, name, ...). This would only require a single join, but may complicate other areas of your schema, eg. user membership of an organisation.
An alternative method would be to union the users and organisations tables together and then join once from events.
Owner has columns common to all owner-subtypes.
Person and Organization have columns specific to each one.
How about moving ownership information out of Events into two join tables, EventsUsers and EventsOrganisations (each of which has just two columns, FKs to Events and the apppropriate owning-object table) ? Then you can UNION two queries each of which joins through the join table to the owning-object table.
A bit old, but I think it would be useful, this version performs better in my scenario than multiple joins
select e.id,
case when e.owner_type = 'person' then
(
select p.name from person p
where p.id=e.owner_id
)
else
(
select o.name from organization o
where o.id=e.owner_id
)
end entityName,
e.owner_type
from events e
in postgres you could even build a json of entire related entity