ActiveRecord Join Query and select in Rails - sql

In my rails 4 application, a client (clients table) can have many projects (projects table). I have a column called name in each table. I am trying to write a join and then select which uses projects as the base table and clients as the lookup table. client_id is the foreign_key in the projects table:
I am writing my query as follows:
Project.joins(:client).select('projects.id,projects.name,clients.name')
I get the following response:
Project Load (0.6ms) SELECT projects.id,projects.name,clients.name FROM "projects" INNER JOIN "clients" ON "clients"."id" = "projects"."client_id"
=> #<ActiveRecord::Relation [#<Project id: 1, name: "Fantastico Client">]>
If I try to alias it like so:
Project.joins(:client).select('projects.id,projects.name,clients.name as client_name')
Then I get the following response:
Project Load (0.8ms) SELECT projects.id,projects.name,clients.name as client_name FROM "projects" INNER JOIN "clients" ON "clients"."id" = "projects"."client_id"
=> #<ActiveRecord::Relation [#<Project id: 1, name: "The Dream Project">]>
In either case, ActiveRecord looses one of the names as you can see from the above response. How should I be writing this query?

If the column in select is not one of the attributes of the model on which the select is called on then those columns are not displayed. All of these attributes are still contained in the objects within AR::Relation and are accessible as any other public instance attributes.
You could verify this by calling first.client_name:
Project.joins(:client)
.select('projects.id,projects.name,clients.name as client_name')
.first.client_name

You can use :'clients.name' as one of your symbols. For instance:
Project.select(:id, :name, :'clients.name').joins(:client)
I like it better because it seems like Rails understands it, since it quotes all parameters:
SELECT "projects"."id", "projects"."name", "clients"."name"
FROM "projects"
INNER JOIN "clients" ON "clients"."id" = "projects"."client_id"
(I'm not 100% sure that's the exact SQL query, but I'm fairly certain and I promise it will use "clients"."name")

To get both project table name and client name you can do like below query
Project.joins(:client).pluck(:name,:'clients.name')

your query don't looses any thing. Actually you have applied join on models and you have written Project.joins(:client) that why it is looking like.
means It will hold Project related data as it is and associated data hold with alias name that you have given 'client_name' in your query.
if you use
Project.joins(:client)
.select('projects.id project_id, projects.name projects_name,clients.name as client_name')
then it look like
[#, #]
but it hold all the attribute that you selected.

Try This:
sql = Project.joins(:client).select(:id, :name, :"clients.name AS client_name").to_sql
data = ActiveRecord::Base.connection.exec_query(sql)
OUTPUT
[
{"id"=>1, "name"=>"ProjectName1", "client_name"=>"ClientName1"},
{"id"=>2, "name"=>"ProjectName2", "client_name"=>"ClientName2"}
]

Related

Querying between two tables that share an association

New to seqeul and sql in general. I have two tables, groups and resources, that are associated many_to_many and therefore have a groups_resources join table. I also have a task table that has a foreign_key :group_id, :groups and is associated many_to_one with groups.
I'm trying to figure out what query to use that will allow my to get the resources that are able to do a task, based on a task's group. Do I have to do a complicated query via the `groups_resources' join table, or is there a more straightforward query/ way of setting up my associations?
Thanks!
I would structure the SQL statement as below. Which would provide you the resources objects that are associated with a specific task id through the join table.
SELECT r.*
FROM resources r
JOIN groups_resources gr ON gr.resources_id = r.id
JOIN groups g ON gr.group_id = g.id
JOIN task t ON t.id = g.id
WHERE t.id = ?
I think following is enough:
select res.* from resources res, task tk, groups_resources gr
where res.resource_id = gr.resource_id and
gr.group_id = tk.group_id and
tk.group_id=<>;
The other two answers are helpful for how to structure a SQL query, but thought I would answer my own question specifically as it relates to Sequel. Turns out there is a many_through_many plugin that makes this sort of querying simple, if you make both tables many_to_many :
Task.plugin :many_through_many
Task.many_through_many :resources,
:through =>[
[:groups_tasks, :task_id, :group_id],
[:groups, :id, :id],
[:groups_resources, :group_id, :resource_id]
]
Now you can just call something like task.resources on a Task instance, even though your tables don't explicitly associate tasks and resources.

Double relation to one table merged as one

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.

Sequelize select with aggregate includes bad attributes => error

The models are Person and Team with a M:1 relationship.
The query that fails:
db.Person.findAll({
attributes: [ [sequelize.fn('COUNT', sequelize.col('*')), 'count']],
include: [{model: db.Team, required: true}], // to force inner join
group: ['team.team_id']
}).complete(function(err, data) {
...
});
The generated SQL is:
SELECT "person"."person_id",
COUNT(*) AS "count",
"team"."team_id" AS "team.team_id",
"team"."team_name" AS "team.team_name",
"team"."team_email" AS "team.team_email",
"team"."team_lead" AS "team.team_lead"
FROM "person" AS "person" INNER JOIN "team" AS "team"
ON "person"."team_id" = "team"."team_id"
GROUP BY "team"."team_id";
Obviously, the person.person_id included in the SELECT clause, screws it up, with Postgres complaining correctly that:
ERROR: column "person.person_id" must appear in the `GROUP BY` clause or be used in an aggregate function
It seems that the attributes option is taken into account since the COUNT appears correctly, but all the rest of the columns in the SELECT clause are added by default.
Is there another way (besides attributes) to explicitly define which columns appear in the SELECT clause or is this a bug?
I'm using Sequelize v2.0.3.
Sequelize will always add the primary key to the selected fields. Currently there is no way to disable that.
Perhaps adding DISTINCT ON as suggested here https://stackoverflow.com/a/19723716/800016 to person_id could fix the issue?
Otherwise, feel free to open an issue on the sequelize bug tracker

Rails ActiveRecord query where relationship does not exist based on third attribute

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.

Can someone verify that I'm interpreting this sql statement properly?

I'm using rails and a plugin to manage tags - The query I'm using involves
- a tasks table (many to many relationship with users)
- a tags table ( stores reference to a tag - id (int), name (string) )
- a taggable table (polymorphic table that references a tag, the taggable item, and the tagger of that item, in my case, tasks)
Here is the sql:
ActsAsTaggableOn::Tag Load (0.1ms)
SELECT "tags".* FROM "tags" WHERE (lower(name) = '#sharedtag')
Task Load (0.4ms)
SELECT "tasks".*
FROM "tasks" INNER JOIN "task_relationships"
ON "tasks"."id" = "task_relationships"."task_id"
JOIN taggings tasks_taggings_f7b47be
ON tasks_taggings_f7b47be.taggable_id = tasks.id
AND tasks_taggings_f7b47be.taggable_type = 'Task'
AND tasks_taggings_f7b47be.tag_id = 23
WHERE "task_relationships"."user_id" = 1
ORDER BY tasks.created_at DESC
What I'm confused about is line 3 of the task load, where tasks_taggings_f7b47be.tag_id shows up out of nowhere. I assume it's some sort of temporary table or reference to a created join table, but have only recently started exploring sql.
Any explanation, links, or general knowledge would be appreciated.
I think tasks_taggings_f7b47be is an alias to the taggings table => http://www.w3schools.com/sql/sql_alias.asp
It is permissible to omit the AS keyword:
"The general syntax of an alias is SELECT * FROM table_name [AS] alias_name. Note that the AS keyword is completely optional and is usually kept for readability purposes." more