Average sales price for each day - ruby-on-rails

I have Rails app with a PostgreSQL DB Sales Table that looks like this:
Sales
price(decimal) date(datetime)
100.20 11/25/2012 11:00:00
30.43 11/26/2012 03:00:00
124.43 11/25/2012 18:00:00
190.34 11/26/2012 22:00:00
The DB has thousands of records like this, spanning 10 years.
I need to find the average sales price for each day and save each as a separate record in another DB table via a rake task.
For further clarity, Sales need to be grouped by day (even though data is in datetime format), and then the price values for that day need to be averaged.
Since I have 10 years of days, the ability to loop through each daily group and perform the averages is essential.
Does anyone have any suggestions as to how to write this?

You'll need to double check the syntax, but something like this should do it all in the database...
Sales.average(:price, :group => "DATE_TRUNC('day', date)")
Note that DATE_TRUNC is PG specific.

Related

Rails query data by hour

I'm having difficulty querying data by an hour for the previous day. Using Rails 4 on Postgres.
eg,
Table X:
created_at, value
timestamp1 3
timestamp2 5
I want to get:
time, value
YYMMDD 00:00 15
YYMMDD 01:00 20
basically the sum per hour. I've tried
Rails & Postgresql: how to group queries by hour?
Is it possible to group by hour/minute/quarter hour directly in ActiveRecord/Rails?
but I still cant figure out how to get it to work properly, i get the wrong hour for replies. How would i set my timezone along with this query?
TableX.group("DATE_trunc('hour',created_at)").count
Thanks!
All you need is create a query like this one:
SELECT date_trunc('hours', created_at), sum(value)
FROM TableX
GROUP BY date_trunc('hours', created_at)
This one uses date_trunc instead of suggested date_part, which fits your question better.

Group by date from a specific time to time in Rails

I'm trying to figure out if it's possible to group data by date from a specific time of a date to a time in next date.
I've a bills table in Postgresql database with the following columns:
id serial NOT NULL,
bill_amount double precision,
tax_amount double precision,
discount double precision,
grand_total double precision,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
I want group by clause like from date 2015-09-12 06:00:00.00000 to date 2015-09-13 06:00:00.00000, means all records less than 2015-09-13 06:00:00.00000 and greater than 2015-09-12 06:00:00.00000 will be considered as data of 12th September. So if I want to get the data of a month with group by date, is it possible?
In Postgresql you could use this as your grouping expression:
date_trunc('day',created_at-interval '6h')
(assuming created_at is the field you are wanting to group by).
I am no Rails expert, but glancing through the documentation I formed the opinion you might be able to get it to issue such SQL with something like
Bills.select("date_trunc('day',created_at-interval '6h') as day")
.group("date_trunc('day',created_at-interval '6h')")
Incidentally, I am guessing the reason you want the days to run from 06:00 to 06:00 is that the timezones of the date columns are not aligned - you would be better off to either ensure that the data stored in these columns is aligned so that days run from 00:00 - 00:00, or to use timestamp with timezone as the type of these columns if the table will store data from multiple time zones.

Rails: How to group records that have the same Date (month day year) for a Datetime field?

I have a Purchase model and I want to group all of the records by their ordered_datetime field. However, I don't care about the time, I just want to group by the date. So if there are 2 orders ordered on:
5/12/2014 12:00PM
5/12/2014 3:00PM
They should be grouped together even though they happened at different times during the day.
Is there a way to do this? Purchase.uniq.pluck(:ordered_datetime) separates the 2 records into 2 groups since their times are different.
You can use the DATE function on the timestamp column:
Purchase.group('DATE(ordered_datetime)').count
Which returns each date with a purchase count.
You can also sort the dates by adding an order clause:
Purchase.group('DATE(ordered_datetime)').order('date_ordered_datetime').count

Store the day of the week and time?

