Select first row from join with grouped data - sql

I have two separate queries which I am trying to efficently join.
Query 1:
Select Id From Accounts Where Status='OPEN' and Product = 'Product A'
Query 2:
Select AccountId From Transactions Group By AccountId Having Count(*) > 20;
Table transactions hold millions of rows.
What I want to achieve is to return the first account in an open status with product A having more than 20 transactions.
So far i got this, but it is not very efficent due to full table scan of transaction table:
Select A.Id From Accounts A
Left Outer Join (Select AccountId From Transactions Group By AccountId Having Count(*) > 20 ) T on A.Id=T.AccountId
Where A.Status='OPEN' and A.Product = 'Product A'
And rownum = 1
How do I optimize this query?

Would this be better, filtering the specific accounts and grouping ??
Select AccountId From Transactions where AccountId in (Select id From Accounts Where Status='OPEN' and Product = 'Product A') Group By AccountId Having Count(*) > 20;

Related

Get active and total bookings (from 1 table) for every user in users table

I have 2 tables:
users:
username (pk)
bookings:
username (fk)
status (A = Active, C = Cancelled , D = DONE)
I'm willing to show user details along with with their count of active and total bookings (where total bookings will be all the entries in "bookings" table for a particular user).
Table to show:
username, active bookings (count), total bookings (count)
Currently I'm unable to make an efficient query for this.
My DB is postgresql.
Please assist.
Thank you
As you are using PostgreSQL you can take the advantage of Filter() clause. Also you have to use Left Join because you want the details for every user from user table. So Write your query like below:
select
t1.username,
count(*) filter (where t2.status='A') as "Active_Bookings",
count(t2.*) as "Total_Bookings"
from users t1 left join bookings t2 on t1.username=t2.username
group by 1
Edit as per comment:
Filter clause is supported by Postgresql and SQLite. For others count with case will do the thing. Below query should work for almost every other database.
select
t1.username,
count(case when t2.status='A' then 1 end) as "Active_Bookings",
count(t2.*) as "Total_Bookings"
from users t1 left join bookings t2 on t1.username=t2.username
group by t1.username
you can use sum(case when t2.status='A' then 1 else 0 end) as "Active_Bookings" also.
You can try the below -
select u.username,count(*) as total_booking,
count(case when status='Active' then 1 end) as active_bookings
from users u join bookings b on u.username=b.username
group by u.username
Not very sure I udnerstood your question but based on the input you gave try this , this should work
select x.username,active_bookings,total_bookings from (
(select username, count(status) as active_bookings from bookings where status='A' group by username)x join (select username,count(status) as total_bookings from bookings group by username)y on x.username=y.username);

Join 2 tables on multiple case conditions

I am using pgAdmin on a Postgres db. I am trying to achieve the following result (amounts are random):
In order to do that, I need to query the 2 tables: accounts and transactions
I am not sure how to get the sum(amount) results into 1 column. I have tried the following:
select SUM(
CASE WHEN debit_account_id = 1 then amount
when credit_account_id = 1 then amount * (-1) else 0 end),
SUM(
CASE WHEN debit_account_id = 2 then amount
when credit_account_id = 2 then amount * (-1) else 0 end)
from transactions
where entity_id = 1
and so on up to account_id 6. This will give me the correct sums for each account but each result is in new column. How I can combine this so the results looks like in example above?
You can use UNION ALL.
select debit_account_id account_id, -amount from transactions
union all
select credit_account_id account_id, amount from transactions;
now you have data together in one column
I'd sum the debits and the credits for each account in different queries and join them on the accounts table:
SELECT account_name, sum_credut - sum_debit AS balance
FROM accounts a
JOIN (SELECT credit_account_id, SUM(amount)
FROM transfer
GROUP BY credit_account_id) c ON a.id = c.credit_account_id
JOIN (SELECT debit_account_id, SUM(amount)
FROM transfer
GROUP BY debit_account_id) d ON a.id = d.debit_account_id
I would recommend a lateral joins for this:
select a.account_name,
sum(v.signed_amount) as total_amount
from transactions t left join lateral
(values (t.debit_account_id, t.amount),
(t.credit_account_id, - t.amount)
) v(account_id, signed_amount) join
account a
on a.id = v.account_id
group by a.account_name;
I don't see entity_id in any of the tables, so I don't know where that comes from.

join 2 foreign key using subquery

