Rails: Joining by one table OR another table - ruby-on-rails

I'm looking for a more efficient way to write an ActiveRecord query. I want to get all instances of a model that either join one table or another table. Both is easy, but either is difficult.
Right now, I have the following two queries:
across_clues = Clue.joins(:across_cells)
down_clues = Clue.joins(:down_cells)
(Followed by the unsatisfactory clues = (across_clues + down_clues).uniq.sort_by{|clue| clue.id} )
I'm wondering how to write a single query that will give me the union of both of my queries. That way I can let Postgres do the heavy lifting and keep Rails from getting its hands dirty.
I know how to get the intersection of the two sets:
bad_clues = Clue.joins(:across_cells, :down_cells)
but I haven't seen a good way to get their union. Any help would be appreciated and loved!

(For posterity)
I used UNION DISTINCT according to shiva's answer, but just slightly modified it to be less hard-coded:
across_query = Clue.joins(:across_cells).to_sql
down_query = Clue.joins(:down_cells).to_sql
clues = Clue.find_by_sql("(#{across_query}) UNION DISTINCT (#{down_query})")
It works!

The key is you need to use find_by_sql and UNION DISTINCT
I am a MySQL guy so here is how I would do it
Clue.find_by_sql("(SELECT clue.* FROM clue
INNER JOIN across_cell ON across_cell.clue_id=clue.id)
UNION DISTINCT
(SELECT clue.* FROM clue
INNER JOIN down_cell ON down_cell.clue_id = clue.id)")

What about
across_clues = Clue.joins(:across_cells)
down_clues = Clue.joins(:down_cells)
Clue.where do
(id.in across_clues.select{id}) | (id.in down_clues.select{id})
end
with Squeel?

Related

How to convert a plain sql query with subqueries to use rails active record

