SQL - Sum of query not true - sql

I have this query;
SELECT l.Name, COALESCE(SUM(A.Count), 0) AS A, COALESCE(SUM(B.Count), 0) AS B
FROM List l
LEFT JOIN A ON A.Name = l.Name
LEFT JOIN B ON B.Name = l.Name
GROUP BY l.Name
ORDER BY l.Name
And query results not true.
Sum of Product3 in Table A is not true.
Demo : https://www.db-fiddle.com/f/rdKLkyaeEsi8bPcNPkUnTE/4

You could sum separately for A and B and then combine results:
SELECT Name, MAX(A) AS A, MAX(B) AS B
FROM (
SELECT l.Name, SUM(A.Count) AS A, 0 AS B
FROM List l
LEFT JOIN A ON A.Name = l.Name
GROUP BY l.Name
UNION ALL
SELECT l.Name, 0 AS A, SUM(B.Count)AS B
FROM List l
LEFT JOIN B ON B.Name = l.Name
GROUP BY l.Name) sub
GROUP BY Name
ORDER BY Name;
db-fiddle.com demo

You should be aggregating the A and B tables in separate subqueries:
SELECT
l.Name,
COALESCE(a.cnt, 0) AS a_cnt,
COALESCE(b.cnt, 0) AS b_cnt
FROM List l
LEFT JOIN
(
SELECT Name, SUM(Count) AS cnt
FROM A
GROUP BY Name
) a
ON l.Name = a.name
LEFT JOIN
(
SELECT Name, SUM(Count) AS cnt
FROM B
GROUP BY Name
) b
ON l.Name = b.name;
The problem with your current approach is that the double join to the A and B tables is likely resulting in double counting. By using separate subqueries we avoid this problem.

In your original question on this subject, I suggested correlated subqueries. These are probably the easiest way to accomplish what you want:
select l.name,
(select sum(a.count)
from a
where a.name = l.name
) as a,
(select sum(b.count)
from b
where b.name = l.name
) as b
from list l;

You should check null values before sum() not after.
SELECT l.Name, SUM(COALESCE(A.Count, 0)) AS A, SUM(COALESCE(B.Count, 0)) AS B
FROM List l
LEFT JOIN A ON A.Name = l.Name
LEFT JOIN B ON B.Name = l.Name
GROUP BY l.Name
ORDER BY l.Name

Related

Can i do a inner "With" inside another "With" in SQL?

I'm trying to use multiple SQL With clauses.
The reason of me using multiple With is that I'm sending this SQL to a AS400 project. The With TEMP has to be obligatory instead of Temp2 that has to be optional.
I can't figure out how to do it. This SQL still throws an error:
With Temp2 As
(
With Temp As
(
Select Name, Surname, Age
From People
Where Age > 18
)
Select A.*, B.*
From Temp A
Left Join City B on B.Name = A.Name
and B.Surname = A.Surname
Where B.City = "Venice"
)
Select *
From Temp2 C
Left Join State D on D.City = C.City
I'd like to understand how I can do something like that.
Yes, any CTE can reference a CTE that is created before it. The first CTE must be prefaced by "With" and terminated with a comma, which allows for another CTE to be created.
with temp as
(
select name, surname, age
from people
where age > 18
),
temp2 as
(
select a.*, b.*
from temp a
left join city b
on b.name = a.name
and b.surname = a.surname
where b.city = "Venice"
)
select *
from temp2 c
left join state d
on d.city = c.city
;
This is functionally equivalent to the query below, which does not require any CTE's.
select *
from people as a
join city b
on b.name = a.name
and b.surname = a.surname
and b.city = "Venice"
left join state c
on c.city = b.city
where a.age > 18
;
For what you are describing, you shouldn't need either CTEs or subqueries. Just use regular JOINs.
SELECT p.Name, p.Surname, p.Age, C.City, s.StateName, s.CountryName
FROM People p
INNER JOIN City c ON p.Name = c.Name
AND p.Surname = c.Surname
AND c.City = 'Venice'
LEFT OUTER JOIN State s ON c.City = s.City
WHERE p.Age > 18
See https://dbfiddle.uk/?rdbms=db2_11.1&fiddle=6d16c8325ee1da354588ddddc75bb162 for demo.

Group by and aggregate by multiple columns

Example tables
taccount
tuser
tproject
What I want to achieve:
accountName count(u.id) count(p.id)
-----------------------------------
Account A 1 1
Account B 1 1
Account C 2 3
In other words I want a single query to join these tables together and count user's and project's per account
I tried:
SELECT
a.name as "accountName",
count(u.name),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY u.name, a.name, p.id
But it's not grouping by account. It's giving me the following result
Any advice?
You can try below
SELECT
a.name as "accountName",
count(distinct u.name),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY a.name
When you do Aggregate Function and If there are Column are not do Aggregate you must put in your Group By, because Aggregate functions perform a calculation on a set of rows and return a single row.
SELECT
a.name as "accountName",
count(distinct u.name),
count(p.id)
FROM
"taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY
a.name
So you need just Group By your column "accountName"
change your group by column name
SELECT
a.name as "accountName",
count(distinct u.account_id),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY a.name
this will work:
select a.name,count(u.id),count(p.id) from
taccount a,tuser b, tproject where
a.id=b.account_id and
b.id=c.admin_id
group by a.name;

Find the total number of points for each D, if there is no D in P, then points should be 0

