Postgres find tuples by WHERE and COUNT from other table - sql

I have two tables
table a
id
title
year
table b
id
a.id references a(id)
I want to get title, year and count() for all tuples with max(year). I just can't get this to work.
I can do them separate, but whenever i add a WHERE clause it fails.
select title, year, count(*)
from a inner join b on a.id = b.a_id
group by title, year
Something like this would give me list with title year and count. If I try to add a WHERE clause
select title, year, count(*)
from a inner join b on a.id = b.a_id
where year = (select max(year) from a)
group by title, year
Here I get no tuple. I think I almost understand why, but I can't manage to solve it.
Hope someone understands me and can help!

how about this?
WITH a_max_years AS (
SELECT id
FROM a
WHERE year = (SELECT max(year) FROM a)
)
SELECT a.title, a.year, count(1)
FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN a_max_years.id = a.id
GROUP BY b.a_id;
or:
WITH b_counts AS (
SELECT a_id, count(1) as cnt
FROM b
GROUP BY a_id
)
SELECT a.title, a.year, b_counts.cnt
FROM a
INNER JOIN b_counts ON b_counts.a_id = a.id
WHERE a.year = (SELECT max(year) FROM a);
you could also write them as INNER JOIN (SELECT ...) a_max_years, but WITH is more readable

Related

sql find parent table where sum of child column not equal to parent column

hey guys i am trying to find the a bill from billMaster where the sum of billDetails.total is not equal to billMaster.remainingAmount
NB
this is a one to many relationship where one bill can contain more billdetails
i tried the below query and got an sql error
/* SQL Error (1111): Invalid use of group function */
SELECT a.id AS billMAsterId FROM eBillMaster AS a JOIN eBillDetail AS b ON a.id = b.billId
WHERE SUM(b.total) != b.remainAmount GROUP BY a.id
SELECT a.remainAmount, a.id AS BillId FROM eBillMaster a JOIN (SELECT MAX(id) AS id FROM eBillMaster) b JOIN eBillDetail c ON (a.id - c.billId) WHERE SUM(c.total) != a.remainAmount
both queries returned the same error i gess its on how i used the sum on where close.
But the sad thing is that i cant sole the problem..
Any response will be appreciated.
now assume i want to get recent user bill that meets the above condition . Note the billMaster has a column called user_id. how will the new query look like.
An example would make thinks much easier, but I think this is what you want:
SELECT m.id
FROM eBillMaster AS m
JOIN (select billId, sum(total) sumTotal from eBillDetail group by billId) AS d ON m.id = d.billId
WHERE d.sumTotal != m.remainAmount;
Check it out here http://sqlfiddle.com/#!9/89dcfb/7
It's more efficient tou use a cross apply also known as a lateral join - depending on your sql dialect, you haven't tagged your database
select a.id as billMAsterId
from eBillMaster as a
cross apply (
select Sum(total) total
from eBillDetail as b
where b.billId = a.id
)b
where a.remainAmount != b.total
To do the same with having clause you can do
select a.id as billMAsterId
from eBillMaster as a
join eBillDetail as b on a.id = b.billId
group by a.id, a.remainAmount
having Sum(b.total) != a.remainAmount
You need to use the having clause and also group by remainamount
with ebillmaster (id) as(
select 1 from dual union all
select 2 from dual union all
select 3 from dual),
ebilldetail (billid, total, remainamount) as(
select 1, 4, 5 from dual union all
select 2, 3, 4 from dual union all
select 3, 3, 3 from dual union all
select 4, 1, 2 from dual)
SELECT
a.id AS billmasterid
FROM
ebillmaster a
JOIN ebilldetail b ON a.id = b.billid
GROUP BY
a.id,
b.remainamount
HAVING
SUM(b.total) != b.remainamount
This is standard SQL, Aggregated values are filtered in the HAVING clause.
SELECT a.id AS billMAsterId
FROM eBillMaster AS a
JOIN eBillDetail AS b ON a.id = b.billId
GROUP BY a.id, a.remainAmount
HAVING SUM(b.total) != a.remainAmount
Some Sql engines allow to omitt a.remainAmount from GROUP BY when eBillMaster.id is PK.
One more (exotic a bit) option
SELECT a.id AS billMAsterId
FROM eBillMaster AS a
JOIN eBillDetail AS b ON a.id = b.billId
GROUP BY a.id
HAVING SUM(b.total) != AVG(a.remainAmount)
Thank you every one for your feedback it led me to the correct path and the modified query is
SELECT m.propertyId, d.sumTotal, m.remainAmount, m.id
FROM eBillMaster AS m JOIN ( SELECT MAX(id) AS id FROM eBillMaster GROUP BY propertyId) b USING (id)
JOIN (select billId, sum(total) sumTotal from eBillDetail group by billId) AS d ON m.id = d.billId
WHERE d.sumTotal != m.remainAmount;

