Multiple conditions during iteration, rails - sql

So I have
<% #invites.where(accept: 1).where(user_id: #user_id).each do |invite| %>
The idea is to only display the user in question, and where the accept value is equal to 1.
This causes the SQL query to be:
SELECT `invites`.* FROM `invites` WHERE `invites`.`accept` = 0 AND `invites`.`user_id` IS NULL
How do I solve this?

Related

How to convert complex Postgres SQL query / subqueries to active record in Ruby on Rails 5.2?

My application requires a text field and its translations to be displayed all at once in the edit view.
Available translations and even the list of configured languages evolve over time, so that the edit view must display the available translations, and create empty fields for missing ones.
Thus, the _form.html.erb file contains nested fields to handle these translations.
<%= f.fields_for translation_fields, translations.sort_by { |e| [ e.language == current_language.to_s ? 0 : 1, e.language ] } do |locution| %>
But this only allows handling of existing translations.
To allow additional languages support, I need to create a right-join with the list of languages, and initialise empty records accordingly. The corresponding SQL query for providing translations fields for the name of Playground object with id=555 is:
select 555 as document_id, 'Playground' as document_type, 'name' as field_name, property as language,
case when property = language then existing_translations.id else null end as id,
case when property = language then existing_translations.translation else null end as translation
from
(select *
from dqm_app.translations
where document_id = 555 and field_name = 'name' and document_type = 'Playground'
) as existing_translations
right outer join
(select property
from dqm_app.parameters inner join dqm_app.parameters_lists on parameters.parameters_list_id = parameters_lists.id
where parameters_lists.CODE = 'LIST_OF_LANGUAGES') as langues on property = language
As a result, we can see herunder that only a french translation exists, and other translations are to be created:
To reproduce this in Active Record, I created subqueries, which I need to join:
<% existing_translations = Translation.where({document_id: document_id, document_type: document_type, field_name: field_name}) %>
<% languages = Parameter.where("parameters_list_id = (select id from parameters_lists where code = 'LIST_OF_LANGUAGES')") %>
How can I create a right outer join between these subqueries?
Is there a better way to achieve this?
Thanks a lot!
Note: I finally solved any complex query by using AREL, starting with
this cheat sheet.

Perform a query with association aggregates

I have 2 models that have an association, say:
class Visit
# column names: made_purchases, purchase_amount
has_many :payments
end
class Payment
# column names: amount
belongs_to :visit
end
Now, I want to perform a query for all visits where either of the following matches:
1. made_purchases is true
OR
2. purchase_amount is not null
OR
3. has any associated payments
I've tried to create a subquery to count the number of payments and tried to use it with my WHERE query, but I can't use aggregate functions on WHERE, another option I could use would have been HAVING but I'm not sure how that would work in this case
I'm looking for either a query that can easily be done either with ActiveRecord or Arel composition that basically does something like:
Pseudocode:
WHERE visits.made_purchases = true OR visits.purchase_amount is NOT NULL OR HAVING COUNT(*) of payments > 0
I found out that even though a HAVING clause is useful for groups, you can still add the row filters and get the desired results.
Here, I ended up with:
Visits.having("(SELECT COUNT(*) FROM payments WHERE payments.visit_id= visits.id) > 0 OR visits.made_purchases= true OR (visits.purchase_amount IS NOT NULL AND visits.purchase_amount != '')")

Rails: Optimize querying maximum values from associated table

