I am developing a Rails app.
after I execute a sql command like following:
sql="select * from some_tbl;"
rslt = ActiveRecord::Base.connection.execute(sql)
how can I get sliced results from rslt?
I mean for example, if the rslt.size is 200, I would like to get 20 results start from the 5th one (which like the array operation arr[5,20]), how to do it?
Try to convert rslt to Array:
...
rslt.to_a[5,20]
If you don't need to fetch all the instances of the table, you can do:
Model.offset(5).limit(20)
You should read that, it will probably help: http://guides.rubyonrails.org/active_record_querying.html#limit-and-offset
The object returned from ActiveRecord::Base.connection.execute is in fact an Array which means you can use rslt[5,20] to obtain the 20 results starting at the 6th element.
I'd also like to point out that you can use find_in_batches which is an API provided by ActiveRecord.
The example given on the API page is:
Person.where("age > 21").find_in_batches do |group|
sleep(50) # Make sure it doesn't get too crowded in there!
group.each { |person| person.party_all_night! }
end
EDIT: sorry it didn't work for you. direct from my rails (3.0.10) console
ruby-1.8.7-p299 :006 > sql = "select * from domains"
=> "select * from domains"
ruby-1.8.7-p299 :007 > ActiveRecord::Base.connection.execute(sql).class
=> Array
ruby-1.8.7-p299 :008 > ActiveRecord::Base.connection.execute(sql).length
=> 18
ruby-1.8.7-p299 :010 > ActiveRecord::Base.connection.execute(sql)[5,20].length
=> 13
You dont need to use sql in rails you use ActiveRecord to create, read, update and delete entries in the database. You should read the beginners guides from http://guides.rubyonrails.org/ especially the Model section will be interresting for you because its about dealing with the datbase!
Related
I need to query comments made in one day. The field is part of the standard timestamps, is created_at. The selected date is coming from a date_select.
How can I use ActiveRecord to do that?
I need something like:
"SELECT * FROM comments WHERE created_at BETWEEN '2010-02-03 00:00:00' AND '2010-02-03 23:59:59'"
Just a note that the currently accepted answer is deprecated in Rails 3. You should do this instead:
Comment.where(:created_at => #selected_date.beginning_of_day..#selected_date.end_of_day)
Or, if you want to or have to use pure string conditions, you can do:
Comment.where('created_at BETWEEN ? AND ?', #selected_date.beginning_of_day, #selected_date.end_of_day)
I would personally created a scope to make it more readable and re-usable:
In you Comment.rb, you can define a scope:
scope :created_between, lambda {|start_date, end_date| where("created_at >= ? AND created_at <= ?", start_date, end_date )}
Then to query created between:
#comment.created_between(1.year.ago, Time.now)
Hope it helps.
Rails 5.1 introduced a new date helper method all_day, see: https://github.com/rails/rails/pull/24930
>> Date.today.all_day
=> Wed, 26 Jul 2017 00:00:00 UTC +00:00..Wed, 26 Jul 2017 23:59:59 UTC +00:00
If you are using Rails 5.1, the query would look like:
Comment.where(created_at: #selected_date.all_day)
This code should work for you:
Comment.find(:all, :conditions => {:created_at => #selected_date.beginning_of_day..#selected_date.end_of_day})
For more info have a look at Time calculations
Note: This code is deprecated. Use the code from the answer if you are using Rails 3.1/3.2
I ran this code to see if the checked answer worked, and had to try swapping around the dates to get it right. This worked--
Day.where(:reference_date => 3.months.ago..Time.now).count
#=> 721
If you're thinking the output should have been 36, consider this, Sir, how many days is 3 days to 3 people?
Comment.find(:all, :conditions =>["date(created_at) BETWEEN ? AND ? ", '2011-11-01','2011-11-15'])
I have been using the 3 dots, instead of 2. Three dots gives you a range that is open at the beginning and closed at the end, so if you do 2 queries for subsequent ranges, you can't get the same row back in both.
2.2.2 :003 > Comment.where(updated_at: 2.days.ago.beginning_of_day..1.day.ago.beginning_of_day)
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE ("comments"."updated_at" BETWEEN '2015-07-12 00:00:00.000000' AND '2015-07-13 00:00:00.000000')
=> #<ActiveRecord::Relation []>
2.2.2 :004 > Comment.where(updated_at: 2.days.ago.beginning_of_day...1.day.ago.beginning_of_day)
Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE ("comments"."updated_at" >= '2015-07-12 00:00:00.000000' AND "comments"."updated_at" < '2015-07-13 00:00:00.000000')
=> #<ActiveRecord::Relation []>
And, yes, always nice to use a scope!
If you only want to get one day it would be easier this way:
Comment.all(:conditions => ["date(created_at) = ?", some_date])
there are several ways. You can use this method:
start = #selected_date.beginning_of_day
end = #selected_date.end_of_day
#comments = Comment.where("DATE(created_at) BETWEEN ? AND ?", start, end)
Or this:
#comments = Comment.where(:created_at => #selected_date.beginning_of_day..#selected_date.end_of_day)
There should be a default active record behavior on this I reckon. Querying dates is hard, especially when timezones are involved.
Anyway, I use:
scope :between, ->(start_date=nil, end_date=nil) {
if start_date && end_date
where("#{self.table_name}.created_at BETWEEN :start AND :end", start: start_date.beginning_of_day, end: end_date.end_of_day)
elsif start_date
where("#{self.table_name}.created_at >= ?", start_date.beginning_of_day)
elsif end_date
where("#{self.table_name}.created_at <= ?", end_date.end_of_day)
else
all
end
}
You could use below gem to find the records between dates,
This gem quite easy to use and more clear By star am using this gem and the API more clear and documentation also well explained.
Post.between_times(Time.zone.now - 3.hours, # all posts in last 3 hours
Time.zone.now)
Here you could pass our field also Post.by_month("January", field: :updated_at)
Please see the documentation and try it.
I want to pull 5 records at a time into the view (I'm using rails 2.3.8).
I am using will_paginate, and it's working great but:
I want to send those results to the view, 5 records at a time, then add a div, then loop through until they're all shown.
I tried using find_in_batches, but I don't know how to access the objects it's returning. Can I use #{}?
I know there's .first and .last methods but is there any such thing as .second, .third, .fourth, etc?
You have an example for find_in_batches in the documentation:
Person.find_in_batches(:conditions => "age > 21", :batch_size => 5) do |group|
sleep(50) # Make sure it doesn't get too crowded in there!
group.each { |person| person.party_all_night! }
end
This may not be the solution you're looking for, though. If you have 5000 records, this will issue 1000 database queries.
A better solution would be using each_slice, this method will fetch all records in one database query and split the result set afterwards:
Person.find(:all, :conditions => "age > 21").each_slice(5) do |group|
group.each { |person| person.party_all_night! }
end
How do you do an OR query in Rails 3 ActiveRecord. All the examples I find just have AND queries.
Edit: OR method is available since Rails 5. See ActiveRecord::QueryMethods
If you want to use an OR operator on one column's value, you can pass an array to .where and ActiveRecord will use IN(value,other_value):
Model.where(:column => ["value", "other_value"]
outputs:
SELECT `table_name`.* FROM `table_name` WHERE `table_name`.`column` IN ('value', 'other_value')
This should achieve the equivalent of an OR on a single column
in Rails 3, it should be
Model.where("column = ? or other_column = ?", value, other_value)
This also includes raw sql but I dont think there is a way in ActiveRecord to do OR operation. Your question is not a noob question.
Rails 5 added or, so this is easier now in an app with Rails version greater than 5:
Model.where(column: value).or(Model.where(other_column: other_value)
this handles nil values as well
Use ARel
t = Post.arel_table
results = Post.where(
t[:author].eq("Someone").
or(t[:title].matches("%something%"))
)
The resulting SQL:
ree-1.8.7-2010.02 > puts Post.where(t[:author].eq("Someone").or(t[:title].matches("%something%"))).to_sql
SELECT "posts".* FROM "posts" WHERE (("posts"."author" = 'Someone' OR "posts"."title" LIKE '%something%'))
An updated version of Rails/ActiveRecord may support this syntax natively. It would look similar to:
Foo.where(foo: 'bar').or.where(bar: 'bar')
As noted in this pull request https://github.com/rails/rails/pull/9052
For now, simply sticking with the following works great:
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Update: According to https://github.com/rails/rails/pull/16052 the or feature will be available in Rails 5
Update: Feature has been merged to Rails 5 branch
Rails has recently added this into ActiveRecord. It looks to be released in Rails 5. Committed to master already:
https://github.com/rails/rails/commit/9e42cf019f2417473e7dcbfcb885709fa2709f89
Post.where(column: 'something').or(Post.where(other: 'else'))
# => SELECT * FROM posts WHERE (column = 'something') OR (other = 'else)
Rails 5 comes with an or method. (link to documentation)
This method accepts an ActiveRecord::Relation object. eg:
User.where(first_name: 'James').or(User.where(last_name: 'Scott'))
If you want to use arrays as arguments, the following code works in Rails 4:
query = Order.where(uuid: uuids, id: ids)
Order.where(query.where_values.map(&:to_sql).join(" OR "))
#=> Order Load (0.7ms) SELECT "orders".* FROM "orders" WHERE ("orders"."uuid" IN ('5459eed8350e1b472bfee48375034103', '21313213jkads', '43ujrefdk2384us') OR "orders"."id" IN (2, 3, 4))
More information: OR queries with arrays as arguments in Rails 4.
The MetaWhere plugin is completely amazing.
Easily mix OR's and AND's, join conditions on any association, and even specify OUTER JOIN's!
Post.where({sharing_level: Post::Sharing[:everyone]} | ({sharing_level: Post::Sharing[:friends]} & {user: {followers: current_user} }).joins(:user.outer => :followers.outer}
Just add an OR in the conditions
Model.find(:all, :conditions => ["column = ? OR other_column = ?",value, other_value])
With rails + arel, a more clear way:
# Table name: messages
#
# sender_id: integer
# recipient_id: integer
# content: text
class Message < ActiveRecord::Base
scope :by_participant, ->(user_id) do
left = arel_table[:sender_id].eq(user_id)
right = arel_table[:recipient_id].eq(user_id)
where(Arel::Nodes::Or.new(left, right))
end
end
Produces:
$ Message.by_participant(User.first.id).to_sql
=> SELECT `messages`.*
FROM `messages`
WHERE `messages`.`sender_id` = 1
OR `messages`.`recipient_id` = 1
You could do it like:
Person.where("name = ? OR age = ?", 'Pearl', 24)
or more elegant, install rails_or gem and do it like:
Person.where(:name => 'Pearl').or(:age => 24)
I just extracted this plugin from client work that lets you combine scopes with .or., ex. Post.published.or.authored_by(current_user). Squeel (newer implementation of MetaSearch) is also great, but doesn't let you OR scopes, so query logic can get a bit redundant.
I'd like to add this is a solution to search multiple attributes of an ActiveRecord. Since
.where(A: param[:A], B: param[:B])
will search for A and B.
Using the activerecord_any_of gem, you can write
Book.where.any_of(Book.where(:author => 'Poe'), Book.where(:author => 'Hemingway')
I want to grab the most recent entry from a table. If I was just using sql, you could do
Select top 1 * from table ORDER BY EntryDate DESC
I'd like to know if there is a good active record way of doing this.
I could do something like:
table.find(:order => 'EntryDate DESC').first
But it seems like that would grab the entire result set, and then use ruby to select the first result. I'd like ActiveRecord to create sql that only brings across one result.
You need something like:
Model.first(:order => 'EntryDate DESC')
which is shorthand for
Model.find(:first, :order => 'EntryDate DESC')
Take a look at the documentation for first and find for details.
The Rails documentation seems to be pretty subjective in this instance. Note that .first is the same as find(:first, blah...)
From:http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263
"Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args)."
Digging into the ActiveRecord code, at line 1533 of base.rb (as of 9/5/2009), we find:
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
This calls find_every which has the following definition:
def find_every(options)
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
records = find_by_sql(construct_finder_sql(options))
if include_associations.any?
preload_associations(records, include_associations)
end
end
records.each { |record| record.readonly! } if options[:readonly]
records
end
Since it's doing a records.each, I'm not sure if the :limit is just limiting how many records it's returning after the query is run, but it sure looks that way (without digging any further on my own). Seems you should probably just use raw SQL if you're worried about the performance hit on this.
Could just use find_by_sql http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002267
table.find_by_sql "Select top 1 * from table ORDER BY EntryDate DESC"
With :limit in query, I will get first N records. What is the easiest way to get last N records?
This is the Rails 3 way
SomeModel.last(5) # last 5 records in ascending order
SomeModel.last(5).reverse # last 5 records in descending order
Updated Answer (2020)
You can get last N records simply by using last method:
Record.last(N)
Example:
User.last(5)
Returns 5 users in descending order by their id.
Deprecated (Old Answer)
An active record query like this I think would get you what you want ('Something' is the model name):
Something.find(:all, :order => "id desc", :limit => 5).reverse
edit: As noted in the comments, another way:
result = Something.find(:all, :order => "id desc", :limit => 5)
while !result.empty?
puts result.pop
end
new way to do it in rails 3.1 is SomeModel.limit(5).order('id desc')
For Rails 5 (and likely Rails 4)
Bad:
Something.last(5)
because:
Something.last(5).class
=> Array
so:
Something.last(50000).count
will likely blow up your memory or take forever.
Good approach:
Something.limit(5).order('id desc')
because:
Something.limit(5).order('id desc').class
=> Image::ActiveRecord_Relation
Something.limit(5).order('id desc').to_sql
=> "SELECT \"somethings\".* FROM \"somethings\" ORDER BY id desc LIMIT 5"
The latter is an unevaluated scope. You can chain it, or convert it to an array via .to_a. So:
Something.limit(50000).order('id desc').count
... takes a second.
For Rails 4 and above version:
You can try something like this If you want first oldest entry
YourModel.order(id: :asc).limit(5).each do |d|
You can try something like this if you want last latest entries..
YourModel.order(id: :desc).limit(5).each do |d|
Solution is here:
SomeModel.last(5).reverse
Since rails is lazy, it will eventually hit the database with SQL like: "SELECT table.* FROM table ORDER BY table.id DESC LIMIT 5".
If you need to set some ordering on results then use:
Model.order('name desc').limit(n) # n= number
if you do not need any ordering, and just need records saved in the table then use:
Model.last(n) # n= any number
In my rails (rails 4.2) project, I use
Model.last(10) # get the last 10 record order by id
and it works.
Just try:
Model.order("field_for_sort desc").limit(5)
we can use Model.last(5) or Model.limit(5).order(id: :desc) in rails 5.2
I find that this query is better/faster for using the "pluck" method, which I love:
Challenge.limit(5).order('id desc')
This gives an ActiveRecord as the output; so you can use .pluck on it like this:
Challenge.limit(5).order('id desc').pluck(:id)
which quickly gives the ids as an array while using optimal SQL code.
Let's say N = 5 and your model is Message, you can do something like this:
Message.order(id: :asc).from(Message.all.order(id: :desc).limit(5), :messages)
Look at the sql:
SELECT "messages".* FROM (
SELECT "messages".* FROM "messages" ORDER BY "messages"."created_at" DESC LIMIT 5
) messages ORDER BY "messages"."created_at" ASC
The key is the subselect. First we need to define what are the last messages we want and then we have to order them in ascending order.
If you have a default scope in your model that specifies an ascending order in Rails 3 you'll need to use reorder rather than order as specified by Arthur Neves above:
Something.limit(5).reorder('id desc')
or
Something.reorder('id desc').limit(5)
A simple answer would be:
Model.limit(5).order(id: :desc)
There is a problem with this solution, as id can't be the sole determiner of when a record was created in the time.
A more reliable solution would be:
Model.order(created_at: :desc).limit(5)
As others have pointed out, one can also use Model.last(5). The only gotcha with this is that it returns Array, and not Model::ActiveRecord_Relation.
Add an :order parameter to the query