Postgresql query with left join and having - sql

Postgresql 9.1: I have a query that must return the values of a second table only if the aggregate function SUM of two columns is greater than zero.
This is the data:
Table a
id
---
1
2
3
Table b
id fk(table a)
---------------
1 1
2 null
3 3
Table c
id fk(table b) amount price
-----------------------------------
1 1 1 10 --positive
2 1 1 -10 --negative
3 3 2 5
As you can see, table b has some ids from table a, and table c can have 1 or more references to table b, table c is candidate to be retrieved only if the sum(amount * price ) > 0.
I wrote this query:
SELECT
a.id, b.id, SUM(c.amount * c.price) amount
FROM
tablea a
LEFT JOIN
tableb b ON b.fk = a.id
LEFT JOIN
tablec c ON c.fk = b.id
GROUP BY
a.id, b.id
HAVING
SUM(c.amount * c.price) > 0
But this query is not retrieving all rows from table a just the row 1 and I need the two rows. I understand this is happening because of the HAVING clause but I don't know how to rewrite it.
Expected result
a b sum
------------------
1 null null -- the sum of 1 * 10 (rows 1 and two) = 0 so its not retrieved.
2 null null -- no foreign key in second table
3 3 10 -- the sum of 2 * 5 (row 3) > 0 so it's ok.

Try this:
SELECT A.ID, B.ID, C.ResultSum
FROM TableA A
LEFT JOIN TableB B ON (B.FK = A.ID)
LEFT JOIN (
SELECT FK, SUM(Amount * Price) AS ResultSum
FROM TableC
GROUP BY FK
) C ON (C.FK = B.ID) AND (ResultSum > 0)
See demo here.

Related

Count elements in table a that have two exactly matching elements in table b

I have two tables and need to get a count of all entries from table A that have two specific matches in table B. Table B has tables A's Id as a foreign key.
Table a
ID Name
1 Foo
2 Bar
3 John
4 Jane
Table b
aID Value
1 12
1 12
2 8
3 8
3 12
4 12
4 8
I now need a count of all names in table A that have both value 8 AND 12 in table B at least once.
SELECT COUNT(*) FROM a join b on a.id = b.aId where b.value = 8 and b.value = 12
gets me 0 results. The correct result should be 2 (John and Jane).
edit:
Obviously, #Larnu is correct and 8 will never be 12.
Also, I should have clarified that there can be two or more of a single value in table B for any table A id, but none of the other (e.g. 8 twice but no 12). I updated the table to reflect that.
You can use EXISTS and HAVING:
SELECT COUNT(*) FROM a
WHERE EXISTS(SELECT b.aID FROM b
WHERE a.ID = b.aID
GROUP BY b.aID
HAVING COUNT(*) = 2)
If you want specifically value = 8 or 12 then add AND b.value IN(8,12) to the inner query
A subquery to get the number of times 8 and 12 appear for each row will do the trick:
select count(id) from
(select id, sum(case when b.Value = 8 then 1 else 0 end) as ct8,
sum(case when b.Value = 12 then 1 else 0 end) as ct12
from a inner join b on a.id = b.aID
group by a.id) as t
where ct8 >= 1 and ct12 >=1
Fiddle
Joining is not the answer here. You need a WHERE clause that includes a correlated subquery that checks your condition using COUNT() or EXISTS(). One of the following should do.
SELECT COUNT(*) FROM A
WHERE (SELECT COUNT(*) ​FROM B ​WHERE B.aID = A.ID ​AND B.VALUE IN (8, 12)) = 2
SELECT COUNT(*) FROM A
WHERE EXISTS(SELECT * FROM B WHERE B.aID = A.ID AND B.VALUE = 8)
AND EXISTS(SELECT * FROM B WHERE B.aID = A.ID AND B.VALUE = 12)

How to select rows by max value from another column in Oracle

I have two datasets in Oracle Table1 and Table2.
When I run this:
SELECT A.ID, B.NUM_X
FROM TABLE1 A
LEFT JOIN TABLE2 B ON A.ID=B.ID
WHERE B.BOOK = 1
It returns this.
ID NUM_X
1 10
1 5
1 9
2 2
2 1
3 20
3 11
What I want are the DISTINCT ID where NUM_X is the MAX value, something like this:
ID NUM_x
1 10
2 2
3 20
You can use aggregation:
SELECT A.ID, MAX(B.NUM_X)
FROM TABLE1 A LEFT JOIN
TABLE2 B
ON A.ID = B.ID
WHERE B.BOOK = 1
GROUP BY A.ID;
If you wanted additional columns, I would recommend window functions:
SELECT A.ID, MAX(B.NUM_X)
FROM TABLE1 A LEFT JOIN
(SELECT B.*,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY NUM_X DESC) as seqnum
FROM TABLE2 B
) B
ON A.ID = B.ID AND B.seqnum = 1
WHERE B.BOOK = 1
GROUP BY A.ID;

