Rails ActiveRecord: Missing column in grouping query - ruby-on-rails

Key.select('products.name as product, product_groups.name as product_group, AVG(keys.cost) as cost')
.group('products.id, product_groups.id')
.left_joins(:product,:product_group)
the result:
=> #<ActiveRecord::Relation [#<Key id: nil, cost: 0.6e1>, #<Key id: nil, cost: 0.4e1>]>
Expected return 3 field, but returnig value: 2 field.
I found the solution. The detail areas in the console did not appear as HASH.

In my understanding the grouping statement would only return Aggregated columns and columns that are used to group the data set. In your case you have not used the grouping columns in the select list but, some other fields. As a result you don't receive the other two columns.

Related

Querying an JSONB array of objects in Rails 7

I'm on Ruby On Rails 7
I have a class ActiveRecord class called Thread.
There are 2 records :
id: 1,
subject: "Hello",
participants:
[{"name"=>"guillaume", "email"=>"guillaume#example.com"},
{"name"=>"Fabien", "email"=>"fabien#example.com"},]
id: 2,
subject: "World",
participants:
[{"name"=>"guillaume", "email"=>"guillaume#example.com"},
{"name"=>"hakim", "email"=>"hakim#example.com"},]
participants is a JSONB array column
I want to find records who have the email "hakim#example.com" in participants column.
I don't think this is supported directly by AR.
You can "unwrap" the jsonb array using _jsonb_array_elements" or as the docs say: "Expands the top-level JSON array into a set of JSON values."
See https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSING-TABLE
You can query the DB with SQL like this:
select * from threads
where exists(
select true
from jsonb_array_elements(participants) as users
where users->>'email' = 'fabien#example.com'
)
Which translates into something like this in AR
condition = <<~SQL
exists(
select true
from jsonb_array_elements(participants) as users
where users->>'email' = ?
)
SQL
Thread.where(condition, 'fabien#example.com')
(from the top of my head. can not test this)
IMHO:
This is something that I see often happening with JSONB data: looks nice and simple in the beginning and then turns out to be more complicated (to query, validate, enforce integrity, insert) than a proper relationship (e.g. a participants table that links users/threads)

Active Record .includes with where clause

I'm attempting to avoid an N+1 query by using includes, but I need to filter out some of the child records. Here's what I have so far:
Column.includes(:tickets).where(board_id: 1, tickets: {sprint_id: 10})
The problem is that only Columns containing Tickets with sprint_id of 10 are returned. I want to return all Columns with board_id of 1, and pre-fetch tickets only with a sprint_id of 10, so that column.tickets is either an empty list of a list of Ticket objects with sprint_id 10.
This is how includes is intended to work. When you add a where clause it applies to the entire query and not just loading the associated records.
One way of doing this is by flipping the query backwards:
columns = Ticket.eager_load(:columns)
.where(sprint_id: 10, columns: { board_id: 1 })
.map(&:column)
.uniq
Column.includes(:tickets).where(board_id: 1, tickets: {sprint_id: 10}) makes two SQL queries. One to select the columns that match the specified where clause, and another to select and load the tickets that their column_id is equal to the id of the matched columns.
To get all the related columns without loading unwanted tickets, you can do this:
columns = Column.where(board_id: 1).to_a
tickets = Ticket.where(column_id: columns.map(&:id), sprint_id: 10).to_a
This way you won't be able to call #tickets on each column (as it will again make a database query and you'll have the N+1 problem) but to have a similar way of accessing a column's tickets without making any queries you can do something like this:
grouped_tickets = tickets.group_by(&:column_id)
columns.each do |column|
column_tickets = grouped_tickets[column.id]
# Do something with column_tickets if they're present.
end

Postgres group by day of month, ActiveRecord(Rails) returns array with nil ids while database query works fine [duplicate]