I need to show a list of partners and the maximum value from the reservation_limit column from Klass table.
Partner has_many :klasses
Klass belongs_to :partner
# Partner controller
def index
#partners = Partner.includes(:klasses)
end
# view
<% #partners.each do |partner| %>
Up to <%= partner.klasses.maximum("reservation_limit") %> visits per month
<% end %>
Unfortunately the query below runs for every single Partner.
SELECT MAX("klasses"."reservation_limit") FROM "klasses" WHERE "klasses"."partner_id" = $1 [["partner_id", 1]]
If there are 40 partners then the query will run 40 times. How do I optimize this?
edit: Looks like there's a limit method in rails so I'm changing the limit in question to reservation_limit to prevent confusion.
You can use two forms of SQL to efficiently retrieve this information, and I'm assuming here that you want a result for a partner even where there is no klass record for it
The first is:
select partners.*,
max(klasses.limit) as max_klasses_limit
from partners
left join klasses on klasses.partner_id = partners.id
group by partner.id
Some RDBMSs require that you use "group by partner.*", though, which is potentially expensive in terms of the required sort and the possibility of it spilling to disk.
On the other hand you can add a clause such as:
having("max(klasses.limit) > ?", 3)
... to efficiently filter the partners by their value of maximum klass.limit
The other is:
select partners.*,
(Select max(klasses.limit)
from klasses
where klasses.partner_id = partners.id) as max_klasses_limit
from partners
The second one does not rely on a group by, and in some RDBMSs may be effectively transformed internally to the first form, but may execute less efficiently by the subquery being executed once per row in the partners table (which would stil be much faster than the raw Rails way of actually submitting a query per row).
The Rails ActiveRecord forms of these would be:
Partner.joins("left join klasses on klasses.partner_id = partners.id").
select("partners.*, max(klasses.limit) as max_klasses_limit").
group(:id)
... and ...
Partner.select("partners.*, (select max(klasses.limit)
from klasses
where klasses.partner_id = partners.id) as max_klasses_limit")
Which of these is actually the most efficient is probably going to depend on the RDBMS and even the RDBMS version.
If you don't need a result when there is no klass for the partner, or there is always guaranteed to be one, then:
Partner.joins(:klasses).
select("partners.*, max(klasses.limit) as max_klasses_limit").
group(:id)
Either way, you can then reference
partner.max_klasses_limit
Your initial query brings all the information you need. You only need to work with it as you would work with a regular array of objects.
Change
Up to <%= partner.klasses.maximum("reservation_limit") %> visits per month
to
Up to <%= partner.klasses.empty? ? 0 : partner.klasses.max_by { |k| k.reservation_limit }.reservation_limit %> visits per month
What maximum("reservation_limit") does it to trigger an Active Record query SELECT MAX.... But you don't need this, as you already have all the information you need to process the maximum in your array.
Note
Using .count on an Active Record result will trigger an extra SELECT COUNT... query!
Using .length will not.
It generally helps if you start writing the query in pure SQL and then extract it into ActiveRecord or Arel code.
ActiveRecord is powerful, but it tends to force you to write highly inefficient queries as soon as you derail from the standard CRUD operations.
Here's your query
Partner
.select('partners.*, (SELECT MAX(klasses.reservation_limit) FROM klasses WHERE klasses.partner_id = partners.id) AS maximum_limit')
.joins(:klasses).group('partners.id')
It is a single query, with a subquery. However the subquery is optimized to run only once as it can be parsed ahead and it doesn't run N+1 times.
The code above fetches all the partners, joins them with the klasses records and thanks to the join it can compute the aggregate maximum. Since the join effectively creates a cartesian product of the records, you then need to group by the partners.id (which in fact is required in any case by the MAX aggregate function).
The key here is the AS maximum_limit that will assign a new attribute to the Partner instances returned with the value of the count.
partners = Partner.select ...
partners.each do |partner|
puts partner.maximum_limit
end
This will return max. limits in one select for an array of parthner_ids:
parthner_ids = #partners.map{|p| p.id}
data = Klass.select('MAX("limit") as limit', 'partner_id').where(partner_id: parthner_ids).group('partner_id')
#limits = data.to_a.group_by{|d| d.id}
You can now integrate it into your view:
<% #partners.each do |partner| %>
Up to <%= #limits[partner.id].limit %> visits per month
<% end %>

Rails 3.2 and getting AR to properly order this query

I currently have a scope where I am attempting to find last record created in an association and select it if a particular boolean value is false
IE Foo has_many Bar's and Bar's has a boolean column named bazzed
scope :no_baz, joins(:bars).order("bars.id DESC").limit(1).where("bars.bazzed = 'f'")
The problem with this is that rails turns this query into something like this
SELECT "foos".* FROM "foos" INNER JOIN "bars" ON "bars"."foo_id" = "foos"."id" WHERE (bars.bazzed = 'f') ORDER BY bars.id DESC LIMIT 1
the problem lies that rails is calling the order and limit after the where clause, what i'm looking for is to do the order and limit first to try and find the last bar that has bazzed set to false.
Is there a native AR way to perform the query I am attempting to accomplish?
EDIT
I am trying to grab the foo's that have a bar where the last bar they have has bazzed set to false and only if the last bar that that foo has has a false bazzed.
Ok, I would suggest this for the query on the "foo" model:
Foo.bars.where("bars.bazzed = ?", 'f').all( :order => "created_at DESC").first
Note: 'f' can be replaced by false, depending on the value you use in your "bazzed" column, of course.
[Edit]
Ok, as I think I better understand the problem, here is a suggestion, but for a public method and not a scoped query.
def no_baz
all_no_baz_foos = Array.new
Foo.all.each do |foo|
last_bar = foo.bars.all.order("bars.id DESC").first
if last_bar.bazzed == 'f'
all_no_baz_foos << foo
end
end
return all_no_baz_foos
end
This method will return an Array with all the no_baz_foos record in it. As I did not test my code, you may have to change few things for it to work, but I think you get the idea.
For the "scope" method, I just can't find a way to chain correctly the queries to have the desired result. If anyone else knows how to achieve that using a scope, I'll be glad to hear the solution too.
Using a class method for now but the problem with that lies that it returns an array object and not an active record relation which is what i'm trying to return. Still attempting to get the query correctly done.

Using raw sql queries in Rails 3 application?