help me solve this, i am intended to join 2 table for 2 different foreign key within the same column, table snapshot provide below:
users table
transactions table
i want to return top 5 based on transactions amount from high-low alongside to display transactions id, investor id, investor name, borrower id, borrower name, amount
the following run properly but contains no investor name
select top 5 t.id,
investor_id,
borrower_id,
username as BorrowerName,
amount
from transactions t join users u on t.borrower_id = u.id
order by t.amount desc;
minus investor name result table
while if i do subquery resulting error
select top 5 t.id,
investor_id,
(select username from users join transactions on users.id =
transactions.investor_id) investorName,
borrower_id,
username BorrowerName,
amount
from transactions t join users u on t.borrower_id = u.id
order by t.amount desc;
select top 5 t.id,
investor_id, ui.username as InvestorName,
borrower_id, ub.username as BorrowerName,
amount
from transactions t
join users ub on t.borrower_id = ub.id
join users ui on t.investor_id = ui.id
order by t.amount desc;
The Subquery must be scalar. i.e. return a single value, but you currently return a result set.
select top 5 t.id,
investor_id,
(-- Correlated Scalar Subquery, returns a single value
select username
from users
WHERE users.id = transactions.investor_id) investorName,
borrower_id,
username BorrowerName,
amount
from transactions t join users u on t.borrower_id = u.id
order by t.amount desc;
Isn't this what you want? Two joins on users table
SELECT TOP 5
investor_id,
investors.username InvestorName,
borrower_id,
borrowers.username BorrowerName,
amount
FROM
transactions
INNER JOIN users investors ON (transactions.investor_id = investors.id)
INNER JOIN users borrowers ON (transactions.borrower_id = borrowers.id)
ORDER BY
amount desc;
I would recommend against using subqueries in this case, since the database will be forced to perform two sequential scans in a nested loop for each row.

Aggregate after join without duplicates

Consider this query:
select
count(p.id),
count(s.id),
sum(s.price)
from
(select * from orders where <condition>) as s,
(select * from products where <condition>) as p
where
s.id = p.order;
There are, for example, 200 records in products and 100 in orders (one order can contain one or more products).
I need to join then and then:
count products (should return 200)
count orders (should return 100)
sum by one of orders field (should return sum by 100 prices)
The problem is after join p and s has same length and for 2) I can write count(distinct s.id), but for 3) I'm getting duplicates (for example, if sale has 2 products it sums price twice) so sum works on entire 200 records set, but should query only 100.
Any thoughts how to sum only distinct records from joined table but also not ruin another selects?
Example, joined table has
id sale price
0 0 4
0 0 4
1 1 3
2 2 4
2 2 4
2 2 4
So the sum(s.price) will return:
4+4+3+4+4+4=23
but I need:
4+3+4=11
If the products table is really more of an "order lines" table, then the query would make sense. You can do what you want by in several ways. Here I'm going to suggest conditional aggregation:
select count(distinct p.id), count(distinct s.id),
sum(case when seqnum = 1 then s.price end)
from (select o.* from orders o where <condition>) s join
(select p.*, row_number() over (partition by p.order order by p.order) as seqnum
from products p
where <condition>
) p
on s.id = p.order;
Normally, a table called "products" would have one row per product, with things like a description and name. A table called something like "OrderLines" or "OrderProducts" or "OrderDetails" would have the products within a given order.
You are not interested in single product records, but only in their number. So join the aggregate (one record per order) instead of the single rows:
select
count(*) as count_orders,
sum(p.cnt) as count_products,
sum(s.price)
from orders as s
join
(
select order, count(*) as cnt
from products
where <condition>
group by order
) as p on p.order = s.id
where <condition>;
Your main problem is with table design. You currently have no way of knowing the price of a product if there were no sales on it. Price should be in the product table. A product cost a certain price. Then you can count all the products of a sale and also get the total price of the sale.
Also why are you using subqueries. When you do this no indexes will be used when joining the two subqueries. If your joins are that complicated use views. In most databases they can indexed

Select sum and inner join

I have two tables
Bills: id amount reference
Transactions: id reference amount
The following SQL query
SELECT
*,
(SELECT SUM(amount)
FROM transactions
WHERE transactions.reference = bils.reference) AS paid
FROM bills
GROUP BY id HAVING paid<amount
was meant to some rows from table Bills, adding a column paid with the sum of amount of related transactions.
However, it only works when there is at least one transaction for each bill. Otherwise, no line for a transaction-less bill is returned.
Probably, that's because I should have done an inner join!
So I try the following:
SELECT
*,
(SELECT SUM(transactions.amount)
FROM transactions
INNER JOIN bills ON transactions.reference = bills.reference) AS paid
FROM bills
GROUP BY id
HAVING paid < amount
However, this returns the same value of paid for all rows! What am I doing wrong ?
Use a left join instead of a subquery:
select b.id, b.amount, b.paid, sum(t.amount) as transactionamount
from bills b
left join transactions t on t.reference = b.reference
group by b.id, b.amount, b.paid
having b.paid < b.amount
Edit:
To compare the sum of transactions to the amount, handle the null value that you get when there are no transactions:
having isnull(sum(t.amount), 0) < b.amount
You need a RIGHT JOIN to include all bill rows.
EDIT
So the final query will be
SELECT
*,
(SELECT SUM(transactions.amount)
FROM transactions
WHERE transactions.reference = bills.reference) AS paid
FROM bills
WHERE paid < amount
I knows this thread is old, but I came here today because I encountering the same problem.
Please see another post with same question:
Sum on a left join SQL
As the answer says, use GROUP BY on the left table. This way you get all the records out from left table, and it sums the corresponding rows from right table.
Try to use this:
SELECT
*,
SUM(transactions.sum)
FROM
bills
RIGHT JOIN
transactions
ON
bills.reference = transactions.reference
WHERE
transactions.sum > 0
GROUP BY
bills.id