Tag.joins(:quote_tags).group('quote_tags.tag_id').order('count desc').select('count(tags.id) AS count, tags.id, tags.name')
Build query:
SELECT count(tags.id) AS count, tags.id, tags.name FROM `tags` INNER JOIN `quote_tags` ON `quote_tags`.`tag_id` = `tags`.`id` GROUP BY quote_tags.tag_id ORDER BY count desc
Result:
[#<Tag id: 401, name: "different">, ... , #<Tag id: 4, name: "family">]
It not return count column for me. How can I get it?
Have you tried calling the count method on one of the returned Tag objects? Just because inspect doesn't mention the count doesn't mean that it isn't there. The inspect output:
[#<Tag id: 401, name: "different">, ... , #<Tag id: 4, name: "family">]
will only include things that the Tag class knows about and Tag will only know about the columns in the tags table: you only have id and name in the table so that's all you see.
If you do this:
tags = Tag.joins(:quote_tags).group('quote_tags.tag_id').order('count desc').select('count(tags.id) AS count, tags.id, tags.name')
and then look at the counts:
tags.map(&:count)
You'll see the array of counts that you're expecting.
Update: The original version of this answer mistakenly characterized select and subsequent versions ended up effectively repeating the current version of the other answer from #muistooshort. I'm leaving it in it's current state because it has the information about using raw sql. Thanks to #muistooshort for pointing out my error.
Although your query is in fact working as explained by the other answer, you can always execute raw SQL as an alternative.
There are a variety of select_... methods you can choose from, but I would think you'd want to use select_all. Assuming the build query that you implicitly generated was correct, you can just use that, as in:
ActiveRecord::Base.connection.select_all('
SELECT count(tags.id) AS count, tags.id, tags.name FROM `tags`
INNER JOIN `quote_tags` ON `quote_tags`.`tag_id` = `tags`.`id`
GROUP BY quote_tags.tag_id
ORDER BY count desc')
See http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html for information on the various methods you can choose from.

Whats the best way to get the nth to last record?

I saw here: How to get last N records with activerecord? that the best way to get the last 5 records is SomeModel.last(5).
Is the best way to get the 5th last record only SomeModel.last(5).first or is it something else?
Thanks in advance!
What you're looking for is a combination of LIMIT, OFFSET, and ensuring that the query is using ORDER BY as a constraint. This is described on the PostgreSQL - Queries: Limit and Offset documentation page.
An example is:
irb> SomeModel.count
=> 35
irb> SomeModel.order(:id).reverse_order.limit(1).offset(4)
# SomeModel Load (0.7ms) SELECT "some_models".* FROM "some_models" ORDER BY "some_models".id DESC LIMIT 1 OFFSET 4
=> #<ActiveRecord::Relation [#<SomeModel id: 31, name: "hello world", created_at: "2014-03-24 21:52:46", updated_at: "2014-03-24 21:52:46">]>
Which yields the same result (albeit as an AR::Relation) as:
irb> SomeModels.last(5).first
# SELECT "some_models".* FROM "some_models" ORDER BY "some_models"."id" DESC LIMIT 5
=> #<SomeModel id: 31, name: "hello world", created_at: "2014-03-24 21:52:46", updated_at: "2014-03-24 21:52:46">
The difference is that in the former query, PostgreSQL will treat the .reverse_order.limit.offset query in memory, and restrict the values appropriately before it returns the result. In the latter, PostgreSQL will return 5 results, and you restrict the values in Ruby by using #first.
You can use offset with a query in the reverse order. For example, the fifth last created record can be retrieved with:
SomeModel.order("created_at DESC").offset(4).first
The simplest way to get the 5th last record is with combination of descending sort, and fifth:
SomeModel.order("created_at DESC").fifth

Rails loop through ActiveRecord::Associations:CollectionProxy

I've got a ActiveRecord::Associations:CollectionProxy of the format
#<DailyViewMetric article_id: xxxxxx, date: xxxxxxx, views: xxxxxx, visitors xxxxx,.....> in a variable called metrics.
The article_id is a foreign key and is repeated in the table (as one article can have metrics on consecutive days (i.e. 15 views today and 20 the day after).
I need a way to loop through this and apply different operations to some of these metrics like get the smallesest date for each article, the total number of views (= sum(views)). I tried metrics.map do |a| and metrics.collect but always just get ruby errors undefined method 'views' For example let's go with the following simplified dataset
article_id date views
1 2014-01-01 10
2 2014-01-01 15
1 2014-01-02 20
2 2014-01-02 12
3 2014-01-02 6
should result in the following new array afterwards:
article_id date views
1 2014-01-01 30
2 2014-01-01 27
3 2014-01-02 6
as you can see the views variable holds the sum of the views for that respective article, the date variable is the minimum of the dates. How do I do this properly? I also tried metrics.to_a but I still get this error.
EDIT
I tried DailyViewMetric.find_by_sql("SELECT article_id, sum(views) from daily_view_metrics where article_id in(SELECT id from articles where user_id=xxx) GROUP BY article_id")
which, if i execute the query in the mysql console, works perfectly fine and returns the second table from up above. but when I run it in the rails console it gives me
[#<DailyViewMetric id: nil, article_id: 1089536>, #<DailyViewMetric id: nil, article_id: 1128849>, #<DailyViewMetric id: nil, article_id: 1141623>,
You can do this completely in SQL/ActiveRecord. The query you want to run ultimately is
SELECT article_id, min(date), sum(views)
FROM daily_value_metrics -- Or whatever your table is called
GROUP BY article_id
You can run this with ActiveRecord with the following:
table = DailyValueMetric.arel_table
results = DailyValueMetric.select(table[:article_id],
table[:date].minimum.as('date'),
table[:views].sum.as('views')).group(:article_id).to_a
# Calling to_a so I can call first for the example
results.first.date #=> date
results.first.views #=> views
results.first.article_id #=> Id
The records will look like
[#<DailyViewMetric id: nil, article_id: 1089536>, ...]
Because the SQL query does not return an id column in the result set. This is because of the way that ActiveRecord::Base#inspect shows the columns defined on the table, and not the returned values outside of the table columns. Views and Date will not necessarily be shown, unless there is a column with the same name, but if you call those attributes on the model instance, you will be able to get the value

Resources