Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aggregate Event cost by month when Event ranges more than one month (in rails 3)

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) )
like image 357
Sævar Avatar asked Jan 10 '13 23:01

Sævar


People also ask

What is the difference between Event Sourcing and event aggregates?

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.

What is the difference between RDBMS aggregate and event store?

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.

What is aggregate state in event stream?

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.

What is the amount in a giftcard aggregate?

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.


1 Answers

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.

The tricks are:

  • Use generate_series() to create one row per day for every event.
  • Use COALESCE to take care of NULL values, which seem to be allowed for ends_at.
  • Calculate the 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.
  • Re-aggregate per event and month.

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).

like image 59
Erwin Brandstetter Avatar answered Sep 30 '22 17:09

Erwin Brandstetter