I have a two-part question about storing days of the week and time in a database. I'm using Rails 4.0, Ruby 2.0.0, and Postgres.
I have certain events, and those events have a schedule. For the event "Skydiving", for example, I might have Tuesday and Wednesday and 3 pm.
Is there a way for me to store the record for Tuesday and Wednesday in one row or should I have two records?
What is the best way to store the day and time? Is there a way to store day of week and time (not datetime) or should these be separate columns? If they should be separate, how would I store the day of the week? I was thinking of storing them as integer values, 0 for Sunday, 1 for Monday, since that's how the wday method for the Time class does it.
Any suggestions would be super helpful.
Is there a way for me to store the the record for Tuesday and
Wednesday in one row or do should I have two records?
There are several ways to store multiple time ranges in a single row. #bma already provided a couple of them. That might be useful to save disk space with very simple time patterns. The clean, flexible and "normalized" approach is to store one row per time range.
What is the best way to store the day and time?
Use a timestamp (or timestamptz if multiple time zones may be involved). Pick an arbitrary "staging" week and just ignore the date part while using the day and time aspect of the timestamp. Simplest and fastest in my experience, and all date and time related sanity-checks are built-in automatically. I use a range starting with 1996-01-01 00:00 for several similar applications for two reasons:
The first 7 days of the week coincide with the day of the month (for sun = 7).
It's the most recent leap year (providing Feb. 29 for yearly patterns) at the same time.
Range type
Since you are actually dealing with time ranges (not just "day and time") I suggest to use the built-in range type tsrange (or tstzrange). A major advantage: you can use the arsenal of built-in Range Functions and Operators. Requires Postgres 9.2 or later.
For instance, you can have an exclusion constraint building on that (implemented internally by way of a fully functional GiST index that may provide additional benefit), to rule out overlapping time ranges. Consider this related answer for details:
Preventing adjacent/overlapping entries with EXCLUDE in PostgreSQL
For this particular exclusion constraint (no overlapping ranges per event), you need to include the integer column event_id in the constraint, so you need to install the additional module btree_gist. Install once per database with:
CREATE EXTENSION btree_gist; -- once per db
Or you can have one simple CHECK constraint to restrict the allowed time period using the "range is contained by" operator <#.
Could look like this:
CREATE TABLE event (event_id serial PRIMARY KEY, ...);
CREATE TABLE schedule (
event_id integer NOT NULL REFERENCES event(event_id)
ON DELETE CASCADE ON UPDATE CASCADE
, t_range tsrange
, PRIMARY KEY (event_id, t_range)
, CHECK (t_range <# '[1996-01-01 00:00, 1996-01-09 00:00)') -- restrict period
, EXCLUDE USING gist (event_id WITH =, t_range WITH &&) -- disallow overlap
);
For a weekly schedule use the first seven days, Mon-Sun, or whatever suits you. Monthly or yearly schedules in a similar fashion.
How to extract day of week, time, etc?
#CDub provided a module to deal with it on the Ruby end. I can't comment on that, but you can do everything in Postgres as well, with impeccable performance.
SELECT ts::time AS t_time -- get the time (practically no cost)
SELECT EXTRACT(DOW FROM ts) AS dow -- get day of week (very cheap)
Or in similar fashion for range types:
SELECT EXTRACT(DOW FROM lower(t_range)) AS dow_from -- day of week lower bound
, EXTRACT(DOW FROM upper(t_range)) AS dow_to -- same for upper
, lower(t_range)::time AS time_from -- start time
, upper(t_range)::time AS time_to -- end time
FROM schedule;
db<>fiddle here
Old sqliddle
ISODOW instead of DOW for EXTRACT() returns 7 instead of 0 for sundays. There is a long list of what you can extract.
This related answer demonstrates how to use range type operator to compute a total duration for time ranges (last chapter):
Calculate working hours between 2 dates in PostgreSQL
Check out the ice_cube gem (link).
It can create a schedule object for you which you can persist to your database. You need not create two separate records. For the second part, you can create schedule based on any rule and you need not worry on how that will be saved in the database. You can use the methods provided by the gem to get whatever information you want from the persisted schedule object.
Depending how complex your scheduling needs are, you might want to have a look at RFC 5545, the iCalendar scheduling data format, for ideas on how to store the data.
If you needs are pretty simple, than that is probably overkill. Postgresql has many functions to convert date and time to whatever format you need.
For a simple way to store relative dates and times, you could store the day of week as an integer as you suggested, and the time as a TIME datatype. If you can have multiple days of the week that are valid, you might want to use an ARRAY.
Eg.
ARRAY[2,3]::INTEGER[] = Tues, Wed as Day of Week
'15:00:00'::TIME = 3pm
[EDIT: Add some simple examples]
/* Custom the time and timetz range types */
CREATE TYPE timerange AS RANGE (subtype = time);
--drop table if exists schedule;
create table schedule (
event_id integer not null, /* should be an FK to "events" table */
day_of_week integer[],
time_of_day time,
time_range timerange,
recurring text CHECK (recurring IN ('DAILY','WEEKLY','MONTHLY','YEARLY'))
);
insert into schedule (event_id, day_of_week, time_of_day, time_range, recurring)
values
(1, ARRAY[1,2,3,4,5]::INTEGER[], '15:00:00'::TIME, NULL, 'WEEKLY'),
(2, ARRAY[6,0]::INTEGER[], NULL, '(08:00:00,17:00:00]'::timerange, 'WEEKLY');
select * from schedule;
event_id | day_of_week | time_of_day | time_range | recurring
----------+-------------+-------------+---------------------+-----------
1 | {1,2,3,4,5} | 15:00:00 | | WEEKLY
2 | {6,0} | | (08:00:00,17:00:00] | WEEKLY
The first entry could be read as: the event is valid at 3pm Mon - Fri, with this schedule occurring every week.
The second entry could be read as: the event is valid Saturday and Sunday between 8am and 5pm, occurring every week.
The custom range type "timerange" is used to denote the lower and upper boundaries of your time range.
The '(' means "inclusive", and the trailing ']' means "exclusive", or in other words "greater than or equal to 8am and less than 5pm".
Why not just store the datestamp then use the built in functionality for Date to get the day of the week?
2.0.0p247 :139 > Date.today
=> Sun, 10 Nov 2013
2.0.0p247 :140 > Date.today.strftime("%A")
=> "Sunday"
strftime sounds like it can do everything for you. Here are the specific docs for it.
Specifically for what you're talking about, it sounds like you'd need an Event table that has_many :schedules, where a Schedule would have a start_date timestamp...

Rails: find by day of week with timestamp

I need to grab the records for same day of the week for the preceeding X days of the week. There must be a better way to do it than this:
Transaction.find_by_sql "select * from transactions where EXTRACT(DOW from date) = 1 and organisation_id = 4 order by date desc limit 7"
It gets me what I need but is Postgres specific and not very "Rails-y". Date is a timestamp.
Anyone got suggestions?
How many days do you want to go back?
I have written a gem called by_star that has a dynamic finder suited for finding up to a certain number of days in the past. If the number of days was always a number you could use this finder:
Transaction.as_of_3_days_ago
If it was dynamic then I would recommend using something such as future or between, depending on if you have transactions in the future (i.e. time travel):
Transaction.future(params[:start_date].to_time)
Transaction.between(params[:start_date].to_time, Time.now)
AFAIK Rails has no any methods to do this by other way. So best, and faster, solution - build DOW index on date column and use your query.

Resources