Having problems identifying my mistake - sql

The tables which are already created and unmodifiable are Book and Author.
Book (Title, Price, Yeareleased)
Author(AName,btitle,position)
Italized are the keys
and Btitle in Author is a foreign key that references Book(Title).
My SQL query:
select distinct AName
from Author
where position in (2,3) AND position<>1
group by AName
When I run this I get all the authors that have a book in position 2 or 3. Which is what I want but I'm only trying to get those authors which have a position 2 or 3 for all there books.
Essentially returning every author who was in the 2nd or 3rd position in all the books.

Maybe something like this would work:
select distinct AName
from #Author
where position in (2,3)
except
select distinct AName
from #Author
where position not in (2,3)
It makes a set of those authors who are in position 2 and 3 and then removes the ones who has another position.

It is not entirely clear whether someone who co-wrote 2 books and was listed second on one and third on the other should be selected or not. It is simpler to allow it; you can refine the query if you need the more stringent condition.
One way to answer this query makes the key observation that you're interested in authors for whom the count of the books they have written is equal to the count of the books where they are listed as second or third author.
Go for some TDQD — Test-Driven Query Design
Number of books each author wrote
SELECT Aname, COUNT(*) AS BookCount
FROM Author
GROUP BY AName
Number of books each author wrote as second or third author
SELECT Aname, COUNT(*) AS NonLeadAuthorCount
FROM Author
WHERE Position IN (2, 3)
GROUP BY Aname
Join those two where the counts are identical
SELECT X.Aname
FROM (SELECT Aname, COUNT(*) AS BookCount
FROM Author
GROUP BY AName
) AS X
JOIN (SELECT Aname, COUNT(*) AS NonLeadAuthorCount
FROM Author
WHERE Position IN (2, 3)
GROUP BY Aname
) AS Y
ON X.BookCount = Y.NonLeadAuthorCount
An alternative way of looking at is 'the set authors who have written a book in position 2 or 3 minus the set of authors who have written a book where the position is neither 2 nor 3'. For this, see the answer by jpw.

Trying to write standard SQL:
SELECT AName FROM (
SELECT
AName,
COUNT(*) AS count_all,
(SELECT COUNT(*) FROM Author AS aa WHERE aa.AName = a.AName AND position=2) AS count_2,
(SELECT COUNT(*) FROM Author AS aa WHERE aa.AName = a.AName AND position=3) AS count_3,
FROM Author AS a
GROUP BY AName
) AS t
WHERE count_all = count_2
OR count_all = count_3
I hope this work for you.

Try this:
select AName from Author where position=2 OR position=3 group by AName;

