SQL How to combine MAX and COUNT using inner query properly - sql

I'm having a problem with selecting rows only with maximum values from column ProblemsAmount, which represents COUNT(*) from inner query. It looks like:
PersonID | PersonName | ProblemID | ProblemsAmount
1 | Johny | 1 | 10
1 | Johny | 2 | 5
1 | Johny | 3 | 18
2 | Sara | 4 | 2
2 | Sara | 5 | 12
3 | Katerina | 6 | 17
3 | Katerina | 7 | 2
4 | Elon | 8 | 20
5 | Willy | 9 | 6
5 | Willy | 10 | 2
What I want to get:
PersonID | PersonName | ProblemID | ProblemsAmount
1 | Johny | 3 | 18
2 | Sara | 5 | 12
3 | Katerina | 6 | 17
4 | Elon | 8 | 20
5 | Willy | 9 | 6
The code I have right now:
SELECT A.PersonID,
A.PersonName,
A.ProblemID,
MAX(A.ProblemsCounter) AS ProblemsAmount
FROM (SELECT Person.PersonId AS PersonID,
Person.Name AS PersonName,
Problem.ProblemId AS ProblemID,
COUNT(*) AS ProblemsCounter
FROM Person,
Problem
WHERE Problem.ProblemId = Person.ProblemId
GROUP BY Person.PersonId, Person.Name, Problem.ProblemId
) A
GROUP BY A.PersonID, A.PersonName, A.ProblemID
ORDER BY A.PersonName, ProblemsAmount DESC;
Inner query returns the same thing as outer does, I'm confused with MAX function. It doesn't work and I don't understand why. I tried to fix it using HAVING, but it wasn't successfully.
Thanks in advance.

A simple method that doesn't require a subquery is TOP (1) WITH TIES and ROW_NUMBER():
SELECT TOP (1) WITH TIES p.PersonId, p.Name AS PersonName,
pr.ProblemId, COUNT(*) AS ProblemsCounter
FROM Person p JOIN
Problem pr
ON pr.ProblemId = p.ProblemId
GROUP BY p.PersonId, p.Name, pr.ProblemId
ORDER BY ROW_NUMBER() OVER (PARTITION BY p.PersonId ORDER BY COUNT(*) DESC);
Note that I have also fixed the JOIN syntax. Always use proper, explicit, standard JOIN syntax.

no need subquery try like below and avoid coma separated join
SELECT Person.PersonID ,
Person.Name AS PersonName,
COUNT(Problem.ProblemId) AS ProblemsCounter
,max(ProblemsAmount) as ProblemsAmount
FROM Person left join
Problem
on Problem.ProblemId = Person.ProblemId
GROUP BY Person.PersonID, Person.Name

Related

How do I query a master-detail result having only the last detail row in MS-Access?

I have the following master table
.------------------.
| id | parent_name |
.------------------.
| 1 | Mike |
| 2 | Sarah |
| 3 | Danial |
| 4 | Alex |
.------------------.
And have the following child-table details:
.------------------------------------------.
| id | parent_id | child_name | birth year |
.------------------------------------------.
| 1 | 1 | mandy | 2000 |
| 2 | 1 | mark | 2003 |
| 3 | 1 | mathew | 2005 |
| 4 | 2 | sandy | 1998 |
| 5 | 2 | sharon | 2006 |
| 6 | 3 | david | 2001 |
.------------------------------------------.
In the example above, I delibretaly choose names of children with the first letter matching their parents' names just to make it easier to understand the relationship, even though each child is connected to his/her parent using the parent_id.
What I would like to have is a list of all parents (4 rows) and to have a matching 4 rows from the children table, selecting only the last born child of each respectful parent.
.-------------------------------.
| id | parent_name | last_child |
.-------------------------------.
| 1 | Mike | mathew |
| 2 | Sarah | sharon |
| 3 | Danial | david |
| 4 | Alex | (null) |
.-------------------------------.
In oracle, this is easy:
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p,
children_table c
WHERE
p.id = c.parent_id
AND c.birth_year = (SELECT MAX(birth_year) FROM children_table where parent_id = p.id)
But I am struggling to generate the same result in MS Access.. MS Access does not accept sub-queries (for select the child having the maximum birth year for the same parent).
Is there a better way to get the result in MS Access?
Access certainly does support subqueries, but you're using a crossjoin, so you will never get a null there.
Instead, left join and perform a subquery in the FROM clause.
Your query would fail identically in Oracle, by the way. There are no relevant differences between Access and Oracle here.
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p
LEFT JOIN
(
SELECT *
FROM children_table c
WHERE c.birth_year = (SELECT MAX(c2.birth_year) FROM children_table c2 WHERE c2.parent_id = c.parent_id)
) c
ON p.id = c.parent_id
Access sometimes performs better with an EXISTS, so a rewrite to that would be:
SELECT
p.id,
p.parent_name,
c.child_name last_child
FROM
parents_table p
LEFT JOIN
(
SELECT *
FROM children_table c
WHERE EXISTS(SELECT 1 FROM children_table c2 WHERE c2.parent_id = c.parent_id HAVING c.birth_year = MAX(c2.birth_year))
) c
ON p.id = c.parent_id
If you just want the child's name, you can use a correlated subquery:
select p.*,
(select top 1 child_name
from children_table as ct
where ct.parent_id = p.id
order by ct.birth_year desc, ct.child_name
) as youngest_child_name
from parents_table as p;
This can take advantage of an index on children_table(parent_id, birth_year desc, child_name). With the index, I would expect this to be quite fast.

