Scenario:
Invoice has many purchase_orders and purchase_orders has many invoices. Intermediate table is invoice_links which has foreign key invoice_id, purchase_order_id.
Tech Stack
Rails 5.x, Postgresql
Here is my sample data
invoices
id | name | status |
---|---|---|
100 | sample.pdf | archived |
101 | sample1.pdf | archived |
102 | sample2.pdf | archived |
103 | sample2.pdf | active |
104 | sample2.pdf | active |
purchase_orders
id | title |
---|---|
1 | first po |
2 | second po |
3 | third po |
4 | fourth po |
npayment_links
id | purchase_order_id | invoice_id |
---|---|---|
1 | 1 | 100 |
2 | 1 | 101 |
3 | 1 | 102 |
4 | 2 | 100 |
5 | 2 | 103 |
6 | 3 | 104 |
7 | 4 | 100 |
I am expecting query which returns all purchase_orders whose all invoices are archived.
- If you see npayment_links
- purchase_orders with id=1 is associated with 3 invoices (100, 101, 102), which has all archived invoices.
- purchase_orders with id=2 is associated with 2 invoices (100, 103), which has archived and active invoices.
- purchase_orders with id=3 is associated with 1 invoice (104), which has active invoice.
- purchase_orders with id=4 is associated with 1 invoice (100), which has archived invoice.
I'm searching for Sql query which returns PO list which contains all archived invoices.
Expected purchase_orders
id | title |
---|---|
1 | first po |
4 | fourth po |
I have achieved above issue with Rails AR way. But, I'm searching for some Sql query to achieve this:
Invoice.find(100).purchase_orders.each do |po|
if po.invoices.all? { |inv| inv.cancelled? }
# po.update(status: :done) # I will do some operation here. And If there are 1000s of data in which each PO again have many invoices, I might feel this will add query complexity. So, I am searching for some optimized solution here.
end
end
Any feedback would be appreciated.
CodePudding user response:
You need to use JOINS to connect your tables. Then you can simply query for all purchase orders that have archived invoices and compare that to second select with EXCEPT
that would give you POs that have active invoices. By using EXCEPT as a result you will get all rows that appear in first select without those that appear in second select.
SELECT
po.*
FROM
purchase_orders po
JOIN npayment_links pl on po.id = pl.purchase_order_id
JOIN invoices i on pl.invoice_id = i.id
WHERE i.status LIKE 'archived'
EXCEPT
SELECT
po.*
FROM
purchase_orders po
JOIN npayment_links pl on po.id = pl.purchase_order_id
JOIN invoices i on pl.invoice_id = i.id
WHERE i.status LIKE 'active'
CodePudding user response:
Maybe not the most optimized solution but the following should work:
PurchaseOrder.where.not(id:
PurchaseOrder
.select(:id)
.joins(:invoices)
.where.not(invoices: {status: 'archived'})
)
The thought process is find all the Purchase Orders who's id is not in a list of Purchase Order ids with a status that is something other than archived and this will result in the following SQL
SELECT
purchase_orders.*
FROM
purchase_orders
WHERE
purchase_orders.id NOT IN (
SELECT
purchase_orders.id
FROM
purchase_orders
INNER JOIN invoice_links ON invoice_links.purchase_order_id = purchase_orders.id
INNER JOIN invoices ON invoices.id = invoice_links.invoice_id
WHERE
invoices.status <> 'archived'
)
This will also return Purchase Orders that do not have any invoices. If having an invoice is also a requirement you can simply add joins(:invoices)
e.g.
PurchaseOrder
.joins(:invoices)
.where.not(id:
PurchaseOrder
.select(:id)
.joins(:invoices)
.where.not(invoices: {status: 'archived'})
)
Note: Your question states the Join Table is invoice_links
and then references npayment_links
so I am unsure which is the actual join table. For my example I will assume the join table is invoice_links
as that makes more logical sense; however, provided the associations are setup correctly in the ORM, this assumption has no impact on the functionality of the proposed solution.