I want to find the total number of points for each D.
If there is no instance of D in P, then the points should return as 0.
My tables are:
P
code (referencing D.id), c_code (referencing C.id)
D
id, name
C
id
name
points
My query is:
SELECT D.id, SUM(C.points)
FROM D JOIN P ON D.id=P.code JOIN C ON P.c_code=C.id
GROUP BY D.id
HAVING CASE WHEN D.id NOT IN (SELECT *
FROM P p1
WHERE p1.code=D.id) THEN '0' end;
You need Outer Joins to get non-matching rows plus coalesce to return zero instead of NULL:
SELECT D.id, coalesce(SUM(C.points), 0)
FROM D LEFT JOIN P ON D.id=P.code
LEFT JOIN C ON P.c_code=C.id
GROUP BY D.id
You can use outer joins, as in:
select
d.id, sum(c.points)
from d
left join p on p.code = d.id
left join c on c.id = p.c_code
group by d.id

SQL - alternative

The below query gives me list of count values:
SELECT books.name as a,
COUNT(library.staff)as b
FROM library, books
WHERE library.staff = books.id
GROUP BY books.name;
How do i get the max value of output
Without window functions or limit:
with data as (
select books.name as a,
COUNT(library.staff) as b
FROM library
JOIN books ON library.staff = books.id
GROUP BY books.name
)
select *
from data
where b = (select max(b) from data);
But maybe you'll now add another requirement that says "no common table expressions":
select books.name as a,
count(library.staff) as b
from library l
join books b on l.staff = b.id
group by b.name
having count(l.staff) = (select max(cnt)
from (
select count(*) cnt
from library l
join books b on l.staff = b.id
group by b.name) t
);
If books.id is a primary (or unique) key, the second statement can be slightly simplified using:
select l.name as a,
count(l.staff) as b
from library l
join books ON l.staff = b.id
group by b.name
having count(l.staff) = (select max(cnt)
from (
select count(*) cnt
from library l
group by l.staff) t
);
Can you use window functions?
SELECT a, b
FROM (
SELECT books.name as a,
count(library.staff) as b
row_number() OVER (ORDER BY count(library.staff) DESC) as rn
FROM library, books
WHERE library.staff = books.id
GROUP BY books.name
) s
WHERE rn=1;

Query returning too many results

SQL query that returns expected 29 results for a.id = 366
select a.name, c.name, MAX(B.date), MAX(b.renew_date) as MAXDATE
from boson_course c
inner join boson_coursedetail b on (c.id = b.course_id)
inner join boson_coursedetail_attendance d on (d.coursedetail_id = b.id)
inner join boson_employee a on (a.id = d.employee_id)
where a.id = 366
GROUP BY a.name, c.name
order by MAX(b.renew_date), MAX(b.date) desc;
SQL code below that returns 34 results, multiple results where two different Provides supplied the same course. I know these extra results are because I added e.name to the list to be returned. But all that is needed is the 29 entries with the latest date and Providers names.
select a.name, c.name, e.name, MAX(B.date), MAX(b.renew_date) as MAXDATE
from boson_course c
inner join boson_coursedetail b on (c.id = b.course_id)
inner join boson_coursedetail_attendance d on (d.coursedetail_id = b.id)
inner join boson_employee a on (a.id = d.employee_id)
inner join boson_provider e on b.provider_id = e.id
where a.id = 366
GROUP BY a.name, c.name, e.name
order by MAX(b.renew_date), MAX(b.date) desc;
Can anyone rework this code to return a single DISTINCT Provider name with the MAX(renew_date) for each course.
This returns exactly one row per distinct combination of (a.name, c.name):
The one with the latest renew_date.
Among these, the one with the latest date (may differ from global max(date)!).
Among these, the one with the alphabetically first e.name:
SELECT DISTINCT ON (a.name, c.name)
a.name AS a_name, c.name AS c_name, e.name AS e_name
, b.renew_date, b.date
FROM boson_course c
JOIN boson_coursedetail b on c.id = b.course_id
JOIN boson_coursedetail_attendance d on d.coursedetail_id = b.id
JOIN boson_employee a on a.id = d.employee_id
JOIN boson_provider e on b.provider_id = e.id
WHERE a.id = 366
ORDER BY a.name, c.name
, b.renew_date DESC NULLS LAST
, b.date DESC NULLS LAST
, e.name;
The result is sorted by a_name, c_name first. If you need your original sort order, wrap this in a subquery:
SELECT *
FROM (<query from above>) sub
ORDER BY renew_date DESC NULLS LAST
, date DESC NULLS LAST
, a_name, c_name, e_name;
Explanation for DISTINCT ON:
Select first row in each GROUP BY group?
Why DESC NULL LAST?
PostgreSQL sort by datetime asc, null first?
Aside: Don't use basic type names like date ad column names. Also, name is hardly ever a good name. As you can see, we have to use aliases to make this query useful. Some general advice on naming conventions:
How to implement a many-to-many relationship in PostgreSQL?
Try using distinct on:
select distinct on (a.name, c.name, e.name), a.name, c.name, e.name,
B.date, b.renew_date as MAXDATE
from boson_course c
inner join boson_coursedetail b on (c.id = b.course_id)
inner join boson_coursedetail_attendance d on (d.coursedetail_id = b.id)
inner join boson_employee a on (a.id = d.employee_id)
inner join boson_provider e on b.provider_id = e.id
where a.id = 366
ORDER BY a.name, c.name, e.name, B.date desc
order by MAX(b.renew_date), MAX(b.date) desc;