I have this Event
model
class Event < ActiveRecord::Base
attr_accessible :starts_at, :ends_at, :price
end
An Event
can be a single instant event:
Event.create(:starts_at => Date.today, :ends_at=>nil, :price => 10.0)
It can also span multiple days or months:
Event.create(:starts_at => Date.today, :ends_at => (Date.today + 2.months), :price => 20.0)
I want to break down the cost of events by months, so that cost for an instant event falls into the month it belongs to, naturally, and cost for an event that spans multiple months should be divided proportionally between those months.
Obviously this would be difficult to handle using SQL, but maybe someone has some advice on that?
What would be the most efficient way of calculating this aggregation?
Update:
Here is a clearly defined structure for the data on sqlfiddle: http://sqlfiddle.com/#!12/a532e/6
What I would want from this dataset is something like:
( (Date('2013-01-01'), 31.6), (Date('2013-02-01'), 8.4) )
They aren’t focused only on reading events from a single fine-grained stream like Aggregates do. In Event Sourcing, an Aggregate has its internal state, which is a projection of a single fine-grained event stream.
In an RDBMS, the aggregate would be the related entities, and the id would be the primary key that you use to load the entities. In an event store, the contents of the stream are describing the changes to the events in the aggregate, the id is just the key you use to find the correct events.
As we can see the conceptually the Aggregate state is a specialised Projection of the event stream, but let’s not confuse the Aggregates itself with a Projection. The Aggregate has the state, but also the logic that allows to handle the commands and defines the consistency boundaries.
GiftCard.amount is one field in a GiftCard Aggregate, but this field is mapped to all the events, like card-redeemed (take money from the card) ever created. The source of data for your Aggregate is not a record in a database but the complete list of events ever created for that specific aggregate.
SELECT event_id
,date_trunc('month', day)::date AS month
,sum(daily) AS price_this_month
FROM (
SELECT event_id
,generate_series(starts_at
,COALESCE(ends_at, starts_at)
,interval '1 day') AS day
,price / COALESCE((ends_at - starts_at) + 1, 1) AS daily
FROM events
) a
GROUP BY 1,2
ORDER BY 1,2;
-> sqlfiddle
I provided an updated sqlfiddle for PostgreSQL. Your original seems to be for MySQL.
generate_series()
to create one row per day for every event.COALESCE
to take care of NULL
values, which seem to be allowed for ends_at
.daily
cost by dividing the price by the number of days between starts_at
and ends_at
+ 1
to fix off-by-one.
Default to 1 in case of ends_at IS NULL
.Voilá.
You even get exact rates per month, depending on how many days a month has. February (28 days) is cheaper than January (31 days).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With