Postgresql - how to combine these two queries - postgresql

I try to combine these two queries in one.
the result of these queries is the number of accepted / rejected applications for a given operator.
I want to get such a result - in three column: number of accepted applications , number of rejected applications and operators assigned to it.
select count(applications.id) as number_of_applications, operator_id
from applications
inner join travel p on applications.id = p.application_id
inner join trip_details sp on p.id = sp.trip_id
where application_status ilike '%rejected%'
group by operator_id
order by number_of_applications desc;
select count(applications.id) as number_of_applications, operator_id
from applications
inner join travel p on applications.id = p.application_id
inner join trip_details sp on p.id = sp.trip_id
where application_status ilike '%accepted%'
group by operator_id
order by number_of_applications desc;

With conditional aggregation:
select
sum(case when application_status ilike '%accepted%' then 1 else 0 end) as number_of_applications_accepted,
sum(case when application_status ilike '%rejected%' then 1 else 0 end) as number_of_applications_rejected,
operator_id
from applications
inner join travel p on applications.id = p.application_id
inner join trip_details sp on p.id = sp.trip_id
where (application_status ilike '%rejected%') or (application_status ilike '%accepted%')
group by operator_id;
You can add the ordering that you prefer.

Related

Is it possible to do a "LIMIT 1" on a left join in Postgres?

