Rails changing query execution order - sql

I want to perform a query on first 10 records.
So, from Rails console I type:
Log.all.limit(10).where({"username"=>"peeyush"}).explain
This gives:
Log Load (0.8ms) SELECT "logs".* FROM "logs" WHERE "logs"."username" = 'peeyush' LIMIT 10
Clearly, LIMIT 10 happens later.
I try running:
Log.all.first(10).where({"username"=>"peeyush"}).explain
But this gives an error:
NoMethodError: undefined method `where' for #<Array:0x0000000539acd8>
How should the query be performed?

If I understand you correctly, you want to retrieve the 1st 10 rows and then filter those 10 records by username?
Filter in ruby
all.first(10).find_all {|i| i.username == "peeyush" }
Filter on the database
all.where(:id => all.first(10).map {|x| x.id}, :username => "peeyush")

Related

How to query the last N Parent rows with that had the most recent children created? in ruby

I have 2 models, Device and HealthRecord with the basic relationship of:
device has_many health_records
health_records belong_to device.
I want to get the LAST 10 devices that has the most recently created health_records.
I can get it using this, but this gets ALL of the records:
Device
.select("devices.id, MAX(health_records.id) AS latest_health_id")
.joins(:health_records)
.group("devices.id")
.order("latest_health_id DESC")
If I add .limit(10), it just gives me a Device::ActiveRecord_Relation which i cannot inspect. (when i inspect it says Invalid column name 'latest_health_id'.). Adding .first(10) does not work too.
As per the description shared below mentioned query will fetch devices of last 10 created health records.
Device
.select("devices.id")
.joins(:health_records)
.group("devices.id")
.order("health_records.created_at DESC").limit(10)
I think you can achieve your goal with the following query:
Device
.joins(:health_records)
.order('health_records.created_at DESC')
.group(:id)
.distinct
.limit(10)
This will return Device::ActiveRecord_Relation object. If you want only ids, just add pluck(:id) at the end, which will change your query from SELECT DISTINCT "devices".* to SELECT DISTINCT "devices"."id" and return Array of ids.
Here is another solution that should probably work for you (albeit untested):
Device.where(id: HealthRecord.select(:device_id)
.group(:device_id)
.order("MAX(health_records.id) DESC")
.limit(10)
)
This should result in a query similar to
SELECT
devices.*
FROM
devices
WHERE
id IN (
SELECT
health_records.device_id
FROM
health_records
GROUP BY
health_records.device_id
ORDER BY
MAX(health_records.id) DESC
LIMIT 10 OFFSET 0
)
For now I ended up using either of this two:
Device
.select("devices.id, MAX(health_records.id) AS latest_health_id")
.joins(:health_records)
.group("devices.id")
.order("latest_health_id DESC")
.map(&:id).first(10)
Device
.joins(:health_records)
.order('health_records.created_at DESC')
.pluck(:id).uniq.first(10)
it gives me an array of the last 10 device ids that most recently created a health_record

Re-writing a SQL Query using Active Record or the Squeel Gem in Ruby on Rails 4

I am having a lot of problems rewriting this SQL Query in Squeel or straight Active Record using Ruby on Rails 4.
All 3 numbers in the query need to be passed to the query before execution. The SQL Query is below.
SELECT "users".*
FROM "users"
WHERE "users"."id"
IN (SELECT "users"."id" FROM "users"
INNER JOIN marketing_assets ON users.id = marketing_assets.user_id
WHERE marketing_assets.marketing_platform_id= 3
GROUP BY users.id HAVING COUNT(DISTINCT marketing_assets.marketing_platform_id) = 1)
You can find out more detail about how it is being used at Error when trying to chain class method in controller in Ruby on Rails
marketing_assets_id = 3
limit_marketing_assets = 1
User.joins(:marketing_assets).where(:marketing_assets => {:marketing_platform_id => marketing_assets_id}).group(:users => :id).having("COUNT(DISTINCT marketing_assets.marketing_platform_id) = #{limit_marketing_assets}")
Here is the query I ended up writing using Squeel. It works for me.
User.where{users.id.in(User.joins{marketing_assets}
.where((['marketing_assets.marketing_platform_id = ?' ] * platforms.count)
.join(' OR '), *platforms).group{users.id}
.having{{marketing_assets =>
{COUNT(DISTINCT(marketing_assets.marketing_platform_id)) => platforms.count }}})}

Simple group by with thinking sphinx

I have an Item model with a name and user_id. I would like to search all Items and group by User so I can display each user with their items:
User 1:
Item A
Item B
User 2
Item C
User 3
Item D
Item E
Item D
...
In the console, I try this: (From the documentation)
Item.search({group_by: :user_id, limit: 50}).all
And I get this:
Sphinx Query (0.4ms)
Sphinx Caught Sphinx exception: can't dup Symbol (0 tries left)
TypeError: can't dup Symbol
from /Users/pinouchon/.rvm/gems/ruby-1.9.3-p392#gemset/gems/riddle-1.5.6/lib/riddle/client/message.rb:18:in `dup'
Same error with this:
Item.search({group_by: :user_id, order_group_by: '#count desc'}).each_with_group
Search with no group by returns results without any problem.
What's wrong ?
The quick answer: try sending through the attribute name as a string, not a symbol.
The longer answer: that query isn't going to give you the results you want – it'll return one item per user. You'd be better served sorting by user_id instead:
items = Item.search(
:order => 'user_id ASC, #weight DESC',
:sort_mode => :extended,
:limit => 50
)
From there, you could then get the layer of users grouping each items using Ruby/Rails:
items.group_by(&:user)

