With some help, I was able to take a method that displays a word count for each day in a project and reduce it down to pretty much one database quere. Here's the method
def project_range(project, start, finish, &blk)
words = project.words.where(:wrote_on => start..finish)
word_map = words.index_by(&:wrote_on)
for day in start..finish
word_count = word_map[day] ? word_map[day].quantity : 0
blk.call(day, word_count)
end
end
The original question is here:
Reducing database hits in Rails
Now though, we want to change the way users enter word counts for each day, however — we want to give them a chance to enter multiple counts for a day, instead of just one per day. This method, however, only returns the word count for the last day entered, not all the word counts for that specific day.
I tried changing index_by to group_by, but I got an undefined method error for quantity. So, my question is... how can I handle multiple entries for the same day, where there are two wrote_on objects for say, 5/4/2012?
group_by worked with another modification. Changes are commented out.
def project_range(project, start, finish, &blk)
words = project.words.where(:wrote_on => start..finish)
#word_map = words.index_by(&:wrote_on)
word_map = words.group_by(&:wrote_on)
for day in start..finish
#word_count = word_map[day] ? word_map[day].quantity : 0
word_count = word_map[day] ? word_map[day].sum(&:quantity) : 0
blk.call(day, word_count)
end
end
Related
I'm not very knowledgeable in coding of Access queries, so I hope someone can help with this issue.
I have a query (using the query builder) that has a field named RetrainInterval from table tblProcedures (this will return a number like 1, 3, 6, 12, etc.; the rotational months the particular document have to be retrained on) and another field named Training/Qualification Date from table tblTrainingRecords.
I want the query to look at the RetrainInterval for a given record (record field is ClassID in tblProcedures) and then look at the Training/Qualification Date and calculate if that record should be in the query.
In a module I would do this:
IF RetrainInterval = 1 Then
DateAdd("m",1,[Training/Qualification Date]) <add to query if <=today()+30>
ElseIf RetrainInterval = 3 Then
DateAdd("m",3,[Training/Qualification Date]) <add to query if <=today()+30>
ElseIF......
How can I translate this into something that would work in a query? My end goal is to generate a report that will show me what document class numbers are due within a specified time interval (say I enter 30 in the form textbox to represent any upcoming required training within 30 days of the query), but all of the calculations to determine this is based off of when the last training date was (stored in the training records table). I also want to make sure that I do not get multiple returns for the same class number since there will be multiple training entries for each class, just grab the minimum last training date. I hope I explained it well enough. It's hard to put this into words on what I am trying to do without positing up the entire database.
UPDATE
I think I have simplified this a bit after getting some rest. Here are two images, one is the current query, and one is what comes up in the report. I have been able to refine this a bit, but now my problem is I only want the particular Class to show once on the report, not twice, even though I have multiple retrain due dates (because everything is looking at the table that holds the employee training data and will have multiple training's for each Class number). I would like to only show one date, the oldest. Hope that makes sense.
Query - http://postimg.org/image/cpcn998zx/
Report - http://postimg.org/image/krl5945l9/
When RetrainInterval = 1, you add 1 month to [Training/Qualification Date].
When RetrainInterval = 3, you add 3 months to [Training/Qualification Date].
And so on.
The pattern appears to be that RetrainInterval is the number of months to add. If that is true, use RetrainInterval directly in your DateAdd() expression and don't bother about IF THEN.
DateAdd("m", RetrainInterval, [Training/Qualification Date])
You can not do that in a query. Been there, cursed that!
You can use the IFF( 2>x ; 1 ;0)
Giving that if the first statement is true, 1 is returned, and 0 if false.
You can not return a criteria like IFF(2>x ; Cell>2 ; Cell>0) (Not possible) It will just return 0 if you try, i think. it will not give an error all the time.
You have to use criterias!
I would to something like this picture:
I hope you follow, else let me know.
Ok so I'm having a bit of a learning moment here and after figuring out A way to get this to work, I'm curious if anyone with a bit more postgres experience could help me figure out a way to do this without doing a whole lotta behind the scene rails stuff (or doing a single query for each item i'm trying to get)... now for an explaination:
Say I have 1000 records, we'll call them "Instances", in the database that have these fields:
id
user_id
other_id
I want to create a method that I can call that pulls in 10 instances that all have a unique other_id field, in plain english (I realize this won't work :) ):
Select * from instances where user_id = 3 and other_id is unique limit 10
So instead of pulling in an array of 10 instances where user_id is 3 and you can get multiple instances with the other_id is 5, I want to be able to run a map function on those 10 instances and get back something like [1,2,3,4,5,6,7,8,9,10].
In theory, I can probably do one of two things currently, though I'm trying to avoid them:
Store an array of id's and do individual calls making sure the next call says "not in this array". The problem here is I'm doing 10 individual db queries.
Pull in a large chunk of say, 50 instances and sorting through them in ruby-land to find 10 unique ones. This wouldn't allow me to take advantage of any optimizations already done in the database and I'd also run the risk of doing a query for 50 items that don't have 10 unique other_id's and I'd be stuck with those unless I did another query.
Anyways, hoping someone may be able to tell me I'm overlooking an easy option :) I know this is kind of optimizing before it's really needed but this function is going to be run over and over and over again so I figure it's not a waste of time right now.
For the record, I'm using Ruby 1.9.3, Rails 3.2.13, and Postgresql (Heroku)
Thanks!
EDIT: Just wanted to give an example of a function that technically DOES work (and is number 1 above)
def getInstances(limit, user)
out_of_instances = false
available = []
other_ids = [-1] # added -1 to avoid submitting a NULL query
until other_ids.length == limit || out_of_instances == true
instance = Instance.where("user_id IS ? AND other_id <> ALL (ARRAY[?])", user.id, other_ids).limit(1)
if instance != []
available << instance.first
other_ids << instance.first.other_id
else
out_of_instances = true
end
end
end
And you would run:
getInstances(10, current_user)
While this works, it's not ideal because it's leading to 10 separate queries every time it's called :(
In a single SQL query, it can be achieved easily with SELECT DISTINCT ON... which is a PostgreSQL-specific feature.
See http://www.postgresql.org/docs/current/static/sql-select.html
SELECT DISTINCT ON ( expression [, ...] ) keeps only the first row of
each set of rows where the given expressions evaluate to equal. The
DISTINCT ON expressions are interpreted using the same rules as for
ORDER BY (see above). Note that the "first row" of each set is
unpredictable unless ORDER BY is used to ensure that the desired row
appears first
With your example:
SELECT DISTINCT ON (other_id) *
FROM instances
WHERE user_id = 3
ORDER BY other_id LIMIT 10
I'd like to count a text-string in every page on a report and print out the count of the strings in the page-footer.
Searching for a string in a text field is straight forward, counting the findings within the text-field too, but how is it possible to sum the findings in a integer variable per report-page when it has several entries?
i.e. I´ve got a report-page like this where each new line is a new record.
Here the first report-page:
aaaaaaF
aaaaaFF
ffaaaaaaaaa
FaaaaaFF
Now the page-footer:
There are 4 records. The letter "F" has been found 6 times on this report page.
Now the second report-page:
aaaFaaF
aaaaaF
fFaaaaaaaaa
FaaaFaFF
FFaaaaFa
page-footer:
There are 5 records. The letter "F" has been found 10 times on this report page.
I'd be happy if smdy has an advice for me.
Thanks!
First step is to figure out how many occurances of "f" occur in each record. Which you can do using
= Len([myField]) - Len(Replace([myField],"f",""))
Now for the total occurances in that page you use the Sum function in a text box in the report footer section.
= Sum( ... )
= Sum(Len([myField]) - Len(Replace([myField],"f",""))) ' if report based on a table
= Sum([myCalculatedField]) ' if you use the occurance count formula in the query instead
If you need to total across the page there is a link detailing how to go about it here (you'll have to scroll down a bit)
http://office.microsoft.com/en-us/access-help/summing-in-reports-HA001122444.aspx
You haven't shown any of the expressions that you are using but, essentially, in the Report Footer you would include a textbox which uses your aggregate function:
=COUNT([SomeField]) 'or
=SUM(iif(some condition, 1, 0))
where SomeField is a field in the detail section, or some condition refers to this field.
That is, you need to SUM (or COUNT) across the whole report by referring to field(s) in the details section. You do not do this by attempting to refer to the subtotals that you have in the page-footers - this won't work.
Hi I am new to programming and rails, and I am trying to create an admin interface in my app that shows stats. I have a Job model that has many Responses, and I need to collect the average response time for each day. In order to collect the response time for the first job I would do the following
job = Job.first
response = job.responses.first
response_time = response.created_at - job.created_at
This is very simple, but I am having trouble trying to collect this information for all the jobs of that day. Im trying to come up with a solution that will give me an array of data pairs. For example {[June 17, 51s], [June 18, 60s], [June 19, 38s], ... etc}.
I cant seem to figure out the correct rails active record call that will give me what I need
Don't think you are going to find an active record solution, but you have what you need, just need to add a little ruby.
Probably a 100 ways to do it, here is one way that creates a hash with the number of whole days from the job creation date as the key and the count as the value
job = Job.first
start_date = job.created_at
response_dates = job.responses.pluck(:created_at) #creates an array of created_at datetimes
day_stats = response_dates.each_with_object(Hash.new(0)) { |dt, h| h[((dt - start_date)/1.day).round(0)] += 1 }
This basically iterates through the datetime array, subtracts the response date from the job date, divides it by 1 day and rounds it to a whole day.
Output would be something like:
=> {0=>1, 5=>2, 6=>1, 7=>2, 9=>1, 31=>1, 37=>6, 40=>1, 42=>3, 44=>1, 59=>32, 60=>59, 61=>2, 64=>1, 65=>2, 78=>168, 97=>39, 93=>2, 110=>1, 214=>1}
If you want the date, you could add the key*1.day to the start_date
I'm trying to group a series of records in Active Record so I can do some calculations to normalize that quantity attribute of each record for example:
A user enters a date and a quantity. Dates are not unique, so I may have 10 - 20 quantities for each date. I need to work with only the totals for each day, not every individual record. Because then, after determining the highest and lowest value, I convert each one by basically dividing by n which is usually 10.
This is what I'm doing right now:
def heat_map(project, word_count, n_div)
return "freezing" if word_count == 0
words = project.words
counts = words.map(&:quantity)
max = counts.max
min = counts.min
return "max" if word_count == max
return "min" if word_count == min
break_point = (max - min).to_f/n_div.to_f
heat_index = (((word_count - min).to_f)/break_point).to_i
end
This works great if I display a table of all the word counts, but I'm trying to apply the heat map to a calendar that displays running totals for each day. This obviously doesn't total the days, so I end up with numbers that are out of the normal scale.
I can't figure out a way to group the word counts and total them by day before I do the normalization. I tried doing a group_by and then adding the map call, but I got an error an undefined method error. Any ideas? I'm also open to better / cleaner ways of normalizing the word counts, too.
Hard to answer without knowing a bit more about your models. So I'm going to assume that the date you're interested in is just the created_at date in the words table. I'm assuming that you have a field in your words table called word where you store the actual word.
I'm also assuming that you might have multiple entries for the same word (possibly with different quantities) in the one day.
So, this will give you an ordered hash of counts of words per day:
project.words.group('DATE(created_at)').group('word').sum('quantity')
If those guesses make no sense, then perhaps you can give a bit more detail about the structure of your models.