PostgreSQL - Max value for each id

I'm trying to get max value of exam_id from table exams for each protege.
proteges
protege_id | protege_patron | protege_firstname | protege_lastname
------------+----------------+-------------------+------------------
1 | 1 | Andrzej | Maniek
2 | 1 | Anna | Maj
3 | 1 | Joanna | Jankowska
exams
exam_id | exam_protege | exam_weight | exam_glucose | exam_pressure
---------+--------------+-------------+--------------+---------------
1 | 1 | 84 | 3ml | 123/84
2 | 1 | 99 | 23ml | 124/72
3 | 2 | 99 | 23ml | 124/72
4 | 3 | 94 | 23ml | 124/72
First I've tried
SELECT DISTINCT protege_patron, exams.*
FROM exams INNER JOIN proteges ON protege_id = exam_protege
WHERE exam_id = (SELECT MAX(exam_id) FROM exams WHERE protege_patron = 1);
and the output was:
protege_patron | exam_id | exam_protege | exam_weight | exam_glucose | exam_pressure
----------------+---------+--------------+-------------+--------------+---------------
1 | 4 | 3 | 94 | 23ml | 124/72
(1 row)
After trying SELECT protege_firstname, protege_lastname, MAX (exam_id) FROM exams JOIN proteges ON protege_id = exam_protege GROUP BY protege_id; the output is:
protege_firstname | protege_lastname | max
-------------------+------------------+-----
Andrzej | Maniek | 2
Anna | Maj | 3
Joanna | Jankowska | 4
(3 rows)
So, logical way was to add more things like exam_weight
That's what I did :
SELECT protege_firstname, protege_lastname, exam_weight, MAX (exam_id) FROM exams JOIN proteges ON protege_id = exam_protege GROUP BY protege_id;
ERROR: column "exams.exam_weight" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select protege_firstname, protege_lastname, exam_weight, MAX...
^
Atm I don't know how to fix that. Tried distinct, read some about aggregate functions... Is there any way to do that? All I want to do is to JOIN two tables and for each protege select all of his values and values of his exam with max exam_id...
You can to use distinct on. I think the logic is:
select distinct on (exam_protege) e.*
from exams e
order by exam_protege, exam_id desc;
You can, of course also bring in the protege information using a join:
select distinct on (exam_protege) e.*, p.*
from exams e join
protege p
on e.exam_protege = p.protege_id
order by exam_protege, exam_id desc;
You can do it like this:
select * from
(
select max(exam_id) maxexamid, exam_protege from exams group by exam_protege
) as maxexams
inner join proteges p
on maxexams.exam_protege = p.protege_id
inner join exams e
on e.exam_id = maxexams.maxexamid

Group By with MAX value from another column

Table FieldStudies is :
ID Name
---|-----------------------|
1 | Industrial Engineering|
2 | Civil Engineering |
3 | Architecture |
4 | Chemistry |
And table Eductionals is :
ID UserID Degree FieldStudy_ID
---|------|--------|------------|
1 | 100 | 3 | 4 |
2 | 101 | 2 | 2 |
3 | 101 | 3 | 2 |
4 | 101 | 4 | 3 |
5 | 103 | 3 | 4 |
6 | 103 | 4 | 2 |
I want to find the number of students in each FieldStudies , provided that the highest Degree is considered.
Output desired:
ID Name Count
---|-----------------------|--------|
1 | Industrial Engineering| 0 |
2 | Civil Engineering | 0 |
3 | Architecture | 1 |
4 | Chemistry | 2 |
I have tried:
select Temptable2.* , count(*) As CountField from
(select fs.*
from FieldStudies fs
left outer join
(select e.UserID , Max(e.Degree) As ID_Degree , e.FieldStudy_ID
from Eductionals e
group by e.UserID) Temptable
ON fs.ID = Temptable.FieldStudy_ID) Temptable2
group by Temptable2.ID
But I get the following error :
Column 'Eductionals.FieldStudy_ID' is invalid in the select list
because it is not contained in either an aggregate function or the
GROUP BY clause.
If I understand correctly, you want only the highest degree for each person. If so, you can use row_number() to whittle down the multiple rows for a given person and the rest is aggregation and join:
select fs.id, fs.Name, count(e.id)
from fieldstudies fs left join
(select e.*,
row_number() over (partition by userid order by degree desc) as seqnum
from educationals e
) e
on e.FieldStudy_ID = fs.id and seqnum = 1
group by fs.id, fs.Name
order by fs.id;