I am working on migrating a legacy database into my Rails application (3.2.3). The original database comes with quite a few long sql queries for reports. For now, what I would like to do it use the sql queries in the Rails application and then one by one (when time allows) swap the sql queries to 'proper' Rails queries.
I have a clinical model and the controller has the following code:
#clinical_income_by_year = Clinical.find_all_by_sql(SELECT date_format(c.transactiondate,'%Y') as Year,
date_format(c.transactiondate,'%b') as Month,
sum(c.LineBalance) as "Income"
FROM clinical c
WHERE c.Payments = 0 AND c.LineBalance <> 0
AND c.analysiscode <> 213
GROUP BY c.MonthYear;)
However, when I run that code I get a few errors to do with the formatting.
Started GET "/clinicals" for 127.0.0.1 at 2012-04-29 18:00:45 +0100
SyntaxError (/Users/dannymcclelland/Projects/premvet/app/controllers/clinicals_controller.rb:6: syntax error, unexpected tIDENTIFIER, expecting ')'
...rmat(c.transactiondate,'%Y') as Year,
... ^
/Users/dannymcclelland/Projects/premvet/app/controllers/clinicals_controller.rb:7: syntax error, unexpected tIDENTIFIER, expecting keyword_end
...rmat(c.transactiondate,'%b') as Month,
... ^
/Users/dannymcclelland/Projects/premvet/app/controllers/clinicals_controller.rb:8: syntax error, unexpected tIDENTIFIER, expecting keyword_end
... sum(c.LineBalance) as "Income"
... ^
/Users/dannymcclelland/Projects/premvet/app/controllers/clinicals_controller.rb:10: syntax error, unexpected tCONSTANT, expecting keyword_end
... WHERE c.Payments = 0 AND c.LineBalance <> 0
... ^
/Users/dannymcclelland/Projects/premvet/app/controllers/clinicals_controller.rb:10: syntax error, unexpected '>'
...yments = 0 AND c.LineBalance <> 0
... ^
/Users/dannymcclelland/Projects/premvet/app/controllers/clinicals_controller.rb:11: syntax error, unexpected '>'
... AND c.analysiscode <> 213
... ^
Is there something I should be doing to the sql query before importing it into the controller? Although it's possible there is something wrong with the query (It was written quite some time ago), it does work as expected when run directly within the database. It returns an array like this:
----------------------------------------------
| Year | Month | Income |
----------------------------------------------
----------------------------------------------
| 2012 | January | 20,000 |
| 2012 | February | 20,000 |
| 2012 | March | 20,000 |
| 2012 | April | 20,000 |
----------------------------------------------
etc..
Any help, advice or general pointers would be appreciated!
I'm reading through http://guides.rubyonrails.org/active_record_querying.html trying to convert the sql query to a correct Rails query.
So far I have matched the second to last line:
AND c.analysiscode <> 213
with
#clinical_income_by_year = Clinical.where("AnalysisCode != 213")
baby steps!
UPDATE
I've got the filtering sorted now, thanks to the Rails guide site but I'm stuck on the grouping and sum part of the sql query. I have the following so far:
#clinical_income_by_year = Clinical.where("AnalysisCode != 213 AND Payments != 0 AND LineBalance != 0").page(params[:page]).per_page(15)
I'm struggling to build in the following two lines of the sql query:
sum(c.LineBalance) as "Income"
and
GROUP BY c.MonthYear;)
My view code looks like this:
<% #clinical_income_by_year.each do |clinical| %>
<tr>
<td><%= clinical.TransactionDate.strftime("%Y") %></td>
<td><%= clinical.TransactionDate.strftime("%B") %></td>
<td><%= Clinical.sum(:LineBalance) %></td>
</tr>
<% end %>
</table>
<%= will_paginate #clinical_income_by_year %>
The Ruby parser doesn't understand SQL, you need to use a string:
#clinical_income_by_year = Clinical.find_by_sql(%q{ ... })
I'd recommend using %q or %Q (if you need interpolation) for this so that you don't have to worry about embedded quotes so much. You should also move that into a class method in the model to keep your controllers from worrying about things that aren't their business, this will also give you easy access to connection.quote and friends so that you can properly use string interpolation:
find_by_sql(%Q{
select ...
from ...
where x = #{connection.quote(some_string)}
})
Also, the semicolon in your SQL:
GROUP BY c.MonthYear;})
isn't necessary. Some databases will let it through but you should get rid of it anyway.
Depending on your database, the identifiers (table names, column names, ...) should be case insensitive (unless some hateful person quoted them when they were created) so you might be able to use lower case column names to make things fit into Rails better.
Also note that some databases won't like that GROUP BY as you have columns in your SELECT that aren't aggregated or grouped so there is ambiguity about which c.transactiondate to use for each group.
A more "Railsy" version of your query would look something like this:
#c = Clinical.select(%q{date_format(transactiondate, '%Y') as year, date_format(transactiondate, '%b') as month, sum(LineBalance) as income})
.where(:payments => 0)
.where('linebalance <> ?', 0)
.where('analysiscode <> ?', 213)
.group(:monthyear)
Then you could do things like this:
#c.each do |c|
puts c.year
puts c.month
puts c.income
end
to access the results. You could also simplify a little bit by pushing the date mangling into Ruby:
#c = Clinical.select(%q{c.transactiondate, sum(c.LineBalance) as income})
.where(:payments => 0)
.where('linebalance <> ?', 0)
.where('analysiscode <> ?', 213)
.group(:monthyear)
Then pull apart c.transactiondate in Ruby rather than calling c.year and c.month.