How to generate loop kind of behaviour in SQL query to fetch multiple queries & compare results?

I am unable to generate a looping kind of behaviour in a SQL query.
I am having two tables:
Table A
Id Brand Prod_Id Alt_Prod_Id
1 A 2 5
2 B 3 9
3 C 5 9
Table B
Id Prod_Id Rate
1 2 5
2 3 9
2 5 7
2 9 9
Rate in Table B needs to be looked up for each brands Prod_ID & Alt_Prod_Id & select the least value between 2 found value
The expected result / output is:
Brand Min_Prod_Val
A 5
B 9
C 7
Can this be done in a query?
Thanks!
You could join tableb twice (once for prod_id, another for alt_prod_id), and then select the smallest rate:
select
a.brand,
least(b1.rate, b2.rate) min_prod_val
from tablea a
inner join tableb b1 on b1.prod_id = a.prod_id
inner join tableb b2 on b2.prod_id = a.alt_prod_id
It is unclear which database you are using. If that's SQL Server: it does not support least(), so you need a case expression:
case when b1.rate < b2.rate then b1.rate else b2.rate end min_prod_val
You can use a single join and GROUP BY the brand:
SELECT a.Brand,
MIN( b.rate ) AS min_prod_val
FROM TableA A
INNER JOIN TableB b
ON ( b.prod_id IN ( a.prod_id, a.alt_prod_id ) )
GROUP BY a.Brand
Or you can use a correlated sub-query:
SELECT a.Brand,
(
SELECT MIN( rate )
FROM TableB b
WHERE b.prod_id IN ( a.prod_id, a.alt_prod_id )
) AS min_prod_val
FROM TableA A
db<>fiddle

How to find rows missing by every group

I have two tables:
Input:
A:
ID col
1 a
1 b
1 c
2 a
2 b
3 x
4 y
B
ID col
1 a
1 b
2 a
I want to for every ID in B, find rows in A but not in B by every ID.
Output:
ID Col
1 c
2 b
What I tried:
left/right join. I am trying something like select * from a left join b on a.id = b.id where b.id is null
except. select * from a except select * from b
but not sure how to modify it.
Assuming you want the values in A for which there are records in B with the same ID, but not the same col, you could do:
select
a.ID,
a.col
from A
left join B
on b.ID = a.ID and b.col = a.col
where A.ID in (select distinct ID from B) -- B contains this `ID` somewhere...
and B.ID is null -- ...but not with the same `col`
Test it here.
Using a combination of exists and not exists.
select *
from a
where exists (select 1 from b where a.id=b.id) --id check
and not exists (select 1 from b where a.id=b.id and a.col=b.col) -- col check

SELECT all rows where sum of count for this id is not 0

I'm querying an access db from excel. I have a table similar to this one:
id Product Count
1 A 0
1 B 5
3 C 0
2 A 0
2 B 0
2 C 5
3 A 6
3 B 5
3 C 7
From which I'd like to return all the rows (including the ones where count for that product is 0) where the sum of the count for this ID is not 0 and the product is either A or B. So from the above table, I would get:
id Product Count
1 A 0
1 B 5
3 A 6
3 B 5
The following query gives the right output, but is quite slow (takes almost a minute when querying from a somewhat small 7k row db), so I was wondering if there is a more efficient way of doing it.
SELECT *
FROM [BD$] BD
WHERE (BD.Product='A' or BD.Product='B')
AND BD.ID IN (
SELECT BD.ID
FROM [BD$] BD
WHERE (Product='A' or Product='B')
GROUP BY BD.ID
HAVING SUM(BD.Count)<>0)
Use your GROUP BY approach in a subquery and INNER JOIN that back to the [BD$] table.
SELECT BD2.*
FROM
(
SELECT BD1.ID
FROM [BD$] AS BD1
WHERE BD1.Product IN ('A','B')
GROUP BY BD1.ID
HAVING SUM(BD1.Count) > 0
) AS sub
INNER JOIN [BD$] AS BD2
ON sub.ID = BD2.ID;
IN() statement can perform badly a lot of times, you can try EXISTS() :
SELECT * FROM [BD$] BD
WHERE BD.Product in('A','B')
AND EXISTS(SELECT 1 FROM [BD$] BD2
WHERE BD.id = BD2.id
AND BD2.Product in('A','B')
AND BD2.Count > 0)
If you are looking for the records where the sum of the count for the id is non-zero, then at least one non-unique id must have a count that is non-zero.
SELECT *
FROM [BD$] BD
WHERE BD.Product IN ('A', 'B')
AND BD.ID IN (
SELECT DISTINCT b.ID
FROM [BD$] b
WHERE b.Product IN ('A', 'B')
AND b.Count<>0
)