oracle12c,sql,difference between count(*) and sum()

Tell me the difference between sql1 and sql2:
sql1:
select count(1)
from table_1 a
inner join table_2 b on a.key = b.key where a.id in (
select id from table_1 group by id having count(1) > 1
)
sql2:
select sum(a) from (
select count(1) as a
from table_1 a
inner join table_2 b on a.key = b.key group by a.id having count(1) > 1
)
Why is the output not the same?
The queries are not even similar. They are very different. Let's check the first one:
select count(1)
from table_1 a
inner join table_2 b
on a.key = b.key
where a.id in (
select id from table_1 group by id having count(1) > 1
) ;
You are first making an inner join:
select count(1)
from table_1 a
inner join table_2 b
on a.key = b.key
In this case, you can use count(1), count(id), count(*), it's equivalent. You are counting the common elements in both tables: those ones that have in common the key field.
After that, you are enforcing this:
where a.id in (
select id from table_1 group by id having count(1) > 1
)
In other words, that every "id" of the table_1 must be at least two times in the table_1 table.
And lastly, you are doing this:
select count(1)
In other words, counting those elements. So, translated into english you have done this:
get every record of table_1 and pair with records of table_2 for the id, and get only those that match
for the result above, filter out only the elements whose id of the table_1 appears more than one time
count that result
Let's see what happens with the second query:
select sum(a) from (
select count(1) as a
from table_1 a
inner join table_2 b
on a.key = b.key
group by a.id
having count(1) > 1
);
You are making the same inner join:
select count(1) as a
from table_1 a
inner join table_2 b
on a.key = b.key
but, you are grouping it by the id of the table:
group by a.id
and then filtering out only those elements who appear more than one time:
having count(1) > 1
The result so far are a set of records that have in common the key field in both tables, but grouped by the id: this means that only those fields that are at leas two times in the table_b are outputed of this join. After that, you group by id, collapsing those results into the table_1.id field and counting the result. I presume that very few records will match this strict criteria.
And lastly, you sum all those set.
When you use count(*) you count ALL the rows. The SUM() function is an aggregate function that returns the sum of all or distinct values in a set of values.

Return matched-pair and the non-matched pair for each ID only once