Unique rows from PostgreSQL database and ordering

how can I get unique rows from PostgreSQL (8.4.20), at this moment my query looks like:
SELECT
DISTINCT ON(student.id) student.id,
student.*,
programme_stage.*
FROM
person AS student
INNER JOIN
programme ON (student.id = programme.person_id)
RIGHT JOIN
programme_stage ON (programme.id = programme_stage.programme_id)
ORDER BY
student.id,
student.last_name ASC,
student.first_name ASC
LIMIT 10 OFFSET 0
The above query works correctly, but I want to sort by the last and first name in first order.
Sample data:
| id | first_name | last_name | programme_id | programme_stage _id |
|----|------------|-----------|--------------|---------------------|
| 1 | Michał | Nowak | 1 | 1 |
| 2 | Jan | Kowalski | 2 | 2 |
| 3 | Tomasz | Thomas | 2 | 1 |
Expected output:
| id | first_name | last_name | programme_id | programme_stage _id |
|----|------------|-----------|--------------|---------------------|
| 2 | Jan | Kowalski | 2 | 2 |
| 1 | Michał | Nowak | 1 | 1 |
| 3 | Tomasz | Thomas | 2 | 1 |
If I try to remove from order statment student.id column, I getting error:
..SELECT DISTINCT ON expressions must match the expressions .. ORDER BY LINE 2: DISTINCT ON(student_person.id) student_person.id,
^
Use a subquery:
SELECT s.*
FROM (SELECT DISTINCT ON (s.id) s.*, ps.*
FROM programme_stage ps LEFT JOIN
programme p
ON programme.id = ps.programme_id LEFT JOIN
person s
ON s.id = p.person_id)
ORDER BY s.id
LIMIT 10 OFFSET 0
) s
ORDER BY s.last_name ASC, s.first_name ASC;
Notes:
I prefer shorter table aliases so the query is easier to write and to read.
I prefer LEFT JOIN to RIGHT JOIN. However, because you are using the person table for aggregation, you probably want inner joins.
There is no need to select the id twice.
You may need to select explicit columns instead of * if columns have the same name.

PostgreSQL join two tables with LIMIT 1

I have two tables:
First table "persons"
id | name |
---------------
1 | peter |
3 | martin |
5 | lucy |
Second table "meetings"
id | date | id_persons |
--------------------------------
1 | 2014-12-08 | 1 |
2 | 2013-05-10 | 2 |
3 | 2015-08-25 | 1 |
4 | 2016-10-18 | 1 |
5 | 2012-01-01 | 3 |
6 | 2016-09-28 | 5 |
I need somehow get only last date from "meeting" table for every person (or selected). And result table must be order by name. I thought, it could be like this, but WHERE clause in LEFT JOIN can't be used:
SELECT meetings.id, meetings.date, persons.name FROM persons
LEFT JOIN (SELECT meetings.date, meetings.id, meetings.id_persons FROM
meetings WHERE persons.id = meetings.id_persons ORDER BY
meetings.date DESC LIMIT 1) m ON m.id_persons = persons.id
WHERE persons.id < 6 ORDER BY persons.name
So I started with DISTINCT and it worked, but I think that it is not good idea:
SELECT * FROM
(SELECT DISTINCT ON (persons.id) persons.id, persons.name,
m.date, m.id FROM persons
LEFT JOIN (SELECT meetings.id, meetings.date, meetings.id_persons
FROM meetings ORDER BY meetings.date DESC) m
ON m.id_persons = persons.id
WHERE persons.id < 6 ORDER BY persons.id) p
ORDER BY p.name
Result what I need is:
name | date | id_meetings
-----------------------------------
lucy | 2016-09-28 | 6
martin | 2012-01-01 | 5
peter | 2016-10-18 | 4
Could you help me with better solution?
In Postgres, the easiest way is probably distinct on:
select distinct on (p.id) p.*, m.*
from persons p left join
meetings m
on m.id_persons = p.id
order by p.id, m.date desc;
Note: distinct on is specific to Postgres.