Rails Rest Service GET

I have set up a Rails REST Service and I am having a problem showing a single record. Here is the URL that I am trying to hit:
http://website:3000/users/2/timesheets/21
Controller code:
def show
puts "SHOW"
puts params.inspect
#timesheets = User.find(params[:user_id]).timesheets(params[:id])
respond_to do |format|
format.json { render json: #timesheets }
end
end
I know the params are getting to the controller, but it is not using the :timesheet_id. Here is the console output to show what I mean:
Started GET "/users/2/timesheets/21" for **.**.***.** at 2013-03-19 06:12:11 -0400
Processing by TimesheetsController#show as */*
Parameters: {"user_id"=>"2", "id"=>"21"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", "2"]]
Timesheet Load (0.5ms) SELECT "timesheets".* FROM "timesheets" WHERE "timesheets"."user_id" = 2
Completed 200 OK in 120ms (Views: 36.5ms | ActiveRecord: 2.9ms)
I see the timesheet id of 21 in the parameters hash. A query is then made to get the user, but then all of the timesheets for that user are grabbed. Any help would be appreciated. Thanks.
What Prakash suggests works, but executes two queries: one to get the user and one the get the timesheet. There does not seem to be any reason to do User.find(...) first. Might as well query the timesheets table only, which will only execute one query and is thus faster:
#timesheet = Timesheet.where('user_id = ? and id = ?', params[:user_id], params[:id]).first
Try this:
#user = User.find(params[:user_id])
#timesheet = #user.timesheets.find(params[:id])
This should run a query as follows:
SELECT "timesheets".* FROM "timesheets" WHERE "timesheets"."id" = 21 AND ("timesheets"."user_id" = 2) LIMIT 1
The corresponding view should be referring to #timesheet variable and not #timesheets.

How do I create this active_records query

I have a user model and a story model. Users have many stories.
I want to create a scope that returns the twenty-five users records for users who have created the most stories today, along with the amount of stories that they have created.
I know that there are people on SO that are great with active_records queries. I also know that I am not one of those guys:-(. Help would be greatly appreciated and readily accepted!
#UPDATE with the solution
I've been working with #MrYoshiji's suggestion, and here is what i came up with (note, I'm using this query in my active_admin dashboard):
panel "Today's Top Posters" do
time_range = Date.today.beginning_of_day..Date.today.end_of_day
table_for User.joins(:stories)
.select('users.username, count(stories.*) as story_count')
.group('users.id')
.where(:stories => {:created_at => time_range})
.order('story_count DESC')
.limit(25) do
column :username
column "story_count"
end
end
And low and behold, it works!!!!
Note, when I tried a simplified version of MrYoshiji's suggestion:
User.includes(:stories)
.select('users.username, count(stories.*) as story_count')
.order('story_count DESC') #with or without the group statement
.limit(25)
I got the following error:
> User.includes(:stories).select('users.username, count(stories.*) as story_count').order('story_count DESC').limit(25)
User Load (1.6ms) SELECT DISTINCT "users".id, story_count AS alias_0 FROM "users" LEFT OUTER JOIN "stories" ON "stories"."user_id" = "users"."id" ORDER BY story_count DESC LIMIT 25
ActiveRecord::StatementInvalid: PG::Error: ERROR: column "story_count" does not exist
LINE 1: SELECT DISTINCT "users".id, story_count AS alias_0 FROM "us...
It seems like includes don't like any aliasing.
I can't test this right now, running under Windows... Can you try it?
User.includes(:stories)
.select('users.*, count(stories.*) as story_count')
.group('users.id')
.order('story_count DESC')
.where('stories.created_at BETWEEN ? AND ?', Date.today.beginning_of_day, Day.today.end_of_day)
.limit(25)
Using .includes, as suggested by MrYshiji, did not work due to aliasing problems (see the original question for more info on this). Instead, I used .joins as follows to get the query results that I wanted (note, this query is inside my active_admin dashboard):
panel "Today's Top Posters" do
time_range = Date.today.beginning_of_day..Date.today.end_of_day
table_for User.joins(:stories)
.select('users.username, count(stories.*) as story_count')
.group('users.id')
.where(:stories => {:created_at => time_range})
.order('story_count DESC')
.limit(25) do
column :username
column "story_count"
end
end