So I have three tables:
authors:
--------
ID Name
1 John
2 Sue
3 Mike
authors_publications:
---------------------
AuthorID PaperID
1 1
1 2
2 2
3 1
3 2
3 3
publications:
-------------
ID year
1 2004
2 2005
3 2004
I'm trying to join them so that I count the number of publications each author has had on 2004. If they didn't publish anything then it should be zero
ideally the result should look like this:
ID Name Publications_2004
1 John 1
2 Sue 0
3 Mike 2
I tried the following:
select a.ID, Name, count(*) as Publications_2004
from authors_publications as ap left join authors as a on ap.AuthorID=a.ID left join publications as p on p.ID=ap.PaperID
where year=2004
group by ap.AuthorID
I don't understand why it's not working. Its completely removing any authors that haven't published in 2004.
Your WHERE statement is taking the result set returned from the JOIN's and them trimming off records where year<>2004.
To get around this you can do a few different things
You can apply a filter to the publications table in the ON statement when joining. This will filter the results before joining
SELECT a.ID,
NAME,
count(*) AS Publications_2004
FROM authors_publications AS ap
LEFT JOIN authors AS a
ON ap.AuthorID = a.ID
LEFT JOIN publications AS p
ON p.ID = ap.PaperID AND
p.year = 2004
GROUP BY ap.AuthorID
You could use a case statement instead of a WHERE:
SELECT a.ID,
NAME,
SUM(CASE WHEN p.year = 2004 THEN 1 ELSE 0) END AS Publications_2004
FROM authors_publications AS ap
LEFT JOIN authors AS a
ON ap.AuthorID = a.ID
LEFT JOIN publications AS p
ON p.ID = ap.PaperID
GROUP BY ap.AuthorID, NAME
You could use a subquery to pre-filter the publications table to only 2004 records, which is just explicitly doing what was implicit in the first option:
SELECT a.ID,
NAME,
count(*) AS Publications_2004
FROM authors_publications AS ap
LEFT JOIN authors AS a
ON ap.AuthorID = a.ID
LEFT JOIN (SELECT * FROM publications WHERE AND year = 2004) AS p
ON p.ID = ap.PaperID
GROUP BY ap.AuthorID, NAME
Also, because you are not aggregating NAME with a formula, you should add that to your GROUP BY otherwise you may get funky results.
Related
I have 3 tables that contain info about users. I would like to find how many of each type of item in each bucket a person has.
I'm not grasping why this wouldn't work. Does table join order matter or something I not aware of?
A sample of the tables:
PERSONS
ID
NAME
1
John
2
Jane
BUCKETS
ID
LABEL
PERSONID
1
Random
1
2
Vacation
1
THINGS
ID
BUCKETID
TYPE
VALUE
1
1
Image
abc12
2
1
Image
abc13
3
1
Video
abc34
4
1
Image
def12
5
1
Video
def34
SELECT P.NAME, B.LABEL, T.TYPE, COUNT(T.TYPE)
FROM PERSONS P
LEFT JOIN BUCKETS B ON
B.PERSONID = P.ID
LEFT JOIN THING T ON
T.BUCKETID = B.ID
GROUP BY P.NAME, B.LABEL, T.TYPE
I expect it to return:
John, Random, Images, 3
John, Random, Videos, 2
But it returns:
John, Random, Images, 5
John, Random, Videos, 5
I have tried COUNT(*) which results in the same and COUNT(DISTINCT T.TYPE) which of course returns 1 as the count.
This works perfectly in MySQL. Fiddle here: https://www.db-fiddle.com/f/vcb3wiPMSAFBXrWbgYxuMH/8
MSSQL is a different beast all together.
I think you want count(distinct) of some sort. I would speculate:
SELECT P.NAME, B.LABEL, T.TYPE, COUNT(DISTINCT T.BUCKETID)
FROM PERSONS P LEFT JOIN
BUCKETS B
ON B.PERSONID = P.ID LEFT JOIN
THING T
ON T.BUCKETID = B.ID
GROUP BY P.NAME, B.LABEL, T.TYPE;
SQL Server 2012.
Each enterprise has one or more teams. Each team can have sponsors or cannot have any sponsors.
Enterprise
Id Name
1 A
2 B
3 C
and the team table:
Team
Id Name EnterpiseId
1 For 1
2 Xor 2
3 Nor 2
4 Xur 1
5 Fir 3
6 Fte 2
and now the table sponsor
Sponsor
id Name TeamId
1 XX1 1
2 FC7 1
3 89U 3
Now I need to know how to present this table that shows only the enterprises that have at least one sponsor.
FINAL TABLE
Id Name
1 A
3 C
The enterprise B has 3 teams, but there are no sponsors for those 3 teams, so I want to show the enterprises that have sponsors which are "A" and "C".
Select A.id, A.name
FROM Enterprise A
LEFT JOIN Team B on A.Id=b.EnterpriseId
INNER JOIN Sponsor C on B.Id=C.TeamId
Where (SELECT COUNT(*) FROM Sponsor S INNER JOIN Team T on T.id=S.TeamId group by T.id)>0
This is not working. I am not used to use subsets which is likely the way to achieve the desired table. Thanks.
You can do this with JOINs. The GROUP BY is just to eliminate duplicates:
SELECT e.id, e.name
FROM Enterprise e JOIN
Team t
ON e.Id = t.EnterpriseId JOIN
Sponsor s
ON t.Id = s.TeamId
GROUP BY e.id, e.name;
The JOIN only matches teams that have sponsors.
If you were looking for more than one, then something like HAVING COUNT(*) > 1 would be called for.
Firstly, I'd like to apologise for the ambiguous title (I promise to revise it once I'm actually aware of the problem I'm trying to solve!)
I have two tables, player and match, which look like the following:
player:
id name
-- ----
1 John
2 James
3 April
4 Jane
5 Katherine
match:
id winner loser
-- ------ -----
1 1 2
2 3 4
Records in the match table represent a match between two players, where the id column is generated by the database, and the values in the winner and loser columns reference the id column in the player table.
I want to run a query which spits out the following:
player.id player.name total_wins total_matches
--------- ----------- ---------- -------------
1 John 1 1
2 James 0 1
3 April 1 1
4 Jane 0 1
5 Katherine 0 0
I currently have a query which retrieves total_wins, but I'm not sure how to get the total_matches count on top of that.
select p.id, p.name, count(m.winner)
from player p left join match m on p.id = m.winner
group by p.id, p.name;
Thanks for your help!
Try
select p.id, p.name,
sum(case when m.winner = p.id then 1 end ) as total_wins,
count(m.id) as total_matches
from player p
left join match m on p.id in ( m.winner, m.loser )
group by p.id, p.name;
One method splits the match match table, so you have a single row for each win and loss. The rest is just a left join and aggregation:
select p.id, p.name, coalesce(sum(win), 0) as win, count(m.id) as total_matches
from player p left join
(select match, winner as id, 1 as win, 0 as loss from match
union all
select match, loser as id, 0 as win, 1 as loss from match
) m
on p.id = m.id
group by p.id, p.name;
I have two tables (Books, Authors) with ManyToMany relationship. I need SQL statement to retrieve books with authors, sorted by authors/books. Important thing is that I must retrieve them with pagination (offset ... fetch in sql).
One of the problem that when I join tables there are duplicates in results (of course) and offset/fetch can't be used for this results. Another problem the results must be sorted (not subpages but all books of course).
I have one idea: (it retrieves books sorted by author name and include pagination)
select b.id, b.title, a.name from Books b inner join Books_Authors ba
on ba.bookID = b.id inner join Authors a
on ba.authorID = a.id
where a.name in (select name from Authors order by name offset 9 rows fetch next 3 rows only)
order by a.name
But I think it's not efficient way.
Something like this?
select * from
(
select tmp1.*, ROW_NUMBER() over(partition by b.title, a.name order by b.id, a.id) rang2
from
(
select a.id, b.id, b.title, a.name, ROW_NUMBER() over(partition by b.title, a.name order by b.id, a.id) rang
from Books b inner join Books_Authors ba on ba.bookID = b.id
inner join Authors a on ba.authorID = a.id
) tmp1 where rang=1
) tmp2
where rang2 between 3 and 9
order by title, name
table1
sno exam questions time_duration
1 unit test 1 10
2 mock 1 2 10
3 mock2 5 10
4 mock3 6 6
table2
qid answer user_attempt_option
1 1 1
2 2 3
2 3 4
3 4 1
3 1 2
3 2 3
3 3 1
I faced an interview and below is the question. The tables are below
Table1: Book (BookId is primary key)
BookId | Book_Title
1 | Book1
2 | Book2
3 | Book3
4 | Book4
5 | Book5
Table2: Book_Copies
BookId | BranchId | No_of_copies
1 1 2
1 2 5
2 1 0
2 2 2
2 3 0
3 1 0
3 2 0
The output should list all the Books with no stock at all as shown below.
Output:
Book_Title
---------
Book3
Book4
Book5
Please note that there are two possibilities. Either the 'No_of_copies' could be 0 or no record for a Book in the "Book_Copies" table.
For e.g.
the total number of copies for "Book3" is 0. The output should
include "Book3"
the total number of copies for "Book1 and "Book2" is 7 and 2
respectively. Both "Book1" and "Book2" shouldn't display
there is no entry for Book4 and Book5 in "Book_Copies", so both
should be included in output
After coming home, I wrote the below query after so many trials :)
select B.Book_Title
from
(
select BC.BookId, sum(BC.No_of_copies) as 'No of copies'
from Book_Copies BC
group by BC.BookId
having sum(BC.No_of_copies) = 0
union
select B.BookId, BC.No_of_copies
from Book B
left outer join Book_Copies BC on B.BookId = BC.BookId
where BC.BookId is null
)
as BookIds_withNoStock
inner join Book B on B.BookId = BookIds_withNoStock.BookId
This query works fine and tested properly.
Is there any way we can improve this query? like complexity, performance etc
If we can improve it, it would be helpful if you can provide the optimized query and reason. Thanks
You want books with no stock. I would approach this with a left outer join, aggregation, and a having clause:
select b.bookid, b.book_title
from book b left join
book_copies bc
on b.bookid = bc.bookid
group by b.bookid, b.book_title
having coalesce(sum(bc.no_of_copies), 0) = 0;
Try This
SELECT B.Book_Title
From Book B LEFT JOIN book_copies BC ON B.Bookid = BC.Bookid
Group By B.Book_Title
Having SUM(ISNULL(BC.no_of_copies,0)) = 0
FIddle Demo
Output:
BOOK_TITLE
Book3
Book4
Book5
The below code worked for me:
SELECT a.Book_Title
FROM #books a
left join(
SELECT BookId,sum(No_of_copies) Cnt
FROM #Book_Copies
GROUP BY BookId) b
ON a.BookId = b.BookId
WHERE b.cnt = 0 OR b.Cnt is null
try this:
select Book_Title from Book left join Book_Copies on Book.BookId = Book_Copies.BookId
group by Book_Title having
SUM(Book_Copies.No_of_copies) IS NULL OR SUM(Book_Copies.No_of_copies)=0
select book_title
from books
where book_id NOT IN
(
select book_id
from book_copies
group by book_id
having sum( no_copies) > 0
)