I have tried converting this plain sql query to rails active record but I am unable to do so.
select vote_shares.election_year as vs_election_name,
vote_shares.party as vs_party,
(sum(vote_shares.party_seats)/totals.total)*100 AS vs
from pcdemographics INNER JOIN vote_shares on vote_shares.pc_id = pcdemographics.pc_id,
(
SELECT vote_shares.election_name, sum(vote_shares.party_seats) as total
FROM `pcdemographics`
INNER JOIN vote_shares on vote_shares.pc_id = pcdemographics.pc_id
GROUP BY `election_name`
) AS totals
where vote_shares.election_name=totals.election_name
group by vote_shares.party,vote_shares.election_name;
This is what I have tried
#vssubquery = Pcdemographic.select('vote_shares.election_name, sum(vote_shares.party_seats) as total').joins('INNER JOIN vote_shares on vote_shares.pc_id = pcdemographics.pc_id')
Pcdemographic.select("vote_shares.election_year as vs_election_year,
vote_shares.party as vs_party,
(sum(vote_shares.party_seats)/'#{totals.total}')*100 AS vs").from(#vssubquery,:totals)
.joins("INNER JOIN vote_shares on vote_shares.pc_id = pcdemographics.pc_id and vote_shares.election_name='#{totals.election_name}'")
My answer might not be what you hoped for but I recommend not using AR, use Sequel (http://sequel.jeremyevans.net/) instead. It uses the concept of Datasets which I don't think has any equivalent in AR.
Disclaimer: Nobody asked me to advertise for it. I used both AR and Sequel and I found that Sequel is much better to perform complex queries and avoid the N+1 problem.
Did you try find_by_sql method?

ActiveRecord, find all records where associated record EITHER does not exist OR exists with nil status

3 things:
- Logistic has_many RentalRequests through Type_Logistic association table.
- Logistic does not have to have a RentalRequest to exist (i.e., there's no Type_Logistic association present)
- RentalRequest has a status_id column
I would like to find all the Logistic records where EITHER there are no associated RentalRequests OR the associated RentalRequest has a status_id of nil. My current solution is multi-step:
assoc_RR_has_nil_status = Logistic.includes(:rental_requests).where(rental_requests: {status_id: nil}).pluck(:id)
no_assoc_RR = Logistic.includes(:rental_requests).where(rental_requests: {id:nil}).pluck(:id)
inc_logistics_ids = assoc_RR_has_nil_status | no_assoc_RR
#Incomplete_logistics = Logistic.find(inc_logistics_ids.sort)
But I'm wondering if there's a way to do this in one single where or chain of wheres.
Thanks!
EDIT! Updated my code above because the original merge represents an AND not an OR.
Don't be afraid to use SQL. It is a great tool!
Logistic.
joins("LEFT OUTER JOIN type_logistics ON type_logistics.logistic_id = logistics.id").
joins("LEFT OUTER JOIN rental_requests ON type_logistics.rental_request_id = rental_requests.id").
where("rental_requests.id IS NULL OR rental_requests.status_id IS NULL")
If you must avoid SQL:
It is possible to generate the same query as above using just Ruby code (no SQL snippets). For example you could use ARel, such as is suggested in this SO discussion.
ARel is great for constructing queries at a high level of abstraction. For the majority of cases I've encountered it is much simpler to use SQL snippets than ARel. Choose whichever is best for your use case.

Using distinct in a join

I'm still a novice at SQL and I need to run a report which JOINs 3 tables. The third table has duplicates of fields I need. So I tried to join with a distinct option but hat didn't work. Can anyone suggest the right code I could use?
My Code looks like this:
SELECT
C.CUSTOMER_CODE
, MS.SALESMAN_NAME
, SUM(C.REVENUE_AMT)
FROM C_REVENUE_ANALYSIS C
JOIN M_CUSTOMER MC ON C.CUSTOMER_CODE = MC.CUSTOMER_CODE
/* This following JOIN is the issue. */
JOIN M_SALESMAN MS ON MC.SALESMAN_CODE = (SELECT SALESMAN_CODE FROM M_SALESMAN WHERE COMP_CODE = '00')
WHERE REVENUE_DATE >= :from_date
AND REVENUE_DATE <= :to_date
GROUP BY C.CUSTOMER_CODE, MS.SALESMAN_NAME
I also tried a different variation to get a DISTINCT.
/* I also tried this variation to get a distinct */
JOIN M_SALESMAN MS ON MC.SALESMAN_CODE =
(SELECT distinct(SALESMAN_CODE) FROM M_SALESMAN)
Please can anyone help? I would truly appreciate it.
Thanks in advance.
select distinct
c.customer_code,
ms.salesman_code,
SUM(c.revenue_amt)
FROM
c_revenue c,
m_customer mc,
m_salesman ms
where
c.customer_code = mc.customer_code
AND mc.salesman_code = ms.salesman_code
AND ms.comp_code = '00'
AND Revenue_Date BETWEEN (from_date AND to_date)
group by
c.customer_code, ms.salesman_name
The above will return you any distinct combination of Customer Code, Salesman Code and SUM of Revenue Amount where the c.CustomerCode matches an mc.customer_code AND that same mc record matches an ms.salesman_code AND that ms record has a comp_code of '00' AND the Revenue_Date is between the from and to variables. Then, the whole result will be grouped by customer code and salesman name; the only thing that will cause duplicates to appear is if the SUM(revenue) is somehow different.
To explain, if you're just doing a straight JOIN, you don't need the JOIN keywords. I find it tends to convolute things; you only need them if you're doing an "odd" join, like an LEFT/RIGHT join. I don't know your data model so the above MIGHT still return duplicates but, if so, let me know.

JPQL join two entities with no direct relations

I have an issue: When I am trying to join two tables which do not have a foreign key or a direct entity relation through my java code within themselves. I am using the below JPQL query: -
SELECT p FROM P p, OM orgm WHERE p.o.id = orgm.o.id and p.u.id = orgm.u.id and orgm.ma = true and p.u.id = ? AND p.o.id IN (:oId);
But this turns to a MySQL query which has a "cross join" which obviously is expensive.
What I need is to make sure that a similar query gives me an inner join MySQL query between the two tables.
I am trying to make usage of the "WITH" clause but seems that it doesn't work with inner join.
Please revert what can be done in this scenario.
Thanks in advance.

Excluding data using NOT IN in the join using DB2, SQL PL

I need assistance in formulating the correct approach to a query.
I have staff members that I need to give work to. If they're not available on a date, they're excluded from the group of staff members that can get work. I think it's clear what I'm trying to do, but it's incorrect syntax:
INNER JOIN mySchema."STAFF" S
ON RS.STAFF_ID = S.STAFF_ID
AND RS.STAFF_ID NOT IN (SELECT SU.STAFF_ID
FROM mySchema."STAFF_UNAVAIL" SU
WHERE SU.UNAVAIL_DT = OUTSTANDING_DATE)
Any ideas on how one could achieve a NOT IN in a join without actually doing it in the join?
put it in a where clause after the joins
INNER JOIN mySchema."STAFF" S
ON RS.STAFF_ID = S.STAFF_ID
...any other joins...
WHERE RS.STAFF_ID NOT IN (SELECT SU.STAFF_ID
FROM mySchema."STAFF_UNAVAIL" SU
WHERE SU.UNAVAIL_DT = OUTSTANDING_DATE)

Resources