I would like to convert this query using symbols and table_name cause my DB isn't in rails standard.
SELECT v.taux, sum(v.basevat) as tax, sum(v.valuevat) as amountvat, count(t.id)
FROM vatsomething as v
INNER JOIN somethingreceipt as t
ON v.uid_receipt = t.uid
WHERE t.cancelled = 0
GROUP BY v.taux
ORDER BY v.taux ASC;
class Vat < ActiveRecord::Base
self.table_name = "vatsomething"
alias_attribute :base, :basevat
alias_attribute :value, :valuevat
alias_attribute :rate, :taux
belongs_to :receipt, foreign_key: 'uid_receipt', primary_key: 'uid'
end
class Receipt < ActiveRecord::Base
self.table_name = "somethingreceipt"
has_many :item, foreign_key: 'uid_receipt', primary_key: 'uid'
end
I don't have the choice, if I divide this query to 4 queries, it's too slow.
I tried some queries :
Vat.joints(:receipt).
select(:rate, sum(:base), sum(:value), count(Receipt.table_name.:id) ).
where(Receipt.table_name => hash_of_conds.rejectblank)
I've tried quickly with pluck but I don't know if I can use symbols.
I understand that my query is really hard and the non standard db doesn't help.
Maybe, I'll have to use const_get...
Can you help me ?
Thank you.
Unfortunately you cannot use the more railsy methods very easily with legacy databases in rails. Your queries will have to end up being more SQL for this to function correctly. (I have run into this a lot with people that want to keep their DB but run it on rails)
Have you tried this
Vat.joins(:receipt).where("somethingreceipt.cancelled = 0").
group("vatsomething.taux").
select("vatsomething.taux as rate,
sum(vatsomething.basevat) as tax,
sum(vatsomething.valuevat) as amountvat,
count(somethingreceipt.id) as receipt_count").
order("vatsomething.taux")
This should return your desired result.
You have to remember that aliasing methods does not alter the names in the table when running queries you will still have to use the legacy table names and column names.
You can then access the attributes of each object in the ActiveRecord::Relation though their as names e.g. (#rate,#tax,#amountvat,#receipt_count)
Scoping option great for legacy DB's to keep your specific queries consolidated inside the model making it easier to make changes without having to find these queries all over the application.
class Vat
scope :receipt_summary, ->(conditions){
joins(:receipt).where(conditions).
group("vatsomething.taux").
select("vatsomething.taux as rate,
sum(vatsomething.basevat) as tax,
sum(vatsomething.valuevat) as amountvat,
count(somethingreceipt.id) as receipt_count").
order("vatsomething.taux")
}
end
Then you can call Vat.receipt_summary("somethingreceipt.cancelled = 0") or even should be able to call Vat.receipt_summary({receipt:{cancelled:0}})
Related
I have 2 models:
DeliverySlot has_many :orders
Order belongs_to :delivery_slot
Delivery Slots have a limit of how many orders they can hold. I want to create a scope to give all the available delivery slots. An available delivery slot is one that hasn't reached it's limit of associated orders.
My attempt looks like:
scope :available, where("limit > ?", order.count).joins(:orders)
order.count is pseudocode above.
To do this like you have setup you would need to use orders.count instead of order.count because you're referring to the association. This would prompt ActiveRecord to assemble a query that looks something like SELECT COUNT(*) FROM orders WHERE delivery_slot_id = 1.
Rails is actually smart enough to then use that as a subquery in your where condition when you pass it appropriately, a la where('limit > ', orders.count). But as you might see, this won't work if it's precompiled because the query uses an explicit ID in the condition.
What you need instead is to count orders with an ambiguous condition, then use it as a subquery: where('limit > ?', Order.where(delivery_slot_id: 'delivery_slots.id').count). If you tried to run the query for the order count on its own it would fail on delivery_slots, but because it's in the subquery here you should be smooth sailing.
I'd like to propose another way of doing this altogether though, using a counter cache:
class AddCounterCacheToDeliverySlots < ActiveRecord::Migration
class DeliverySlot < ActiveRecord::Base; end
def change
add_column :delivery_slots, :orders_count, :integer, default: 0
add_index :delivery_slots, [:orders_count, :limit]
DeliverySlot.reset_column_information
DeliverySlot.find_each do |delivery_slot|
DeliverySlot.update_counters delivery_slot.id, orders_count: delivery_slot.orders.count
end
end
end
class Order < ActiveRecord::Base
belongs_to :delivery_slot, counter_cache: true
end
class DeliverySlot < ActiveRecord::Base
has_many orders
scope :available, where('orders_count < limit')
end
Rails will automatically increment and decrement the orders_count column for each DeliverySlot, and because it's indexed, it's ridiculously fast to query.
scope :available, lambda {
|delivery_slot| joins(:orders).
where("limit > ?", order.count)
}
try this
So I found a way to do it in SQL. If anyone knows of a more ruby way without creating loads of database queries please jump in.
scope :available, joins('LEFT JOIN orders
ON orders.delivery_slot_id = delivery_slots.id')
.where("delivery_slots.limit > (
SELECT COUNT(*) FROM orders
WHERE orders.delivery_slot_id = delivery_slots.id )
")
Really been struggling trying to get a group by to work when I have to join to another table. I can get the group by to work when I don't join, but when I want to group by a column on the other table I start having problems.
Tables:
Book
id, category_id
Category
id, name
ActiveRecord schema:
class Category < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :category
end
I am trying to get a group by on a count of categories. I.E. I want to know how many books are in each category.
I have tried numerous things, here is the latest,
books = Book.joins(:category).where(:select => 'count(books.id), Category.name', :group => 'Category.name')
I am looking to get something back like
[{:name => fiction, :count => 12}, {:name => non-fiction, :count => 4}]
Any ideas?
Thanks in advance!
How about this:
Category.joins(:books).group("categories.id").count
It should return an array of key/value pairs, where the key represents the category id, and the value represents the count of books associated with that category.
If you're just after the count of books in each category, the association methods you get from the has_many association may be enough (check out the Association Basics guide). You can get the number of books that belong to a particular category using
#category.books.size
If you wanted to build the array you described, you could build it yourself with something like:
array = Categories.all.map { |cat| { name: cat.name, count: cat.books.size } }
As an extra point, if you're likely to be looking up the number of books in a category frequently, you may also want to consider using a counter cache so getting the count of books in a category doesn't require an additional trip to the database. To do that, you'd need to make the following change in your books model:
# books.rb
belongs_to :category, counter_cache: true
And create a migration to add and initialize the column to be used by the counter cache:
class AddBooksCountToCategories < ActiveRecord::Migration
def change
add_column :categories, :books_count, :integer, default: 0, null: false
Category.all.each do |cat|
Category.reset_counters(cat.id, :books)
end
end
end
EDIT: After some experimentation, the following should give you close to what you want:
counts = Category.joins(:books).count(group: 'categories.name')
That will return a hash with the category name as keys and the counts as values. You could use .map { |k, v| { name: k, count: v } } to then get it to exactly the format you specified in your question.
I would keep an eye on something like that though -- once you have a large enough number of books, the join could slow things down somewhat. Using counter_cache will always be the most performant, and for a large enough number of books eager loading with two separate queries may also give you better performance (which was the reason eager loading using includes changed from using a joins to multiple queries in Rails 2.1).
I have a model called websites that has_many :likes, and of course another model called likes with belongs_to :website. I want to get an array of all the websites but order them by the number of likes a website has.
helpful info:
I have it set up so #website.likes will return the likes from #website. (#website.likes.count is the number of likes a website has)
I want to get an array of all the websites but order them by the number of likes a website has.
As others have posted, you can do a join onto likes and then order by the count. Performance may be a bit iffy depending on indexing etc. You'll have slightly different syntax depending on if you're running Rails 2 or 3.
An alternative would be to maintain a denormalised likes_count column on websites which is updated when a Like model object is saved.
Then you just need to query on Website and specify an order likes_count descending (and is easily indexed).
To do this, create a likes_count integer column on the websites and specify the :counter_cache option on the belongs_to declaration in the Likes model. e.g:
class Likes
belongs_to :website, :counter_cache => true
end
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html for more info
This query should give you what you need:
all(:select => 'websites.*, count(*) as count',
:joins => :likes,
:group => :websites,
:order => 'count DESC')
Something along the lines of:
SELECT Websites.*, COUNT(Like.ID) AS Liked
FROM websites
LEFT OUTER JOIN Like ON websites.ID = Like.website_id
GROUP BY Like.website_id ORDER BY Liked DESC
I have the following models. Users have UserActions, and one possible UserAction can be a ContactAction (UserAction is a polymorphism). There are other actions like LoginAction etc. So
class User < AR::Base
has_many :contact_requests, :class_name => "ContactAction"
has_many :user_actions
has_many_polymorphs :user_actionables, :from => [:contact_actions, ...], :through => :user_actions
end
class UserAction < AR::Base
belongs_to :user
belongs_to :user_actionable, :polymorphic => true
end
class ContactAction < AR::Base
belongs_to :user
named_scope :pending, ...
named_scope :active, ...
end
The idea is that a ContactAction joins two users (with other consequences within the app) and always has a receiving and a sending end. At the same time, a ContactAction can have different states, e.g. expired, pending, etc.
I can say #user.contact_actions.pending or #user.contact_requests.expired to list all pending / expired requests a user has sent or received. This works fine.
What I would now like is a way to join both types of ContactAction. I.e. #user.contact_actions_or_requests. I tried the following:
class User
def contact_actions_or_requests
self.contact_actions + self.contact_requests
end
# or
has_many :contact_actions_or_requests, :finder_sql => ..., :counter_sql => ...
end
but all of these have the problem that it is not possible to use additional finders or named_scopes on top of the association, e.g. #user.contact_actions_or_requests.find(...) or #user.contact_actions_or_requests.expired.
Basically, I need a way to express a 1:n association which has two different paths. One is User -> ContactAction.user_id, the other is User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id. And then join the results (ContactActions) in one single list for further processing with named_scopes and/or finders.
Since I need this association in literally dozens of places, it would be a major hassle to write (and maintain!) custom SQL for every case.
I would prefer to solve this in Rails, but I am also open to other suggestions (e.g. a PostgreSQL 8.3 procedure or something simliar). The important thing is that in the end, I can use Rails's convenience functions like with any other association, and more importantly, also nest them.
Any ideas would be very much appreciated.
Thank you!
To provide a sort-of answer to my own question:
I will probably solve this using a database view and add appropriate associations as needed. For the above, I can
use the SQL in finder_sql to create the view,
name it "contact_actions_or_requests",
modify the SELECT clause to add a user_id column,
add a app/models/ContactActionsOrRequests.rb,
and then add "has_many :contact_actions_or_requests" to user.rb.
I don't know how I'll handle updating records yet - this seems not to be possible with a view - but maybe this is a first start.
The method you are looking for is merge. If you have two ActiveRecord::Relations, r1 and r2, you can call r1.merge(r2) to get a new ActiveRecord::Relation object that combines the two.
If this will work for you depends largely on how your scopes are set up and if you can change them to produce a meaningful result. Let's look at a few examples:
Suppose you have a Page model. It has the normal created_at and updated_at attributes, so we could have scopes like:
:updated -> { where('created_at != updated_at') }
:not_updated -> { where('created_at = updated_at') }
If you pull this out of the database you'll get:
r1 = Page.updated # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at)
r2 = Page.not_updated # SELECT `pages`.* FROM `pages` WHERE (created_at = updated_at)
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) AND (created_at = updated_at)
=> []
So it did combine the two relations, but not in a meaningful way. Another one:
r1 = Page.where( :name => "Test1" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test1'
r2 = Page.where( :name => "Test2" ) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2'
So, it might work for you, but maybe not, depending on your situation.
Another, and recommended, way of doing this is to create a new scope on you model:
class ContactAction < AR::Base
belongs_to :user
scope :pending, ...
scope :active, ...
scope :actions_and_requests, pending.active # Combine existing logic
scope :actions_and_requests, -> { ... } # Or, write a new scope with custom logic
end
That combines the different traits you want to collect in one query ...
How can I write an AR find query to have the results ordered by the number of records in a has_many association?
class User < ActiveRecord::Base
has_many :photos
end
I want to do something like...
User.find(:all, :order => photos.count)
I realize my find is not valid code. Say I have the following data.
User 1, which has 3 photos
User 2, which has 5 photos
User 3, which has 2 photos
I want my find to bring me back the users in the order of...
User 2,
User 1,
User 3
based on the count of of the users photos
The easiest way to achieve this is probably to add a counter cache to that model and then sort by that column.
class Photo < ActiveRecord::Base
belongs_to :user, :counter_cache => true
end
And be sure to add a column to your users table called photos_count.
Then you will be able to...
User.find(:all, :order => 'photos_count')
If you don't want an extra column, you could always ask for an extra column in the returned result set:
User.all(:select => "#{User.table_name}.*, COUNT(#{Photo.table_name}.id) number_of_photos",
:joins => :photos,
:order => "number_of_photos")
This generates the following SQL:
SELECT users.*, COUNT(photos.id) number_of_photos
FROM `users` INNER JOIN `photos` ON photos.user_id = users.id
ORDER BY number_of_photos
If you don't want to add a counter cache column, your only option is to sort after the find. If you :include the association in your find, you won't incur any additional database work.
users = User.find(:all, :include => :photos).sort_by { |u| -u.photos.size }
Note the negative sign in the sort_by block to sort from high to low.
I would advise you not to write direct SQL, since implementations of it may vary from store to store. Fortunately, you have arel:
User.joins(:photos).group(Photo.arel_table[:user_id]).
order(Photo.arel_table[:user_id].count)
Counter cache will help, but you'll need an extra column in the db.
I'd add this as a comment on the top answer, but I can't for some reason. According to this post:
http://m.onkey.org/active-record-query-interface
The User.all(options) method will be deprecated after Rails 3.0.3, and replaced with a bunch of other (handy, chainable) active record type stuff, but it makes it very hard to figure out how to put together the same kind of query.
As a result, I've gone ahead and implemented the counter cache method. This was pretty easy and painless with the exception that you need to remember to update the column information in your migration, otherwise all existing records will have "0."
Here's what I used in my migration:
class AddUserCountToCollections < ActiveRecord::Migration
def self.up
add_column :collections, :collectionusers_count, :integer, :default => 0
Collection.reset_column_information
Collection.all.each do |c|
Collection.update_counters c.id, :collectionusers_count => c.collectionusers.count
end
end
def self.down
remove_column :collections, :collectionusers_count
end
end
In theory this should be faster, too. Hope that's helpful going forward.
Your question doesn't make sense. The :order parameter specifies a column name and an optional ordering direction i.e. asc(ending) or desc(ending).
What is the result that you're trying to achieve?