I have this set of tables (I'm using postgresql)
User (with id, first_name, ....)
Assignment (with id, name, start_date, finish_date,....)
AssignmentUser(with assignment_id, user_id, flag_status)
The flag_status is a boolean that says if a user is still or not in an assignment.
Let's say user1 applies for assignment1, assignment2, assignment3 as follows:
start_date finish_date flag_status
user1 assignment1 11-11-11 11-12-11 false
user1 assignment2 01-10-11 01-02-12 true
user1 assignment3 01-01-12 01-03-12 true
Let's say I want to search TODAY the closest start_date of an user's assignment.
I've done this in my User model:
def last_date
self.assignments.where("date < ?", Date.today).max.try(:date)
end
and this
def last_status
AssignmentUser.find_by_assignment_id_and_user_id(Assignment.find_by_date(self.last_date), self.id).flag_status if self.last_date.present?
end
And in my view for each user:
User.all.each do |u|
<td> u.first_name </td>
<td> u.last_date </td>
<td> u.last_status </td>
end
It works well but, in my log I see 3 queries for each user (as expected).
So my question is: how can I avoid these multiple queries? (I guess it's more like a SQL question than a Rails one)
#au = AssignmentUsers.find_by_sql(
"SELECT assignment_users.user_id, MAX(assignment_users.date) as last_date,
assignment_id,
assignments.flag_status,
users.first_name
FROM assignment_users
LEFT JOIN assignments
ON assignments.id = assignment_id
LEFT JOIN users
ON users.id = user_id
WHERE date < CURDATE()
GROUP BY user_id;"
)
Then in your view:
#au.each do |u|
<tr>
<td> u.first_name </td>
<td> u.last_date </td>
<td> u.last_status </td>
</tr>
end
PLEASE NOTE: this is iterating over AssignmentUsers, so if there are any users who are without an assignment they will be missed. IF that isn't good enough (i.e. you don't want to pull back a 2nd list - easily done by User.all(:conditions => "id NOT IN (#{#au.collect(&:user_id}) - then this solution isn't suitable and if that's the case, then i think you're already probably doing it the best way, although i would have changed your methods to scopes - if you want me to show you how that would look let me know.
Related
Scenario: I have a table where I am trying to iteratively go through each row that matches my expected text and grab a particular column value for that row.
The html looks something like this:
<table class="table">
<tbody>
<tr xpath="1">
<td>
Write
</td>
<td>
123
</td>
<td>
1.0.1.8
</td>
<td>
TargetProduct1
</td>
<td>
</td>
<td>
P1
</td>
<td>
10.0
</td>
<td>
en-US
</td>
<td>
</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</tbody>
</table>
On that table, my goal is to go through each row and pick any row that has the value of "TargetProduct" and get the fifth column value of that row.
I am using the following Karate command:
* def getProduct = scriptAll('//table//tbody//tr', '_.innerText', function(x){ return x.contains('TargetProduct') })
However, when I do a karate.log on getProduct, I get something like the following:
Write 123 1.0.1.8 TargetProduct1 P1 10.0 en-US
Write 4586 1.0.1.3 TargetProduct5 Test2 10.1 en-US
Write 134 1.0.1.1 TargetProduct0 Four2 10.3 en-US
Write 3 1.0.1.2 TargetProduct2 Boo3 10.1 en-US
Along with many more like it under it (due to each row that matches the criteria).
It looks like primitive data structure, but I am not well versed in JS to know that. However, my goal is to grab the 5th index of each sets of results like above (basically generate a list with just these values: (P1, Test2, Four2, Boo3). What is one way to do that? Historically I have been able to get values using keys (i.e targetJson.result). However, there are no keys in this case to call to.
I have looked over JSON Transforms section of the documentation and I have tried some potential solutions like mapWithKey, however, they have not worked due to the strange data structure.
Please advise.
You could return an array from the JavaScript expression, containing a first value for filtering and a second string containing the text from your fifth column.
* def filter = function(x){ return x[0].contains('TargetProduct') }
* def rows = scriptAll('//table//tbody//tr', 'function(e){ return [e.innerText, e.cells[4].innerText] }', filter)
* def fifthCols = map(rows, function(x){ return x[1] })
First, read up on innerText to see what it does: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText - it just returns a single string, concatenating all child elements.
Suggestion: try to do this in multiple steps:
* def rows = locateAll('//table//tbody//tr')
* def filtered = rows.filter(x => x.text.includes('TargetProduct'))
* def temp = filtered.map(x => x.locateAll('td'))
What I have above is a guess, sorry I don't have time to dig into the details.
I'm trying to list records for a locations view of recently created at or updated records from the last 24 hours using activerecord but am a beginner developer needing some help.
Does anyone know a solution for implementing this in the controller/view? Thanks in advance for the help.
Since you're using Rails, I will assume that you have these files, corresponding to a Locations resource:
app/views/locations/index.html.erb
app/controllers/locations_controller.rb
app/models/location.rb
There are a few ActiveRecord alternatives for querying records in the past 24 hours:
This example demonstrates the concept that you can specify a range for querying the timestamp columns.
#locations = Location.where(updated_at: (Time.now - 24.hours)..Time.now)
As pointed out in the comments below, there may be a fraction of a second precision error with the above query. You can store a variable, now = Time.now, to ensure that your query spans exactly 24 hours.
now = Time.now
#locations = Location.where(updated_at: (now - 24.hours)..now)
You could eliminate the subtraction operation and let Rails handle it for you, which may also result in a slight offset from an exact window of 24 hours.
#locations = Location.where(updated_at: 24.hours.ago..Time.now)
You can also forego the hash syntax in the where parameters, passing a SQL string that filters with the > comparison operator.
#locations = Location.where('updated_at > ?', 24.hours.ago)
In your controller, add an index action, with your preferred query approach:
def index
#locations = Location.where(updated_at: 24.hours.ago..Time.now)
end
In your view, add these lines:
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Created_At</th>
<th>Updated_At</th>
</tr>
</thead>
<tbody>
<% #locations.each do |location| %>
<tr>
<td><%= location.id %></td>
<td><%= location.name %></td>
<td><%= location.created_at %></td>
<td><%= location.updated_at %></td>
</tr>
<% end %>
</tbody>
</table>
I have two tables in SQL Server Orders and OrderDetails.
Orders contains the OrderID and etc.
OrderDetails contain OrderID, ItemID, Quantity and etc.
These two tables have a one-to-many relationship. One order can have many items.
What is the best way to retrieve records from these 2 table, so that when I display the records in my ASP page, it will have the following tabular format?
OrderID ItemID Quantity
1 156 1
2 156 2
150 1
188 1
3 245 1
344 1
The easiest is to have a query to retrieve the details from the OrderDetails table inside the main loop, but this will be very resource intensive.
Is there a better way to achieve this?
The database is in SQL Server and my page is in classic ASP.
Thank you.
SQL:
select o.OrderID, d.ItemID, d.Quantity
from Orders o
inner join OrderDetails d on o.OrderID = d.OrderID
order by o.OrderID, d.ItemID
ASP:
store the last OrderID in a variable and whenever it's different than the last time print it, otherwise print an empty <td>
<%
set lastId = -1
do while not objRS.EOF
%>
<tr>
<% if lastId <> objRs("OrderID") then %>
<td><%= objRs("OrderID") %></td>
<% else %>
<td></td>
<% end if %>
<td><%= objRs("ItemID") %></td>
<td><%= objRs("Quantity") %></td>
</tr>
<%
lastId = objRs("OrderID")
loop %>
You have 3 options here:
(a) Create a stored procedure that 'flattens' the relationship down into single rows, your presentation layer can then selectively make a choice on what to hide or display. Effectively your query should return the following data into a business object:
OrderID ItemID Quantity
1 156 1
2 156 2
2 150 1
2 188 1
3 245 1
3 344 1
Your UI must then handle the complexity of display the parent child relationship and hiding duplicate orders.
(b) Use an ORM like Entity Framework or nHibernate to populate a parent / child object model that reflects your table and relationship structure. That way the presentation layer can just write out the object model (iterate through each parent and child collection).
(c) Load each orders details collection via a separate query in a loop for each order.
Option (c) is the least preferred as it scales terribly because the number of database calls directly correlates to the number of orders.
Option (b) is the best approach if you are using an ORM but if not a simple solution (a) is a good method to quickly retrieve the data in one go via a stored procedure.
Bear in mind that if you have large data-sets you may want to consider implementing paging as performance will degrade as you retrieve more data.
I am trying to construct a simple site which compares the best time for a race for a number of individuals, however, I am having a lot of difficulty using distinct which seems to be causing me a number of unexpected problems...
I have two databases - Result and Athlete (athlete has many results)
I am attempting to identify the quickest time for each athlete for a specific event and then put them in order. In order to do this, I need to create a list of unique athlete names, BUT they also have to be in the sequence of increasing time (i.e. slower). I am currently using:
<% #filtered_names = Result.where(:event_name => params[:justevent]).joins(:athlete).order('performance_time_hours ASC').order('performance_time_mins ASC').order('performance_time_secs ASC').order('performance_time_msecs ASC').select('distinct athlete_id') %>
This appeared to be working, however, I have discovered that if the last entry in the results database is the slowest across all athletes, this athlete ends up at the end of my list of names, EVEN IF one of their previous results is the fastest of all times recorded!
Is someone able to tell me whether distinct works in some strange way and how I can get around this issue? If the bottom result is the quickest the script works perfectly as it is...
For the sake of completeness, I need this information in order to run the following code:
<% #filtered_names.each do |filtered_name| %>
<% #currentathleteperformance = Result.where(:event_name => params[:justevent]).where(:athlete_id => filtered_name.athlete_id).order('performance_time_hours ASC').order('performance_time_mins ASC').order('performance_time_secs ASC').order('performance_time_msecs ASC').first() %>
<% #currentathlete = Athlete.where(:id => filtered_name.athlete_id).first() %>
<td><%= #currentathleteperformance.performance_time_mins %>:<%= #currentathleteperformance.performance_time_secs %>:<%= #currentathleteperformance.performance_time_msecs %> </td>
<td><%= #currentathleteperformance.wind_speed %></td>
<td><%= #currentathleteperformance.athlete_name %></td>
<td><%= #currentathlete.gender %></td>
<td><%= #currentathlete.sec %></td>
<td><%= #currentathleteperformance.competition_name %></td>
<td><%= #currentathleteperformance.round %></td>
<td><%= #currentathleteperformance.position %></td>
<td><%= #currentathleteperformance.performance_date %></td>
<td><%= #currentathlete.coach_name %></td>
<% end %>
I would use 1 column performance_time of data type time to replace all of:
performance_time_hours
performance_time_mins
performance_time_secs
performance_time_msecs
Or interval if more than 24 hours for one performance are possible.
I am no good with Ruby syntax, but the query to get what you want could look like this - assuming that athletes can perform multiple times per event and event_name is unique:
SELECT athlete_id, min(performance_time) AS min_performance_time
FROM result
WHERE event_name = params[:justevent]
GROUP BY athlete_id
ORDER BY min(performance_time), athlete_id
I order by athlete_id in addition, but that's just an arbitrary measure to break ties in a stable manner.
DISTINCT just takes the first row according to the sort order for every set of duplicates. To achieve the same result with DISTINCT you'd need a subselect:
SELECT athlete_id, performance_time
FROM (
SELECT DISTINCT ON (athlete_id)
athlete_id, performance_time
FROM result
WHERE event_name = params[:justevent]
ORDER BY athlete_id, performance_time
) a
ORDER BY performance_time, athlete_id
You have to ORDER BY athlete_id to get distinct athletes and cannot order by minimum performance_time first on the same query level. Therefore, you'd have to put the result of the SELECT DISTINCT in a subselect and sort by time in additional step.
I have many items and many users in a database. A user can set a status to the various items. The three tables are (they have been simplified in this explanation) users, statuses, items. The models are shown below.
class Item < ActiveRecord::Base
has_many :statuses
end
class Status < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
class User < ActiveRecord::Base
has_many :statuses
end
The controller
class ItemController < ApplicationController
def index
#items = Item.all
end
end
The view (this has been simplified, in my code I actually generate a form for the status so the user can create/modify their status but in the example below I'm just making as if the status is being printed out as text):
<th>Item ID</th>
<th>Item Status</th>
...
<% #items.each do |item| %>
<td><%= item.id %></td>
<% status = item.statuses.where(:user_id => current_user.id) %>
<td><%= status.first.status %></td>
<% end %>
Here are the queries generated by Rails:
Item Load (0.3ms) SELECT `items`.* FROM `items` ORDER BY id asc
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 1) AND (`statuses`.`user_id` = 103)
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 2) AND (`statuses`.`user_id` = 103)
Status Load (0.1ms) SELECT `statuses`.* FROM `statuses` WHERE (`statuses`.item_id = 2) AND (`statuses`.`user_id` = 103) LIMIT 1
SQL (0.2ms) SELECT COUNT(*) FROM `statuses` WHERE (`statuses`.item_id = 3) AND (`statuses`.`user_id` = 103)
All the items are selected in one SQL query which I think is good. Then it seems the SELECT COUNT (*) query is executed, if a status is found then that status is fetched using another SQL query, this happens for each item.
Guessing there is a much better way of going about this, when I have a few hundred items the number of SQL queries being carried out is huge! If anyone has any tips for how they would go about this I'd be interested to hear.
Thanks.
The solution is to include the statuses when you build the items.
#items = Item.includes(:statuses).all
And then use a select method to return the relevant status.
item.statuses.select {|s| s.user_id => current_user.id}
The rails magic sometimes results in these crazy queries, since item.statuses can be interpreted as either an array of status objects, or an ActiveRecord Relation.
When you call the where method on item.statuses, it defaults to ActiveRecord Relation behaviour, and builds a new SQL query. If you call select, item.statuses behaves as an array.
This is also the case for methods like sum. Very annoying!