Try adding
and AName not in (select AName from Author where position != 2 and position != 3
Or something like that...

Related

What's the best way to group SQL results by items batch?

For example, I have a simple table books:
author
book
Author-A
Book-A1
Author-A
Book-A2
Author-B
Book-B1
Author-C
Book-C1
Author-C
Book-C2
And I need to count books by each author, so I'll write:
select author, count(*) from books
group by author
# Author-A = 2
# Author-B = 1
# Author-C = 2
But now I need to count books by groups of authors:
groupA = ['Author-A', 'Author-C'],
groupB = ['Author-B']
select authorGroup, count(*) from books
group by {
case author in groupA -> 'groupA'
case author in groupB -> 'groupB'
} as authorGroup
# ['Author-A', 'Author-C'] = 4
# ['Author-B'] = 1
These groups can be different and come from another module.
What's the best way to write this requests? Maybe without union such as:
select author as 'groupA', count(*) from books
where author in { groupA }
union
select author as 'groupB', count(*) from books
where author in { groupB }
because there could be a lot of groups in request (~20-30)
The problem is that these groups can be absolutely dynamic: I can request ['Author-A', 'Author-B'] in one request as one group and ['Author-B', 'Author-C'] in another.
For example, the group is not something like author's country or genre. It can be totally dynamic.
The usual way is to JOIN on to a mapping table, which can be an in-line-view if need be (though I recommend an actual table, which can be indexed).
WITH
author_group AS
(
SELECT 'Author-A' AS author, 'Group-A' AS group_label
UNION ALL
SELECT 'Author-B' AS author, 'Group-B' AS group_label
UNION ALL
SELECT 'Author-C' AS author, 'Group-A' AS group_label
)
SELECT
author_group.group_label,
COUNT(*)
FROM
books
INNER JOIN
author_group
ON author_group.author = books.author
GROUP BY
author_group.group_label
Similar results can be achieved with CASE expressions, but it doesn't scale very well...
WITH
mapped_author AS
(
SELECT
*,
CASE author
WHEN 'Author-A' THEN 'Group-A'
WHEN 'Author-B' THEN 'Group-B'
WHEN 'Author-C' THEN 'Group-A'
END
AS author_group
FROM
books
)
SELECT
author_group,
COUNT(*)
FROM
mapped_author
GROUP BY
author_group
First you need to create a new table that show in what group is the author.
Later you just count
Like this:
select distinct a.group_auth, count(a.book) over (partition by a.group_auth)
from
(select
case when b.Author in [groupA] then 'groupA',
when b.Author in [groupB] then 'groupB'
end case as group_auth,
b.book as book
from books b
) as a
;

How to get MAX value out of the GROUPs COUNT

I've recently started to learn tsql beyond basic inserts and selects, I have test database that I train on, and there is one query that I can't really get to work.
There are 3 tables used in that query, in the picture there are simplified fields and relations
I have 2 following queries - first one is simply displaying students and number of marks from each subject. Second is doing almost what I want to achive - shows students and maxiumum amount of marks they got, so ex.
subject1 - (marks) 1, 5, 3, 4 count - 4
subject2 - (marks) 5, 4, 5 - count - 3
Query shows 4 and from what I checked it returns correct results, but I want one more thing - just to show the name of the subject from which there is maximum amount of marks so in the example case - subject1
--Query 1--
SELECT s.Surname, subj.SubjectName, COUNT(m.Mark) as Marks_count
FROM marks m, students s, subjects subj
WHERE m.StudentId = s.StudentNumber and subj.SubjectNumber = m.SubjectId
GROUP BY s.Surname, subj.SubjectName
ORDER BY s.Surname
--Query 2--
SELECT query.Surname, MAX(Marks_count) as Maximum_marks_count FROM (SELECT s.Surname, subj.SubjectNumber, COUNT(m.Mark) as Marks_count
FROM marks m, students s, subjects subj
WHERE marks.StudentId = s.StudentNumber and subj.SubjectNumber = m.SubjectId
GROUP BY s.Surname, subj.SubjectName) as query
GROUP BY query.Surname
ORDER BY query.Surname
--Query 3 - not working as supposed--
SELECT query.Surname, query.SubjectName, MAX(Marks_count) as Maximum_marks_count FROM (SELECT s.Surname, subj.SubjectNumber, COUNT(m.Mark) as Marks_count
FROM marks m, students s, subjects subj
WHERE marks.StudentId = s.StudentNumber and subj.SubjectNumber = m.SubjectId
GROUP BY s.Surname, subj.SubjectName) as query
GROUP BY query.Surname, query.SubjectName
ORDER BY query.Surname
Part of the query 1 result
Part of the query 2 and unfortunately query 3 result
The problem is that when I add to the select statement subject name I got results as from query one - there is no more maximum amount of marks just students, subjects and amount of marks from each subject.
If someone could say what I'm missing, I will much appreciate :)
Here's a query that gets the highest mark per student, put it at the top of your sql file/batch and it will make another "table" you can join to your other tables to get the student name and the subject name:
WITH studentBest as
SELECT * FROM(
SELECT *, ROW_NUMBER() OVER(PARTITION BY studentid ORDER BY mark DESC) rown
FROM marks) a
WHERE rown = 1)
You use it like this (for example)
--the WITH bit goes above this line
SELECT *
FROM
studentBest sb
INNER JOIN
subject s
ON sb.subjectid = s.subjectnumber
Etc
That's also how you should be doing your joins
How does it work? Well.. it establishes an incrementing counter that restarts every time studentid changes (the partition clause) and the numberin goes in des ending mark order (the order by clause). An outer query selects only those rows with 1 in the row number, ie the top mark per student
Why can't I use group by?
You can, but you have to write a query that summarises the marks table into the top mark (max) per student and then you have to join that data back to the mark table to retrieve the subject and all in it's a lot more faff, often less efficient
What if there are two subjects with the same mark?
Use RANK instead of ROW_NUMBER if you want to see both
Edit in response to your comment:
An extension of the above method:
SELECT * FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY su, st ORDER BY c DESC) rn FROM
(
SELECT studentid st, subjectid su, count(*) c
FROM marks
GROUP BY st, su
) a
) b
INNER JOIN student stu on b.st = stu.studentnumber
INNER JOIN subject sub on b.su = sub.subjectnumber
WHERE
b.rn = 1
We count the marks by student/subject, then rownumber them in descending order of count per student-subject pair, then choose only the first row and join in the other wanted data
Ok thanks to Caius Jard, some other Stack's question and a little bit of experiments I managed to write working query, so this is how I did it.
First I created view from query1 and added one more column to it - studentId.
Then I wrote query which almost satisfied me. That question helped me a lot with that task: Question
SELECT marks.Surname,
marks.SubjectName,
marks.Marks_count,
ROW_NUMBER() OVER(PARTITION BY marks.Surname ORDER BY marks.Surname) as RowNum
FROM MarksAmountPerStudentAndSubject marks
INNER JOIN (SELECT MarksAmountPerStudentAndSubject.Id,
MAX(MarksAmountPerStudentAndSubject.Marks_count) as MaxAmount
FROM MarksAmountPerStudentAndSubject
GROUP BY MarksAmountPerStudentAndSubject.Id) m
ON m.Id = marks.Id and marks.Marks_count = m.MaxAmount
It gives following results
That's what I wanted to achieve with one exception - if students have the same amount of marks from multiple subjects it displays all of them - thats fine but I decided to restrict this to the first result for each student - I couldn't just simply put TOP(1)
there so I used similar solution that Caius Jard showed - ROW_NUMBER and window function - it gave me a chance to choose records that has row number equals to 1.
I created another view from this query and I could simply write the final one
SELECT marks.Surname, marks.SubjectName, marks.Marks_count
FROM StudentsMaxMarksAmount marks
WHERE marks.RowNum = 1
ORDER BY marks.Surname
With result