Any help would be appreciated.
I have two sample tables here.
Table A:
ID |Name
123|REG
123|ERT
124|REG
124|ACR
Table B
ID |Name
123|REG
123|WWW
124|REG
124|ADR
Here is the simple join output and I will explain my question in the comments:
*Yes -- I want this row
*No -- I don't want this row
AID|Aname|BID|Bname
123|REG |123|REG --Yes-- Matched-pair for id '123'
123|ERT |123|REG --No--'REG' already had one match. 'ERT' should pair with 'WWW' for id '123'
123|REG |123|WWW --No--The same reason as above
123|ERT |123|WWW --Yes--non-matched pair for id '123'
124|REG |124|REG
124|ACR |124|REG
124|REG |124|ADR
124|ACR |124|ADR
My desired result:
AID|Aname|BID|Bname
123|ERT |123|WWW
123|REG |123|REG
124|REG |124|REG
124|ACR |124|ADR
SQL server 2017.
Thank you in advance.
My approach (Inspired by the post from #The Impaler)
;with CTEall as(
select A.id as AID, A.NAME as Aname, b.id as BID,b.NAME as Bname from A
inner join B on A.id = B.id),
match as (
select A.id as AID, A.NAME as Aname, b.id as BID,b.NAME as Bname
from A inner join B on A.id = B.id and A.NAME = B.NAME)
select *
from CTEall
where Aname not in (select Aname from match where AID = BID)
and Bname not in (select Aname from match where BID = AID)
union all
select * from match
order by 1
Often when you think about the logic you want in a different way, the answer (or at least AN answer) becomes obvious.
I am thinking of your logic this way:
JOIN Table A to Table B such that A.ID=B.ID (always) AND EITHER
A.Name=B.Name OR A.Name doesn't have a Match in B, and B.Name doesn't
have a match in A.
This logic is pretty easy to express in SQL
WHERE a.ID=b.ID
AND (
a.Name=b.Name OR (
NOT EXISTS(SELECT * FROM TableB b2 WHERE b2.ID=a.ID AND b2.Name=a.Name)
AND
NOT EXISTS(SELECT * FROM TableA a2 WHERE a2.ID=b.ID AND a2.Name=b.Name)
)
)
I would do:
with
m as ( -- matched rows
select a.id as aid, a.name as aname, b.id as bid, b.name as bname
from table_a a
join table_b b on a.id = b.id and a.name = b.name
),
l as ( -- unmatched "left rows"
select a.id, a.name,
row_number() over(partition by id order by name) as rn
from table_a a
left join table_b b on a.id = b.id and a.name = b.name
where b.id is null
),
r as ( -- unmatched "right rows"
select b.id, b.name,
row_number() over(partition by id order by name) as rn
from table_b b
left join table_a a on a.id = b.id and a.name = b.name
where a.id is null
)
select aid, aname, bid, bname from m
union all
select l.id, l.name, r.id, r.name
from l
join r on r.id = l.id and r.rn = l.rn
Note: This solution may be a little bit overkill, since matches all unmatched rows when there are multiple ones per ID... something that is not necessary. Per OP comments there always be a single unmatched row per ID.

MS access multiple query left join

I am creating a table in MS ACCESS using multiple sub query in a way such that 1st table contains the whole set and remaining are based on some another condition - here is my code
SELECT a.id,
a.cnt AS Total_Count,
b.cnt AS Income_Count
INTO data
FROM (SELECT id,
Count(*) AS Cnt
FROM test
GROUP BY id) AS a
LEFT JOIN (SELECT id,
Count(*) AS Cnt
FROM test
WHERE inc > 25
GROUP BY id) AS b
ON a.id = b.id;
It works fine. But when I am putting another sub query its not working
SELECT a.id,
a.cnt AS Total_Count,
b,
b.cnt AS Income_Count,
c.cnt AS EXP_Count
INTO data
FROM (SELECT id,
Count(*) AS Cnt
FROM test
GROUP BY id) AS a
LEFT JOIN (SELECT id,
Count(*) AS Cnt
FROM test
WHERE inc > 25
GROUP BY id) AS b
ON a.id = b.id
LEFT JOIN (SELECT id,
Count(*) AS Cnt
FROM test
WHERE exp > 25
GROUP BY id) AS c
ON a.id = c.id;
Can you please help me on this.
SELECT a.id,
a.cnt AS Total_Count,
b, <----------------- What is this? b is an alias for a table, not a field
b.cnt AS Income_Count,
c.cnt AS EXP_Count
INTO data
FROM (SELECT id,
Count(*) AS Cnt
FROM test
GROUP BY id) AS a
LEFT JOIN (SELECT id,
Count(*) AS Cnt
FROM test
WHERE inc > 25
GROUP BY id) AS b
ON a.id = b.id
LEFT JOIN (SELECT id,
Count(*) AS Cnt
FROM test
WHERE exp > 25
GROUP BY id) AS c
ON a.id = c.id;
Remove the 'b' or replace it with an actual field. Be careful, if you use b.id then you will run into issues with trying to create two columns with the same name 'id' you will have to change the name of one of them. I ran this on a sql server an it worked fine without the 'b'.

SQL Subquery to get first record

I need to execute a query something like below.
SELECT TO_CHAR(ROWNUM),
A.Name,
B.Order,
(SELECT * FROM (
SELECT ROUND(LAST_ORDER_AMOUNT,5) FROM ORDERS WHERE ID=A.id AND REQUEST_LEVEL='N' ORDER BY O_DATE DESC)
WHERE ROWNUM =1) AS AMOUNT
FROM Table1 A LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
But this gives me A.ID is invalid error in oracle. I need to get the first record from inner query as it will return multiple records.
Can someone please let me know how can i bind these tables to achieve my goal.
Thank you in advance.
You can rewrite subquery using WITH clause, not exactly sure on syntax but should be something like following.
WITH AmountQuery
AS (
SELECT ID
,ROUND(LAST_ORDER_AMOUNT, 5) AS AmountValue
,ROW_NUMBER() OVER ( ORDER BY O_DATE DESC ) AS RN
FROM ORDERS
WHERE REQUEST_LEVEL = 'N'
)
SELECT TO_CHAR(ROWNUM)
,A.Name
,B.Order
,C.AmountValue
FROM Table1 A
LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
LEFT JOIN AmountQuery C
ON a.ID = c.ID
AND c.RN = 1
here is SQLFiddle to show how it works.
http://sqlfiddle.com/#!4/696b6/36
Probably, LIMIT will do the job for you selecting just one record from the subquery (It worked for me in MySQL. I do not have Oracle, but I think it may be similar). Try something like this:
SELECT TO_CHAR(ROWNUM),
A.Name,
B.Order,
COALESCE( C.AMOUNT ) as AMOUNT,
FROM Table1 A LEFT JOIN Table2 B
ON A.TYPE_CODE = B.ENTITY_TYPE
LEFT JOIN ( SELECT ROUND(LAST_ORDER_AMOUNT,5) AS AMOUNT FROM ORDERS WHERE REQUEST_LEVEL='N' ORDER BY O_DATE DESC ) C ON C.ID = A.id
group by A.id;