Count occurrences in many to many - sql

In my database i have following tables:
Person (
id,
name,
agentId
)
Agent (
id,
title
)
Agency (
id,
name
)
AgentAgency (
id,
agentId,
agencyId
)
I need query that will get all info about Person -> Agents with extra attribute numberOfAgencies that will show number of agencies of each agent, AND i need to show one more attribute agencyName that will show me name of first or only agency that user have (i need it in case agent have only 1 agency).
I tried something like this but without any success.
SELECT *, COUNT (aa.agentId) as numberOfAgencies
FROM agentAgencies as aa
LEFT JOIN agent as a ON a.id = aa.agentId
LEFT JOIN agency as ag ON aa.agencyId= ag.id
LEFT JOIN person as p ON p.id = ag.personId
GROUP BY ag.id, aa.id, p.id, a.id
For example i expect response like this:
PersonName John, AgencyName Cool Agency, numberOfAgencies 4

SELECT
MAX(p.Name) PersonName,
count(a.id) NoOfAgencies,
MAX(a.name) AgencyName
FROM persons p
LEFT OUTER JOIN agent g ON g.Id=p.agentId
LEFT OUTER JOIN AgentAgency aa ON aa.agentId = g.Id
LEFT OUTER JOIN Agency a on a.id = aa.agencyId
GROUP BY a.Id

Related

How to display Mother and Father of a person separately from same table in psql