Sql Query for number of available and total books

I need a query which will return list of book from book table
but it will show total number of books and books available too ..
And for number of books it will count number of books with simular ISBN n amount count which will count number of books with simular ISBN with status available
Table structure
Bookid booktitle author ISBN edition publisher price supplier volume language status
status contains available,issued
I want result as
BookTitle Author Publisher Location Total available
Please also explain the query because I need to implement it on many tables
Queries i tried this far
select *,count(1) as Quantity from Book group by Book.BookTitle
It successfully gives me book list with total quantity
and
select *,count(1) as Quantity from Book where status='Available' group by Book.BookTitle
It Successfully gives me list of books with how many are available.
but i need combination of both total and available ..
In brevi:
select isbn, booktitle, sum(case when status='available' then 1 else 0 end) as avail, count(*) as total
from Book
group by isbn, booktitle
http://sqlfiddle.com/#!2/80a41/5
i definitely suggest you to read about 3rd normal form
You must group by all items in the SELECT query. You can either count(*) or sum(1). Here is how I would do:
SELECT BookId, Name, Available, count(*) as Quantity
FROM Book GROUP BY Bookid,Name,Available
select d.* from (select BookTitle,Author,Publisher,Location,count(BookTitle) as Total, Status, Count(Status) as StatusQuantity from Book group by BookTitle,Author,Publisher,Location,Status)d where d.Status='Available'
Try this one:
SELECT
BookTitle,
Author,
Publisher,
Location,
SUM(CASE WHEN status='available' THEN 1 ELSE 0 END) Available,
COUNT(*) AS Total
FROM Book
GROUP BY BookTitle,Author,Publisher,Location
Are you have any key constraints??
you must split the table..
Using the bookid or ISBN as a primary key,store the status in a separate table..
it will be easy for further process otherwise the records should be redundant..

Same value is shown only one, how make it?

Hallo I need to show same column value only one,but each other column value show to next row.
For Example, I have tables Person(iD, name, surname) and Contact(iD, description, contact), and one person has tree contacts.
How can I make this report ?
iD Name Surname Description Contact
5 Johny Walker Email Johny.Walke#xzy.zz
Mobile 6546846168
Fax 688468
In theory something like the following construct should work in 8.1.6 or higher:
select
case r when 1 then p.id end as id,
case r when 1 then name end as name,
case r when 1 then surname end as surname,
description,
contact
from
person p, (
select
id,
row_number() over (partition by id) as r,
description,
contact
from
contact
) c
where p.id = c.id;
but you should add ordering to the window function and an order by to the results to force the output to always be in the correct order.

SQL help: select the last 3 comments for EACH student?

I have two tables to store student data for a grade-school classroom:
Behavior_Log has the columns student_id, comments, date
Student_Roster has the columns student_id, firstname, lastname
The database is used to store daily comments about student behavior, and sometimes the teacher makes multiple comments about a student in a given day.
Now let's say the teacher wants to be able to pull up a list of the last 3 comments made for EACH student, like this:
Jessica 7/1/09 talking
Jessica 7/1/09 passing notes
Jessica 5/3/09 absent
Ciboney 7/2/09 great participation
Ciboney 4/30/09 absent
Ciboney 2/22/09 great participation
...and so on for the whole class
The single SQL query must return a set of comments for each student to eliminate the human-time-intensive need for the teacher to run separate queries for each student in the class.
I know that this sounds similar to
SQL Statement Help - Select latest Order for each Customer but I need to display the last 3 entries for each person, I can't figure out how to get from here to there.
Thanks for your suggestions!
A slightly modified solution from this article in my blog:
Analytic functions: SUM, AVG, ROW_NUMBER
SELECT student_id, date, comment
FROM (
SELECT student_id, date, comment, (#r := #r + 1) AS rn
FROM (
SELECT #_student_id:= -1
) vars,
(
SELECT *
FROM
behavior_log a
ORDER BY
student_id, date DESC
) ao
WHERE CASE WHEN #_student_id <> student_id THEN #r := 0 ELSE 0 END IS NOT NULL
AND (#_student_id := student_id) IS NOT NULL
) sc
JOIN Student_Roster sr
ON sr.student_id = sc.student_id
WHERE rn <= 3
A different approach would be to use the group_concat function and a single sub select and a limit on that subselect.
select (
select group_concat( concat( student, ', ', date,', ', comment ) separator '\n' )
from Behavior_Log
where student_id = s.student_id
group by student_id
limit 3 )
from Student_Roster s