I have two tables: one for money and attributes surrounding it (e.g. who earnt it) and a child table for the "ledger" - this contains one or more entries that represent the history of money that has moved.
SELECT SUM(pl.achieved)
FROM payout p
LEFT JOIN payout_ledgers pl ON pl.payout_id = p.id
This query works well when there is only one ledger item, but when more are added the SUM will increase. I want to join only the latest row. So hypothetically:
SELECT SUM(pl.achieved)
FROM payout p
LEFT JOIN payout_ledgers pl ON pl.payout_id = p.id ORDER BY pl.ts DESC LIMIT 1
WHERE ...
ORDER BY ...
LIMIT ...
(which sadly doesn't work)
What I have tried:
Using a subquery works, but is painfully slow given the size of the data set (and other omitted properties and where clauses etc.):
SELECT SUM(pl.achieved)
FROM payout p
LEFT JOIN payout_ledgers pl ON pl.payout_id = p.id AND pl.id = (SELECT id FROM payout_ledgers WHERE payout_id = p.id ORDER BY ts DESC LIMIT 1)
Incidentally, I'm unsure why this subquery is so slow (~12 seconds, as opposed to 150ms with no subquery). I would have expected it to be quicker given that we're only selecting based on the foreign key (payout_id).
Another thing I tried was to do a select from the join - my logic being that if we select from small joined dataset instead of the whole table it would be quicker. However I was met with relation "pl" does not exist error:
SELECT SUM(pl.achieved)
FROM payouts p
LEFT JOIN payout_ledgers pl ON pl.payout_id = p.id
WHERE pl.id = (SELECT id FROM pl ORDER BY ts DESC LIMIT 1)
Thank you in advance for any suggestions. I am also open to suggestions for schema changes that could make this type of logic easier, although my preference would be to try and get the query working since the schema is not easy to change on our production environment.
If you're on Postgres 9.4+, you can use a LEFT JOIN LATERAL (docs)
SELECT SUM(sub.achieved)
FROM payout p
LEFT JOIN LATERAL (SELECT achieved
FROM payout_ledgers pl
WHERE pl.payout_id = p.id
ORDER BY pl.ts DESC LIMIT 1) sub ON true
This will return the sum of the "achieved" field in the most recent entry in payout_ledgers for all payouts.
window functions:
-- using row_number()
SELECT SUM(sss.achieved)
FROM (SELECT pl.achieved
, row_number() OVER (PARTITION BY pl.payout_id, ORDER BY pl.ts DESC)
FROM payouts p
JOIN payout_ledgers pl ON pl.payout_id = p.id
) sss
WHERE sss.rn =1
;
-- using last_value()
SELECT SUM(sss.achieved)
FROM (SELECT
, last_value(achieved) OVER (PARTITION BY pl.payout_id, ORDER BY pl.ts ASC) AS achieved
FROM payouts p
JOIN payout_ledgers pl ON pl.payout_id = p.id
) sss
;
BTW: you do not need the LEFT JOIN (adding no value to the SUM does not change the sum)

Using another order when window function result is equal

I'm using the window function rank at the following query:
select *,
rank() over(partition by challenge_id order by total_distance desc nulls last) as classification
from user_challenges uc
left join users u on u.id = uc.user_id
left join profiles p on p.user_id = u.id
where challenge_id = 'de26076b-88ac-466e-8878-89d589c48d1c'
limit 10 offset 0
This query returns the users ranked by the total_distance reached but when two or more users has the total_distance equal instead of ordering by the total_distance I'd like to order by the name of the user. How can I achieve that?

How to filter database table by a multiple join records from another one table but different types?

I have a products table and corresponding ratings table which contains a foreign key product_id, grade(int) and type which is an enum accepting values robustness and price_quality_ratio
The grades accept values from 1 to 10. So for example, how would the query look like, if I wanted to filter the products where minimum grade for robustness would be 7 and minimum grade for price_quality_ratio would be 8?
You can join twice, once per rating. The inner joins eliminate the products that fail any rating criteria,
select p.*
from products p
inner join rating r1
on r1.product_id = p.product_id
and r1.type = 'robustness'
and r1.rating >= 7
inner join rating r2
on r2.product_id = p.product_id
and r2.type = 'price_quality_ratio'
and r2.rating >= 8
Another option is to use do conditional aggregation. This requires only one join, then a group by; the rating criteria are checked in the having clause.
select p.product_id, p.product_name
from products p
inner join rating r
on r.product_id = p.product_id
and r.type in ('robustness', 'price_quality_ratio')
group by p.product_id, p.product_name
having
min(case when r.type = 'robustness' then r.rating end) >= 7
and min(case when r.type = 'price_quality_ratio then r.rating end) >= 8
The JOIN proposed by #GMB would've been my first suggestion as well. If that gets too complicated with having to maintain too many rX.ratings, you can also use a nested query:
SELECT *
FROM (
SELECT p.*, r1.rating as robustness, r2.rating as price_quality_ratio
FROM products p
JOIN rating r1 ON (r1.product_id = p.product_id AND r1.type = 'robustness')
JOIN rating r2 ON (r2.product_id = p.product_id AND r2.type = 'price_quality_ratio')
) AS tmp
WHERE robustness >= 7
AND price_quality_ratio >= 8
-- ORDER BY (price_quality_ratio DESC, robustness DESC) -- etc

Can't solve this SQL query

I have a difficulty dealing with a SQL query. I use PostgreSQL.
The query says: Show the customers that have done at least an order that contains products from 3 different categories. The result will be 2 columns, CustomerID, and the amount of orders. I have written this code but I don't think it's correct.
select SalesOrderHeader.CustomerID,
count(SalesOrderHeader.SalesOrderID) AS amount_of_orders
from SalesOrderHeader
inner join SalesOrderDetail on
(SalesOrderHeader.SalesOrderID=SalesOrderDetail.SalesOrderID)
inner join Product on
(SalesOrderDetail.ProductID=Product.ProductID)
where SalesOrderDetail.SalesOrderDetailID in
(select DISTINCT count(ProductCategoryID)
from Product
group by ProductCategoryID
having count(DISTINCT ProductCategoryID)>=3)
group by SalesOrderHeader.CustomerID;
Here are the database tables needed for the query:
where SalesOrderDetail.SalesOrderDetailID in
(select DISTINCT count(ProductCategoryID)
Is never going to give you a result as an ID (SalesOrderDetailID) will never logically match a COUNT (count(ProductCategoryID)).
This should get you the output I think you want.
SELECT soh.CustomerID, COUNT(soh.SalesOrderID) AS amount_of_orders
FROM SalesOrderHeader soh
INNER JOIN SalesOrderDetail sod ON soh.SalesOrderID = sod.SalesOrderID
INNER JOIN Product p ON sod.ProductID = p.ProductID
HAVING COUNT(DISTINCT p.ProductCategoryID) >= 3
GROUP BY soh.CustomerID
Try this :
select CustomerID,count(*) as amount_of_order from
SalesOrder join
(
select SalesOrderID,count(distinct ProductCategoryID) CategoryCount
from SalesOrderDetail JOIN Product using (ProductId)
group by 1
) CatCount using (SalesOrderId)
group by 1
having bool_or(CategoryCount>=3) -- At least on CategoryCount>=3

Join two queries become one subquery

I want to ask how to combine these two queries become one subquery?
select c.CustomerName, A.Qty
from Customer c join (select s.CustomerID, pd.Date, s.Qty
from Period pd join Sales s on pd.TimeID = s.TimeID) A on c.CustomerID = A.CustomerID
where #Date = A.Date
and
select sum(case when (pd.Date between '2010-03-15' and #Date) then s.Qty else 0 end) as TotalQty
from Period pd full join Sales s on pd.TimeID = s.TimeID full join Customer c on s.CustomerID = c.CustomerID
group by c.CustomerName, c.CustomerID
They should result one table contains these following columns: CustomerName, Qty, and TotalQty. I've tried many ways but they didn't work at all. Really hope your help, thanks.
Answering your question assuming you are looking at the result as one row and not "column" :
select c.CustomerName, A.Qty,(select sum(case when (pd.Date between '2010-03-15' and #Date) then s.Qty else 0 end)
from Period pd full join Sales s on pd.TimeID = s.TimeID full join Customer c on s.CustomerID = c.CustomerID
group by c.CustomerName, c.CustomerID
) as TotalQty
from Customer c join (select s.CustomerID, pd.Date, s.Qty
from Period pd join Sales s on pd.TimeID = s.TimeID) A on c.CustomerID = A.CustomerID
where #Date = A.Date
If you still want the result as a single column , google "row to column transpose"