I'm using SQLite3 and would like to get totals by month for a numeric field. Models look like:
# Table name: accounts
# id :integer not null, primary key
# created_at :datetime
# updated_at :datetime
class Account < ActiveRecord::Base
has_many :debitentries, :class_name => "Posting", :foreign_key => "debitaccount_id"
has_many :creditentries, :class_name => "Posting", :foreign_key => "creditaccount_id"
# Table name: postings
# id :integer not null, primary key
# voucherdate :date
# debitaccount_id :integer
# creditaccount_id :integer
# euroamount :decimal(, )
Class Postings < ActiveRecord::Base
belongs_to :debitaccount, :class_name => "Account", :foreign_key => "debitaccount_id"
belongs_to :creditaccount, :class_name => "Account", :foreign_key => "creditaccount_id"
My objective is to query the postings where the voucherdate < 12 month and get a total euroamount-sum of debitentries/creditentries for each account number, i.e.
Account.id| Feb 2012 | Jan 2012 | Dec 2011 | ... | Mar 2011
------------------------------------------------------------
1 | 233.87 | 123.72 | ... | | sum(euroamount)
2 | ... | | | |
I think I will need two queries (one for sume of debitentries and one for creditentries), but I think it will be much more efficient than using rails-functions. Can anyone help me how such a query would have to look like?
Many thanks in advance!
Here's SQL to get credit and debit sums by month for each account:
Select accounts.id,
strftime('%B %Y', voucherdate) month,
sum(credits.euroamount) total_credits,
sum(debits.euroamount) total_debits
from accounts
join postings as credits
on accounts.id = creditaccount_id
join postings as debits
on accounts.id = debitaccount_id
group by accounts.id, strftime('%B %Y', voucherdate)
The result will look like this:
id | month | total_credits | total_debits
-------------------------------------------------------
1 | January 2011 | 243.12 | 123.12
1 | February 2011 | 140.29 | 742.22
1 | March 2011 | 673.19 | 238.11
2 | January 2011 | 472.23 | 132.14
2 | February 2011 | 365.34 | 439.99
To execute arbitrary sql in rails:
account_id_hash = ActiveRecord::Base.connection.select_all("Select accounts.id from accounts")
From here you'll need to cross-tab or pivot the data into the format you need using old-fashioned ruby code. If you need help with that feel free to post a new question.
Related
I guess I wasn't very clear in the title, but I find it hard to describe exactly what I'm after. So it's better to start with an example:
I have the following models:
class Song < ActiveRecord::Base
has_many :learnt_items, as: :learnable
has_many :trained_items, as: :learnable
end
class LearntItem < ActiveRecord::Base
belongs_to :learnable, polymorphic: true
end
class TrainedItem < LearntItem
end
If, for example, I select all songs that have trained items:
Song.joins(:trained_items)
I would see (roughly) records returned like:
learnable_type | learnable_id | type | label
Song | 1 | TrainedItem | happy
Song | 1 | TrainedItem | sad
Song | 1 | TrainedItem | lively
Song | 2 | TrainedItem | lively
If I would like to select all songs that have trained items with specific labels, I'd do:
Song.joins(:trained_items).where(learnt_items: { label: [:happy, :sad] })
Now, I need to get all songs that don't have trained items for given labels. One would think the following would suffice:
Song.joins(:trained_items).where.not(learnt_items: { label: [:happy, :sad] })
But this will still produce the following records:
learnable_type | learnable_id | type | label
Song | 1 | TrainedItem | lively
Song | 2 | TrainedItem | lively
which is indeed not what I intended. You can see that the query filtered out records with the given labels, but the one with label = 'lively' is still there, hence returning the song with id = 1. I would need only the song with id = 2 to be returned from this query.
How can I build a query with ActiveRecord so that my scenario is fulfilled?
Use a subquery to find the ids you don't want and use those in the where.not condition:
Song.joins(:trained_items)
.where.not(learnable_id: Song.select(:learnable_id).where(learnt_items: { label: [:happy, :sad] })
I'm attempting to use single-table inheritance in Rails as a means of allowing a user have multiple user sub-types (e.g., faculty, vendor, etc.). I've ended up with a user table with records containing only a single user type. While still using single-table inheritance, how do I get my users to have multiple types? (I know this is essentially a many-to-many relationship; I'm just not sure of how to accomplish this using STI.)
id | first_name | last_name | birth_date | city | zip_code | email | type | created_at | updated_at
----+------------+-----------+------------+------+----------+-------+---------+----------------------------+----------------------------
1 | Akira | Yamaoka | | | | | Vendor | 2014-08-30 14:58:26.917333 | 2014-08-30 14:58:26.917333
2 | Pyramid | Head | | | | | Faculty | 2014-08-30 15:02:04.70209 | 2014-08-30 15:02:04.70209
Here are my models' classes:
user.rb
1 class User < ActiveRecord::Base
2 end
vendor.rb
1 class Vendor < User
2 belongs_to :user
3 belongs_to :event
4 end
faculty.rb
1 class Faculty < User
2 belongs_to :user
3 belongs_to :event
4
5 end
you could write something like:
class User < ActiveRecord::Base
belongs_to :event
end
class Vendor < User
end
class Faculty < User
end
then get records with different types through User model, like User.all
A short answer: no, you can't do that, type column can only hold one value.
A longer answer: a many-to-many relationship requires that an extra model exists (it has to, HABTM just won't do here) that contains references to a User and an Event. I called it a Pass in this answer to your another question.
You should subclass a Pass, not User. And since User has_many :passes, which can possibly be Pass subclasses, a User can participate in an event in multiple ways: as a vendor or as a faculty member.
I've supplied some example code here.
Let's say we have these two models:
class Attendee < ActiveRecord::Base
has_many :dances
has_many :partners, through: :events
end
class Dance < ActiveRecord::Base
belongs_to :attendee
belongs_to :partner, class_name: Attendee
end
I'd like to implement a method, for example Dance.matches_of(attendee) that returns the dances of an attendee only if his partners have a dance where he is the partner. How would you implement an efficient solution?
EDIT:
Sorry because I couldn't explain my question well, I'll try to clarify it (I changed the Event class name to Dance). If an user wants to dance with another one, he/she will create an object Dance, specifying the partner. So, let's say that Bob would like to dance with Sarah, the database table dances would look like this (I'll use usernames instead of ids to make it clearer, hopefully):
| id | attendee_id | partner_id |
---------------------------------------------------
| 1 | bob | sarah |
So, Dance.matches_of(bob) would return nothing, as there is no one that he wants to dance with who also wants him as a dancing partner (poor Bob). After a while Sarah thinks that maybe Bob is not such a bad guy, and he deserves a chance to hit the floor. Now, the dances table looks like this:
| id | attendee_id | partner_id |
---------------------------------------------------
| 1 | bob | sarah |
---------------------------------------------------
| 2 | sarah | bob |
Dance.matches_of(bob) now returns the dance with id 1, because it's the record that shows Bob's interest on dancing with Sarah AND Sarah wants to dance with him as well (Dance.matches_of(sarah) would retrieve the second record). If Bob wanted to dance with Anna too, and so did she, the method would retrieve two records and Bob would be the happiest guy at the party, as he'd have two dancing partners.
I hope this explanation is clearer, but I'm aware that if it's so hard to be explained and understood, maybe the approach I'm following is not correct, so I'm open to suggestions.
EDIT 2:
The solution I came up with:
def self.matches_of(attendee)
attendee.dances.select { |dance| dance.partner.partners.include? attendee }
end
But I don't expect it to be much efficient anyway.
Given your models, you can add a matches_of scope to the Dance model:
class Attendee < ActiveRecord::Base
has_many :dances
has_many :partners, class_name: 'Attendee', through: :dances
end
class Dance < ActiveRecord::Base
belongs_to :attendee
belongs_to :partner, class_name: 'Attendee'
scope :matches_of, -> (attendee) {
where(partner_id: attendee.id, attendee_id: attendee.partner_ids)
}
end
Given dances like
| id | attendee_id | partner_id |
---------------------------------------------------
| 1 | bob | sarah |
matches_of will return []
bob = Attendee.find 1
Dance.matches_of(bob)
# returns []
Given dances like
| id | attendee_id | partner_id |
---------------------------------------------------
| 1 | bob | sarah |
---------------------------------------------------
| 2 | sarah | bob |
matches_of will return Dance #2
Dance.matches_of(bob)
# returns [#<Dance id: 2, attendee_id: 2, partner_id: 1>]
I can think of a method like this, if there is a partner_id relating to an id in attendees table:
def partner_events
Event.where('partner_id = ? and attendee_id in (?)', id, events.map(&:partner_id))
end
def matches_of(attendee)
partner_ids = Event.pluck(:attendee_id).where(:partern_id = > attendee.id).uniq
Event.where('attendee_id = ? and partner_id in (?)', attendee.id, partner_ids)
end
Let's say I have the following tables:
product transaction
------------------ ------------------
| id | name | | id | product |
------------------ ------------------
| 1 | Product A | | 1 | 2 |
| 2 | Product B | | 2 | 3 |
| 3 | Product C | | 3 | 2 |
------------------ ------------------
Now, let's say I want to make a listing of the transaction table, but I want to display product names instead of product IDs.
In raw SQL, I would do something like this:
SELECT product.name FROM transaction, product WHERE transaction.product = product.id
I'm having a hard time figuring out how this would be done using Rails' Active Record Query Interface. I'm sure this is trivial to you Rails experts out there.
If you want to solve it the "Rails way":
first: change column product in transactions to product_id, and table names should be in plural, otherwise you must add e.g. set_table_name 'transaction' in the models, you will miss a lot of Rails' futures if you don't change it.
add/change these models:
class Product < ActiveRecord::Base
has_many :transactions
end
class Transaction < ActiveRecord::Base
belongs_to :product
end
If you persist on using column name "product" you must change the belongs_to to:
belongs_to :product, :foreign_key => 'product'
but that is kind of ugly
Finally, using this:
Transaction.includes(:product)
Or if you want only those who has a product
Transaction.joins(:product)
Note:
I don't remember exactly, but I think I had problems with using "transactions" as table name in some application. Somebody else perhaps knows about that.
transaction.joins('LEFT OUTER JOIN product ON transaction.id = product.id')
I have two models:
class User
end
class Message
belongs_to :sender, :class_name=> 'User'
belongs_to :recipient, :class_name=> 'User'
end
I want to fetch all the buddies of a given user ordered by most recent date of message what appears in conversation between given user and his buddy and, if it possible in same query, to fetch the number of message in their conversation.
Now, I'm stuck at this:
Messages.all(:joins => :sender,
:conditions => ['sender_id = ? OR recipient_id = ?', some_user.id, some_user.id],
:select => 'users.*, sender_id, recipient_id, MAX(messages.created_at) as last_created, COUNT(messages.id) as messages_count',
:group => 'messages.sender_id, messages.recipient_id',
:order => 'last_created DESC'
That query produce this output:
a)
users.* | sender_id | recipient_id | MAX(last_created) | messages_count
user1 | 1 | 2 | bla | bla
user1 | 1 | 3 | bla | bla
user1 | 1 | 4 | bla | bla
Because models joined by messages.sender_id = user.id I have only user1 records fetched but I need user2, user3 and user4 records in that special situation A when user1 has only send messages to his buddies.
b)
users.* | sender_id | recipient_id | MAX(last_created) | messages_count
user2 | 2 | 1 | bla | bla
user3 | 3 | 1 | bla | bla
user4 | 4 | 1 | bla | bla
In situation B, otherwise, i have what i want to have - all three buddies ordered by most recent date of message what appears in conversation between given user and his buddy.
c)
users.* | sender_id | recipient_id | MAX(last_created) | messages_count
user1 | 1 | 2 | bla | bla
user3 | 3 | 1 | bla | bla
user4 | 4 | 1 | bla | bla
Situation C. user2 as buddy of user1 is missing cause :joins => :sender. Otherwise, if :joins => :recipient would be missing user3 and user4. Thats the cracker. It's no matter how we join models. How to solve this situation in one query?
You need the select_extra_columns gem to return join/aggregate columns. Assuming you have installed the gem, modify your User model as shown below.
class User
select_extra_columns
def friends_with_conversation
User.all(
:select => "users.*, b.last_message_at, b.message_count",
:joins => "
RIGHT JOIN
( SELECT IF(a.sender_id=#{self.id}, a.recipient_id,
a.sender_id) AS friend_id,
MAX(a.created_at) AS last_message_at,
COUNT(a.id) AS message_count
FROM messages AS a
WHERE a.sender_id = #{self.id} OR
a.recipient_id = #{self.id}
GROUP BY IF(a.sender_id=#{self.id}, a.recipient_id,
a.sender_id)
) AS b ON users.id = b.friend_id
",
:order => "b.last_message_at DESC",
:extra_columns => {:last_message_at=>:datetime, :message_count => :integer}
)
end
end
Now you can make following calls to get the friend details.
user.friends_with_conversation.each do |friend|
p friend.name
p friend.last_message_at
p friend.message_count
end
You need the gem to return last_message_at and message_count in the User object returned by the query.
Edit
I am not familiar with PostgresSQL. Cursory reading of the documentation suggests, following SQL might work.
:joins => "
RIGHT JOIN
( SELECT CASE WHEN a.sender_id=#{self.id}
THEN a.recipient_id
ELSE a.sender_id
END AS friend_id,
MAX(a.created_at) AS last_message_at,
COUNT(a.id) AS message_count
FROM messages AS a
WHERE a.sender_id = #{self.id} OR
a.recipient_id = #{self.id}
GROUP BY CASE WHEN a.sender_id=#{self.id}
THEN a.recipient_id
ELSE a.sender_id
END
) AS b ON users.id = b.friend_id
"
Looks like you want an alternative "joins" string (ie not :joins => :sender).
Would :joins => :recipient give the correct response for situation B?
If not - you can also pass in hand-crafted SQL to the :joins key and join the tables however you like.
Looks like there's a good tutorial covering joins here:
http://www.railway.at/articles/2008/04/24/database-agnostic-database-ignorant/