Person table
PersonParent table(relation)
How to display all the records of person table, with their parents, whether or not having parents?
Output
that would be :
here is how to join to get the parents information alongside with person info , however this way , the person row will repeat for each parent :
select p.*, concat_ws(',',parent.firstname , parent_lastname) as ParentName
from persons p
cross join (select *
left join PersonParent pp
on p.personId = pp.personId
left join persons parent
on pp.parentId = parent.personid
if you want to show only one row per person, you can use query below, I used gender column to distinguish between mother and father:
select * from persons p
left join lateral(
select max(concat_ws(',', parents.firstname, parents.lastname)) filter (where parents.gender = 'm') as fatherrname
, max(concat_ws(',', parents.firstname, parents.lastname)) filter (where parents.gender = 'f') as mothername
from personparents pp
join persons parents on pp.parentid = parents.id
where pp.personid = p.id
group by pp.personid
) pp on true

Finding Distinct results in SQL columns

Below is the necessary info.
Table: Parts:
pid, Color
Table: Supplier
sid, sname
Table: Catalog
pid, sid
I am trying to find the pid in parts that have multiple distinct suppliers. I really don't know what command to use to do this.
I know I will have to use INNER JOIN to connect Parts and Supplier but what command ensures that I only get pid that have multiple distinct suppliers?
What about finding parts that have NO suppliers? I know DISTINCT or COUNT could somehow be used but not sure how this would work.
Find Parts with more than 1 supplier :
SELECT
p.Color
,COUNT(DISTINCT s.sname) as nbrSupName
FROM
Parts p
INNER JOIN Catalog c
ON c.pid = p.pid
INNER JOIN Supplier s
ON s.sid = c.sid
GROUP BY
p.Color
HAVING
COUNT(DISTINCT s.sname) > 1
Or :
SELECT
p.Color
,s.sname
FROM
(SELECT
p.pid
,COUNT(DISTINCT s.sname) as nbrSupName
FROM
Parts p
INNER JOIN Catalog c
ON c.pid = p.pid
INNER JOIN Supplier s
ON s.sid = c.sid
GROUP BY
p.Color) subquery
INNER JOIN Catalog c
ON c.pid = subquery.pid
INNER JOIN Supplier s
ON s.sid = c.sid
GROUP BY
p.Color
,s.sname
WHERE
subquery.nbrSupName > 1
Find Parts with NO supplier :
SELECT
p.Color
FROM
Parts p
LEFT JOIN Catalog c
ON c.pid = p.pid
WHERE
c.sid IS NULL
GROUP BY
p.Color
You can also use the 1st query with COUNT(DISTINCT s.sname) = 0
This should work:
select * from parts
where pid in
(select pid
from catalog
group by pid
having count(distinct sid) > 1)
Since you already have a table mapping a pid to one or more sid, you just retrieve the records in that table which have multiple sid values, and use the HAVING clause to implement this filter.
For the pid values with no sid values mapped to them, do a left join like so:
select * from
parts p
left join catalog c on p.pid = c.pid
where c.sid is null
The is null check ensures that only those pid values which do not have a mapped sid in the Catalog table are retrieved.
If you only need the pid you can just work with the Catalog table
SELECT pid
FROM Catalog
GROUP BY pid
HAVING COUNT(sid) > 1
that will work only if a Supplier is identified by the sid and not by the name.
To get the part with no supplier depent on how that information is stored.
If you have the pid in catalog with a NULL sid
SELECT pid
FROM Catalog
WHERE sid IS NULL
If there is no row in the catalog when the part has no supplier
SELECT p.pid
FROM Parts p
LEFT JOIN Catalog c on p.pid = c.pid
WHERE c.pid IS NULL
If your database has the possibility to use a MINUS type command it's possible to do
SELECT pid
FROM Parts
MINUS --EXCEPT in SQLServer 2005+
SELECT pid
FROM Catalog

sql triple join: ambigious attribute name on a count

So I want to count a number of books, but the books are stored in 2 different tables with the same attribute name.
I want to get a result that looks like:
name1 [total number of books of 1]
name2 [total number of books of 2]
I tried this triple join;
SELECT DISTINCT name, count(book)
FROM writes w
LEFT JOIN person p on p.id = w.author
LEFT JOIN book b on b.title = w.book
LEFT JOIN controls l on l.controller=p.id
GROUP BY name
ORDER BY name DESC
but since book exists as an attribute in writes and in controls, it cant execute the query.
It can only do it if I leave out one of joins so it can identify book.
How can I tell the sql engine to count the number of both book attributes together for each person?
As a result of database design that you interested in, you should issue 2 different sql and then merge them to handle single output.
A)
SELECT DISTINCT w.name as 'Name', count(w.book) as 'Cnt'
FROM writes w
LEFT JOIN person p on p.id = w.author
LEFT JOIN book b on b.title = w.book
B)
SELECT DISTINCT l.name as 'Name', count(l.book) as 'Cnt'
FROM controls l
LEFT JOIN person p on p.id = l.controller
LEFT JOIN book b on b.title = l.book
For your purpose, you can get UNION of A and B.
or you can use them as data source on a third SQL
select A.Name, sum(A.Cnt+B.Cnt)
from A, B
where A.Name = B.Name
group by A.Name
order by A.Name
WITH T AS
(
SELECT DISTINCT 'WRITES' FROMTABLE, w.name, w.count(book)
FROM writes w
LEFT JOIN person p on p.id = w.author
LEFT JOIN book b on b.title = w.book
GROUP BY name
UNION ALL
SELECT DISTINCT 'CONTROLLS' FROMTABLE, c.name, count(c.book)
FROM controlls c
LEFT JOIN person p on p.id = c.author
LEFT JOIN book b on b.title = c.book
GROUP BY name
)
SELECT * FROM T ORDER BY NAME
Should work.
HTH
This will work on a per distinct author's ID to how many books they've written. The pre-aggregation will return one record per author with how many books by that author. THEN, join to the person table to get the name. The reason I am leaving it by ID and Name of the author is... what if you have two authors "John Smith", but they have respective IDs of 123 and 389. You wouldn't want these rolled-up to the same person (or do you).
select
P.ID,
P.Name,
PreAgg.BooksPerAuthor
from
( select
w.author,
count(*) BooksPerAuthor
from
writes w
group by
w.author ) PreAgg
JOIN Person P
on PreAgg.Author = P.id
order by
P.Name

