I know I can find the first user in my database in the command line with,
User.first
And I can find the last with
User.last
my question is how would I call the 11th user in a database.
You can use offset with order:
User.offset(10).order(:id).first
You can do:
User.limit(1).offset(10)
That reduces the work to a SQL statement that looks like this:
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 10
Using all will require loading all the users into memory and then finding the 11th one in that array. Quite pricey.
You can do
User.all[10]
User.all gives you an array or objects with index starting from 0. To access 11th user you can do that.
Related
I have a method in my Trial model that take the last two digits of season_year and concats a unique number on the end generated from a count method.
It all works fine and dandy, except when i delete a previous record.
For example: Say I have created 1800, 1801 and 1802. When I delete 1800 and try to create a new record I get the following error.
CACHE (0.0ms) SELECT COUNT(*) FROM "trials" WHERE "trials"."season_year" BETWEEN $1 AND $2 [["season_year", "2018-01-01"], ["season_year", "2018-12-31"]]
CACHE Trial Exists (0.0ms) SELECT 1 AS one FROM "trials" WHERE "trials"."trial_number" = $1 LIMIT $2 [["trial_number", 1802], ["LIMIT", 1]]
For some reason it keeps looping the above.
What my desired outcome is for it to check if number exists, if it does move on to the next one, but for some reason it doesn't work when a previous record is deleted.
What I'd like it to do is start at 00 every time to check if a number has been used. if it has, move on to the next one. ie. 01
Model
class Trial < ApplicationRecord
before_create :create_trial_number
def count_records_from_same_year
self.class.where(season_year: (season_year.beginning_of_year..season_year.end_of_year)).count
end
def create_trial_number
loop do
year = (season_year).strftime("%y")
self.trial_number = year.concat(sprintf '%02d', count_records_from_same_year)
break unless self.class.where(trial_number: self.trial_number).exists?
end
end
end
It looks like the code you posted does mutate any value to advance the loop? I assume you'd need to add a self.season_year += 1 before the end of the block to achieve your goal.
Your method of creating a unique ID for the trial is highly ineffective.
You can use the id from the trials table as a unique identifier for the individual trial.
Alternatively you can let the database create an automatically incrementing column. MySQL uses the AUTO_INCREMENT keyword, Postgres uses the concept of sequences.
So here's the lay of the land:
I have a Applicant model which has_many Lead records.
I need to group leads by applicant email, i.e. for each specific applicant email (there may be 2+ applicant records with the email) i need to get a combined list of leads.
I already have this working using an in-memory / N+1 solution
I want to do this in a single query, if possible. Right now I'm running one for each lead which is maxing out the CPU.
Here's my attempt right now:
Lead.
all.
select("leads.*, applicants.*").
joins(:applicant).
group("applicants.email").
having("count(*) > 1").
limit(1).
to_a
And the error:
Lead Load (1.2ms) SELECT leads.*, applicants.* FROM "leads" INNER
JOIN "applicants" ON "applicants"."id" = "leads"."applicant_id"
GROUP BY applicants.email HAVING count(*) > 1 LIMIT 1
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: column
"leads.id" must appear in the GROUP BY clause or be used in an
aggregate function
LINE 1: SELECT leads.*, applicants.* FROM "leads" INNER JOIN
"appli...
This is a postgres specific issue. "the selected fields must appear in the GROUP BY clause".
must appear in the GROUP BY clause or be used in an aggregate function
You can try this
Lead.joins(:applicant)
.select('leads.*, applicants.email')
.group_by('applicants.email, leads.id, ...')
You will need to list all the fields in leads table in the group by clause (or all the fields that you are selecting).
I would just get all the records and do the grouping in memory. If you have a lot of records, I would paginate them or batch them.
group_by_email = Hash.new { |h, k| h[k] = [] }
Applicant.eager_load(:leads).each_batch(10_000) do |batch|
batch.each do |applicant|
group_by_email[:applicant.email] << applicant.leads
end
end
You need to use a .where rather than using Lead.all. The reason it is maxing out the CPU is you are trying to load every lead into memory at once. That said I guess I am still missing what you actually want back from the query so it would be tough for me to help you write the query. Can you give more info about your associations and the expected result of the query?
In SQLite (development) I don't have any errors, but in production with Postgres I get the following error. I don't really understand the error.
PG::Error: ERROR: column "commits.updated_at" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...mmits"."user_id" = 1 GROUP BY mission_id ORDER BY updated_at...
^
: SELECT COUNT(*) AS count_all, mission_id AS mission_id FROM "commits" WHERE "commits"."user_id" = 1 GROUP BY mission_id ORDER BY updated_at DESC
My controller method:
def show
#user = User.find(params[:id])
#commits = #user.commits.order("updated_at DESC").page(params[:page]).per(25)
#missions_commits = #commits.group("mission_id").count.length
end
UPDATE:
So i digged further into this PostgreSQL specific annoyance and I am surprised that this exception is not mentioned in the Ruby on Rails Guide.
I am using psql (PostgreSQL) 9.1.11
So from what I understand, I need to specify which column that should be used whenever you use the GROUP_BY clause. I thought using SELECT would help, which can be annoying if you need to SELECT a lot of columns.
Interesting discussion here
Anyways, when I look at the error, everytime the cursor is pointed to updated_at. In the SQL query, rails will always ORDER BY updated_at. So I have tried this horrible query:
#commits.group("mission_id, date(updated_at)")
.select("date(updated_at), count(mission_id)")
.having("count(mission_id) > 0")
.order("count(mission_id)").length
which gives me the following SQL
SELECT date(updated_at), count(mission_id)
FROM "commits"
WHERE "commits"."user_id" = 1
GROUP BY mission_id, date(updated_at)
HAVING count(mission_id) > 0
ORDER BY updated_at DESC, count(mission_id)
LIMIT 25 OFFSET 0
the error is the same.
Note that no matter what it will ORDER BY updated_at, even if I wanted to order by something else.
Also I don't want to group the records by updated_at just by mission_id.
This PostgreSQL error is just misleading and has little explanation to solving it. I have tried many formulas from the stackoverflow sidebar, nothing works and always the same error.
UPDATE 2:
So I got it to work, but it needs to group the updated_at because of the automatic ORDER BY updated_at. How do I count only by mission_id?
#missions_commits = #commits.group("mission_id, updated_at").count("mission_id").size
I guest you want to show general number of distinct Missions related with Commits, anyway it won't be number on page.
Try this:
#commits = #user.commits.order("updated_at DESC").page(params[:page]).per(25)
#missions_commits = #user.commits.distinct.count(:mission_id)
However if you want to get the number of distinct Missions on page I suppose it should be:
#missions_commits = #commits.collect(&:mission_id).uniq.count
Update
In Rails 3, distinct did not exist, but pure SQL counting should be used this way:
#missions_commits = #user.commits.count(:mission_id, distinct: true)
See the docs for PostgreSQL GROUP BY here:
http://www.postgresql.org/docs/9.3/interactive/sql-select.html#SQL-GROUPBY
Basically, unlike Sqlite (and MySQL) postgres requires that any columns selected or ordered on must appear in an aggregate function or the group by clause.
If you think it through, you'll see that this actually makes sense. Sqlite/MySQL cheat under the hood and silently drop those fields (not sure that's technically what happens).
Or thinking about it another way if you are grouping by a field, what's the point of ordering it? How would that even make sense unless you also had an aggregate function on the ordered field?
Let's say I have an ActiveRecord query which has 1 million records as result and I'm using WillPaginate to (of course) paginate this result.
So, my doubt is:
All these 1 million records are stored somehow in memory or the will_paginate query gets executed again to retrieve more and more records ?
I hope I'm being clear :)
No, it won't repeatedly grab all 1 million records. The queries look something like this:
SELECT ... FROM ... LIMIT X OFFSET Y
...where X is the maximum per page, and Y is the number of records to skip from the top of the stack, as determined by X * (current_page - 1).
Will_paginate will run separate queries to populate the data and will not fetch all the data at once. You can check how will paginate works in the terminal also. Lets say you are using rails default webrick server. Then in your terminal you will see the sql queries made by will_paginate like so:
Processing by InvoicesController#index as HTML
Invoice Load (0.1ms) SELECT "invoices".* FROM "invoices" ORDER BY id DESC LIMIT 5 OFFSET 0
I have a table with paginated data and this is the way I select data for each page:
#visitors = EventsVisitor
.select('visitors.*, events_visitors.checked_in, events_visitors.checkin_date, events_visitors.source, events_visitors.id AS ticket_id')
.joins(:visitor)
.order(order)
.where(:event_id => params[:event_id])
.where(filter_search)
.where(mode)
.limit(limit)
.offset(offset)
Also to build table pagination I need to know total count of records. Currently my solution for this is very rough:
total = EventsVisitor
.select('count(*) as count, events_visitors.*')
.joins(:visitor)
.order(order)
.where(:event_id => params[:event_id])
.where(filter_search)
.where(mode)
.first()
.count
So my question is as follows - What is the optimal ruby way to select limited data for the current page and total count of records?
I noticed that if I do #visitors.count - additional sql query will be generated:
SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM `events_visitors` INNER JOIN `visitors` ON `visitors`.`id` = `events_visitors`.`visitor_id` WHERE `events_visitors`.`event_id` = 1 LIMIT 15 OFFSET 0) subquery_for_count
First of all, I do not understand what is the reason to send an additional query to get a count of data that we already have, I mean that after we got data from database in #visitors we can count it with ruby without need to send additional queries to DB.
Second - I thought that maybe there are some ways to use something like .total_count that will generate similar count(*) query but without that useless limit/offset?
you should except limit and offset
http://guides.rubyonrails.org/active_record_querying.html#except .
See how kaminari does it
https://github.com/kaminari/kaminari/blob/92052eedf047d65df71cc0021a9df9df1e2fc36e/lib/kaminari/models/active_record_relation_methods.rb#L11
So it might be something like
total = #visitors.except(:offset, :limit, :order).count