sql triple join: ambigious attribute name on a count - sql

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

Related

list all table elements and count() even if count is 0

Suppose the following tables:
table book(
id,
title,
deleted
)
table invoice(
id,
book_id,
settled
)
I need a list off all the books and the number of settled invoices for each book.
I tried this:
select book.id, title, count(invoice.id)
from book LEFT OUTER JOIN invoice ON book.id=invoice.book_id
where deleted=0
and settled=1
group by book.id
This works only if a book has at least 1 settled invoice or if it doesn't have any invoce at all. However it fails when a book has unsettled invoices and it doesn't have any settled invoice.
Any idea how to query it ?
The following will list all books, but only join and count settled invoices.
SELECT
b.id, b.title, COUNT(i.id) AS settled
FROM
book b
LEFT JOIN invoice i
ON b.id = i.book_id
AND i.settled = 1
WHERE
b.deleted = 0
GROUP BY
b.id
The condition and settled = 1 on your WHERE is effectively turning your LEFT JOIN into an INNER JOIN. You can add a CASE expression to your COUNT:
SELECT b.id,
b.title,
COUNT(CASE WHEN i.settled = 1 THEN 1 END)
FROM book b
LEFT JOIN invoice i
ON b.id = i.book_id
WHERE b.deleted=0
GROUP BY b.id;
Or use the LEFT JOIN with the invoice table already filtered:
SELECT b.id,
b.title,
COUNT(i.id)
FROM book b
LEFT JOIN ( SELECT *
FROM invoice
WHERE settled = 1) i
ON b.id = i.book_id
WHERE b.deleted=0
GROUP BY b.id;

Fetching columns, 4 tables

I’ve ran in to some problems with the following SQL assignment. I’m to retrieve the ID, firstname and lastname of any member who is registered in section that contains the word ‘xyz’.
So far I’ve managed the following:
SELECT m.id, p.firstname, p.lastname FROM member m
INNER JOIN person p ON m.id = p.id
WHERE m.id IN (SELECT id FROM membersection);
How do I go forward from here? I have no idea how to retrieve the sectionid from the membersection table then fetch the section name from the section id using that ID so I can check if the section name contains the previously stated word.
member:
id
member_number
registration_date
membersection:
memberid
sectionid
person:
id
firstname
lastname
section:
id
name
Just keep joining. And in the end use LIKE to check if section.name contains 'xyz'.
SELECT m.id,
p.firstname,
p.lastname
FROM member m
INNER JOIN person p
ON p.id = m.id
INNER JOIN membersection ms
ON ms.memberid = m.id
INNER JOIN section s
ON s.id = ms.sectionid
WHERE s.name LIKE '%xyz%';
There is some ambiguity in your question with regards to how your data are structured; what are the primary and foreign keys?
But, making some assumptions, you're almost there, you can chain multiple join statements together:
select
m.id,
p.firstname,
p.lastname
from
member m
inner join person p on
m.id = p.id
inner join membersection ms on
m.id = ms.memberid
inner join section s on
ms.sectionid = s.id
where
s.name like '%xyz%'
It's not super obvious what's going on with your Data Relationships, but this would be the basic route you might want to take (LEFT JOIN as I do not know the relationship):
SELECT m.id, p.firstname, p.lastname, ms.sectionid
FROM member m
INNER JOIN person p ON m.id = p.id
LEFT JOIN section s ON m.id = s.id
LEFT JOIN membersection ms ON m.id = ms.memberid
WHERE s.name = 'xyz'

Count occurrences in many to many

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

SQL: How to save order in sql query?

I have PostgreSQL database and I try to print all my users (Person).
When I execute this query
-- show owners
-- sorted by maximum cars amount
SELECT p.id
FROM car c JOIN person p ON c.person_id = p.id
GROUP BY p.id
ORDER BY COUNT(p.name) ASC;
I get all owners sorted by cars amount
Output: 3 2 4 1
And all order goes wrong when I try to link owner id.
SELECT *
FROM person p
WHERE p.id IN (
SELECT p.id
FROM car c JOIN person p ON c.person_id = p.id
GROUP BY p.id
ORDER BY COUNT(p.name) ASC);
Output: 1 2 3 4 and other data
You see than order is wrong. So here is my question how can I save that order?
Instead Of subquery use join. Try this.
SELECT p.*
FROM person p
JOIN (SELECT p.id,
Count(p.NAME)cnt
FROM car c
JOIN person p
ON c.person_id = p.id
GROUP BY p.id) b
ON p.id = b.id
ORDER BY cnt ASC
Untangle the mess. Aggregate first, join later:
SELECT p.*
FROM person p
JOIN (
SELECT person_id, count(*) AS ct
FROM car
GROUP BY person_id
) c ON c.person_id = p.id
ORDER BY c.cnt;
No need to join to person twice. This should be fastest if you count most or all rows.
For a small selection, correlated subqueries are faster:
SELECT p.*
FROM person p
ORDER BY (SELECT count(*) FROM car c WHERE c.person_id = p.id)
WHERE p.id BETWEEN 10 AND 20; -- some very selective predicate
As for your original: IN takes a set on the right hand, order of elements is ignored, so ORDER BY is pointless in the subuery.

How to write the T-SQL code for a query

I need to use t-sql to query two tables. The first table is Books. The second table is Authors. For each Book record there could be multiple child Author records. I want to write a query that only returns the first Author record found for the current Book record. There are hundreds of thousands of records in the tables so I need the query to be efficient.
select a.FirstName, a.LastName, b.BookName
from Books b
left join
(
select TOP 1 t.BookID, t.FirstName, t.LastName
from Authors t
) a
on a.BookID = b.BookID
where b.BookClassification = 2
This query is not right. I only want to select the top 1 record in the Authors which match the BookID. How can I get the results I am looking for?
You were close:
select a.FirstName, a.LastName, b.BookName
from Books b
outer apply
(
select TOP 1 t.BookID, t.FirstName, t.LastName
from Authors t
WHERE t.BookID = b.BookID
-- uncomment the next line to control which author to prefer
-- ORDER BY t.<someColumn>...
) a
where b.BookClassification = 2
Though it seems odd to me that Authors would be a child of Books... :)
See if this is more efficient. By looking for min(authorID) just once you might get better performance.
select author.FirstName, author.LastName, author.BookName
from Books with (nolock)
join
( select min(authorID) as authorID, bookID
from Authors with (nolock)
group by bookID
) as Author1
on Author1.authorID = Books.authorID
join Authors with (no lock)
on Authors.authorID = Author1.authorID
and Authors.bookID = Author1.bookID
where Books.BookClassification = 2
In the spirit of TIMTOWTDI.
You can use a CTE, a fancy subquery but helpful if the subquery is used more than once. And one the rank functions, row_number().
with bookAuthors as (
select a.FirstName, a.LastName, b.BookName, BookClassification,
row_number() over(partition by b.BookName order by a.lastName ) as rank
from Books b
left join Authors a
on a.BookID = b.BookID
)
select a.FirstName, a.LastName, b.BookName
from bookAuthors
where rank = 1
and BookClassification = 2