SQL: Have 4 Tables, Looking to find unmatched data

I've always done this back asswards in PHP or ASAP, so I figure it's time to actually learn the proper way to do it in SQL. I have the following 4 tables in a database:
Category (Fields: CategoryNumber, Desc) (small table with 15 rows)
Media (Fields: MediaID, Desc, CategoryNumber, etc) (huge table with 15,000 rows)
Sales (Fields: Date, MediaID, EmployeeID etc) (huge table with 100,000 rows)
Employees (Fields: EmployeeID, Name, etc) (small table with only 20 rows)
Category only links to Media
Media has links to both Category and Sales.
Sales links to both the Media and Employee
Employee only links to Sales
What I would like to do is to write a query which tells me what categories a given employee has never sold any media in.
I can write a simple query that looks for unmatched data between 2 tables, but I have no clue how to do it when I'm dealing with 4 tables.
Thanks for your time and help!
Here's my suggestion:
select *
from Category c
where not exists (
select *
from Employee e
inner join Sales s on s.EmployeeId = e.EmployeeId
inner join Media m on m.MediaID = s.MediaID
where e.Name = 'Ryan' and m.CategoryNumber = c.CategoryNumber
)
To query all employes with the categories in which they didn't sell anything:
select e.EmployeeName, c.CategoryNumber
from Category c
cross join Employee e
where not exists (
select *
from Sales s
inner join Media m on m.MediaID = s.MediaID
where c.categoryNumber = m.CategoryNumber
and s.EmployeeId = e.EmployeeId
)
SELECT c.CategoryNumber, c.Desc
FROM Category c
WHERE NOT EXISTS
(
SELECT *
FROM Employees e
INNER JOIN Sales s on s.EmployeeID = e.EmployeeID
INNER JOIN Media m on m.MediaID = s.MediaID
WHERE e.Name = "Ryan"
AND m.CategoryNumber = c.CategoryNumber
)
MS Access evidently needs a lot of parentheses (thanks, Ryan!):
select *
from Category c
where not exists
( select *
from ( Employee e
inner join Sales s on (s.EmployeeId = e.EmployeeId))
inner join Media m on (m.MediaID = s.MediaID)
where (e.Name = 'Ryan' and m.CategoryNumber = c.CategoryNumber) )
select c.desc
from category
left outer join (select s.employeeid,m.categorynumber
from sales s
inner join media m on s.mediaid=m.mediaid
inner join employee e on e.employeeid=s.employeeid
where e.name = 'JOE'
group by employeeid,categorynumber) t on t.categorynumber=c.categorynumber
where s.employeeid is null
Modified Answer based on the solution provided by Carl in Access SQL Syntax:
select *
from Category c
where not exists (
select *
from (Employee e
inner join Sales s on (s.EmployeeId = e.EmployeeId))
inner join Media m on (m.MediaID = s.MediaID)
where (e.Name = 'Ryan' and m.CategoryNumber = c.CategoryNumber)
)

SQL query for master-detail

Master table contains ID and PersonName.
Course table contains ID, CourseName.
Detail table contains ID, MasterID, CourseID, StartDate,EndDate
I want to create report that shows list of persons (PersonName) and the only last course they took (so every person is listed only once):
PersonName - CourseName - StartDate - EndDate
select m.PersonName, c.CourseName
from Master m
join Detail d on d.MasterID = m.ID
join Course c on c.ID = d.CourseID
where d.StartDate = (select max(d2.StartDate)
from Detail d2
where d2.MasterID = m.ID
)
Select personname,coursename from details
inner join course on course.id = details.courseid
inner join master on master.id = details.masterid
inner join (select max(startdate) , courseid,masterid
from details group by masterid,courseid ) as tb1
on tb1.courseid = details.courseid and tb1.masterid = details.masterid