I have two tables in a MySQL database:
Book(title, publisher, year) title is primary key
Author(name, title) title is foreign key to Book
I am trying to select the name of the Authors that published a book each year from 2000 to 2005 inclusive. This SQL query works, but is there a way to do this that makes it easier to change the date range if needed?
SELECT DISTINCT name
FROM Author
WHERE name IN (SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2000)
AND name IN
(SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2001)
AND name IN
(SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2002)
AND name IN
(SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2003)
AND name IN
(SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2004)
AND name IN
(SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2005);
Here's two ways to do it, and how another approach is wrong due to a subtle fault.
SQL Fiddle
MySQL 5.5.32 Schema Setup:
create table Book (title varchar(10), year int) ;
create table Author (name varchar(10), title varchar(10));
insert Book values
('Book1',2000),('Book2',2000),
('Book3',2000),('Book4',2000),
('Book5',2000),('Book6',2000),
('Book7',2001),('Book8',2002),
('Book9',2003),('Book10',2004),
('Book11',2005);
insert into Author values
('Author1','Book1'),('Author1','Book2'),
('Author1','Book3'),('Author1','Book4'),
('Author1','Book5'),('Author1','Book6'),
('Author2','Book6'),('Author2','Book7'),
('Author2','Book8'),('Author2','Book9'),
('Author2','Book10'),('Author2','Book11');
# author1 has written 6 books in one year
# author2 has written 1 book in every of the six years
Query 1:
# incorrect as it matches author1 who has 6 books in a single year
SELECT name from Author
INNER JOIN BOOK on Author.title = Book.Title
WHERE year IN (2000,2001,2002,2003,2004,2005)
GROUP BY name
HAVING COUNT(name) = 6
Results:
| NAME |
|---------|
| Author1 |
| Author2 |
Query 2:
# correct as it counts distinct years
SELECT name from Author
INNER JOIN BOOK on Author.title = Book.Title
WHERE year IN (2000,2001,2002,2003,2004,2005)
GROUP BY name
HAVING COUNT(DISTINCT year) = 6
Results:
| NAME |
|---------|
| Author2 |
Query 3:
# correct using relational division
SELECT DISTINCT name
FROM Author A1
INNER JOIN Book B1 ON A1.title = B1.Title
WHERE NOT EXISTS (
SELECT *
FROM Book B2
WHERE year IN (2000,2001,2002,2003,2004,2005)
AND NOT EXISTS (
SELECT *
FROM Author A2
INNER JOIN Book B3 ON A2.title = B3.Title
WHERE (A1.name = A2.name)
AND (B3.year = B2.year)
)
)
Results:
| NAME |
|---------|
| Author2 |
I would put an 'OR' clause in the where statement. It depends on how your table is set up but it should work:
SELECT DISTINCT name
FROM Author
WHERE name IN (SELECT Author.name
FROM Author INNER JOIN Book ON (Author.title = Book.title)
WHERE year = 2000)
Something like this, I didn't test it but you can group by and select only the ones that would have 6 rows:
SELECT a.name FROM Author a
INNER JOIN Book b
ON a.title = b.title
WHERE b.year BETWEEN 2000 AND 2005
GROUP BY name HAVING COUNT(a.name) = 6
I would use arguments
declare #StartYear int = 2000
declare #EndYear int = 2005
select a.name
from author a
inner join book b on a.title = b.title
where year between #StartDate and #EndDate
group by a.name
having count(distinct year) = #EndDate- #StartDate
It's easy to modify and modify itself whenever year you input
Related
I have Books table
BookID BookName
1 BookA
2 BookB
3 BookC
Member table
MemberID MemberName
1 MemberA
2 MemberB
Borrow Table
MemberID BookID
1 1
1 2
2 1
2 2
I want to find out five popular book by Memeber A
I tried the following query
SELECT TOP (5) Book.BookTitle, COUNT(*) AS Count, Member_1.MemberName
FROM Book INNER JOIN
Borrow ON Book.BookID = Borrow.BookID INNER JOIN
Member ON Borrow.MemberID = Member.MemberID INNER JOIN
Member AS Member_1 ON Borrow.MemberID = Member_1.MemberID
where Member.MemberName='A'
GROUP BY Book.BookTitle, Member_1.MemberName
ORDER BY Count DESC
But this is not giving me the actual result.
Any suggestion would be appreciated.
I think you have too many joins:
SELECT TOP (5) b.BookTitle, COUNT(*) AS Count, m.MemberName
FROM Book b INNER JOIN
Borrow bo
ON bo.BookID = b.BookID INNER JOIN
Member m
ON bo.MemberID = m.MemberID
WHERE m.MemberName = 'A'
GROUP BY b.BookTitle, m.MemberName
ORDER BY Count DESC;
Note: This syntax is usually associated with SQL Server and does not work in MySQL. In MySQL, you would use LIMIT 5 rather then SELECT TOP (5).
I know this is similar to this question but there is still no working answer yet and I will try to explain better.
So I have THREE tables, which are member, meta_name, and meta_value. I think you already know how they are related to each other. For example, assume I have these rows:
member table:
memberID | name
1 | john
meta_name table:
meta_nameID | name
1 | address
2 | jobTitle
meta_value table:
meta_valueID | meta_nameID | memberID | value
1 | 1 | 1 | California
2 | 2 | 1 | Manager
So John has two meta data which are address and jobTitle. The meta data are stored in meta_value table, and the mete_value table has identifiers in meta_name table. It's just a basic meta data system.
Now the question is, how can I get members that fulfill two or more conditions on the meta_value table? Something like, "get members that have an address at California AND a jobTitle as Manager"?
I have tried this query:
SELECT * FROM member JOIN meta_value ON member.memberID = meta_value.memberID WHERE (meta_nameID = '1' AND value = '3') AND (meta_nameID = '2' AND value = 'Jonggol')
I know that's an ugly-not working query but I hope that will help you understand what I'm going to achieve. Thanks!
NOTE: I actually don't need the meta_value table data. I just want to get members that fulfill the conditions.
There are a number of different options.
Straightforward:
SELECT
*
FROM
member
WHERE
EXISTS (
SELECT
*
FROM
meta_value
WHERE
meta_value.memberID = member.memberID
AND meta_value.meta_nameID = 1
AND meta_value.value = '3'
)
AND EXISTS (
SELECT
*
FROM
meta_value
WHERE
meta_value.memberID = member.memberID
AND meta_value.meta_nameID = 2
AND meta_value.value = 'Jonggol'
)
SELECT
*
FROM
member
WHERE
memberID IN (
SELECT
memberID
FROM
meta_value
WHERE
meta_value.meta_nameID = 1
AND meta_value.value = '3'
)
AND memberID IN (
SELECT
memberID
FROM
meta_value
WHERE
meta_value.meta_nameID = 2
AND meta_value.value = 'Jonggol'
)
Another way:
SELECT
member.*
, SUM(IF((meta_value.meta_nameID = 1 AND meta_value.value = '3') OR (meta_value.meta_nameID = 2 AND meta_value.value = 'Jonggol'), 1, 0)) AS x
FROM
member
INNER JOIN meta_value ON (
meta_value.memberID = member.memberID
)
GROUP BY
member.memberID
HAVING
x = 2
However, I'd like to note that such DB schema should only be used to store data that require filtering.
You need to use sub queries. Try the following query to select members with a particular address and a job title.
SELECT member.name
FROM member
WHERE
memberID IN
(SELECT DISTINCT memberID
FROM meta_value
WHERE meta_nameID IN
(SELECT DISTINCT meta_nameID FROM meta_name WHERE name='address')
AND value='California')
AND memberID IN
(SELECT DISTINCT memberID
FROM meta_value
WHERE meta_nameID IN
(SELECT DISTINCT meta_nameID FROM meta_name WHERE name='jobTitle')
AND value='Manager')
You also try a smaller query using with clause:
(Here we are creating a tmp table which has both address and job title, later join the address separately to get just address and jobTitle to get just job title. This will give you a member level table with address and jobTitle as columns for easy use in any subsequent queries)
WITH tmp AS
(SELECT * FROM meta_value mv INNER JOIN meta_name mn ON mv.meta_nameID=mn.meta_nameID)
SELECT member.name , add.value , job.value
FROM member
LEFT JOIN (SELECT * FROM tmp WHERE name='address') add ON member.memberID = add.memberID
LEFT JOIN (SELECT * FROM tmp WHERE name='jobTitle') job ON member.memberID = job.memberID
WHERE add.value = 'required address' AND add.job.value ='required job title'
student table.
| regno | name | lname | address |gender | mobile |
booking table
bookID | regno | tokenNo | CheckIn | CheckOut
I am trying to get the no.of female students present between two given dates. I have tried to get the respective values but it outputs repetitive values as well even with the distinct keyword.
I have also tried to use Union and still it does the right opposite of distinct.
SELECT count(gender) FROM (SELECT distinct regno from student where
gender = 'Male' union SELECT Distinct regno from book) x LEFT OUTER JOIN
student a on x.regno = a.regno LEFT OUTER JOIN book b on x.regno = b.regno
where checkIn >= '2015/7/23' AND checkOut <= '2015/7/31';
this is the other I have tried
SELECT count(gender) FROM (SELECT distinct(regno), gender from student
where gender = 'Male' ) AS A inner JOIN book AS B On A.regno = B.regno
where checkIn >= '2015/7/23' AND checkOut <= '2015/7/31';
It looks like you are over-complicating a simple query. All you need to do is join the tables and filter on date and gender and apply count.
SELECT COUNT(DISTINCT s.regno)
FROM student s
JOIN booking b on s.regno = b.regno
WHERE s.gender = 'Female'
AND b.checkIn >= '2015/7/23' AND b.checkOut <= '2015/7/31';
The distinct keyword makes sure each student is only counted once if the have multiple bookings in the period. If you want to count all bookings just remove the distinct.
I have a order table like this
id | bookId | bookAuthorId
--------------------------
1 3 2
2 2 1
3 1 2
and another table
bookId | book
---------------
1 bookA
2 bookB
3 bookC
and
bookAuthorId | author
------------------------
1 authorA
2 authorB
I want to get record from order table where id = 1 with result-set like this
id | book | author
what i tried :
select * from order
join bookId,bookAuthorId
on order.bookId = books.bookId
and order.authorId = authors.authorId
I don't know how to join these table to get the desired result.How can i do this ?
select o.id, b.book, a.author
from 'order' o
join book b on o.bookid=b.bookid
join author a on o.bookauthorid=a.bookauthorid
where o.id=1
You can do it using the where clause
select
id, book, author
from
`order`, book, author
where
`order`.bookId = book.bookId
and
`order`.authorId = author.authorId
Or
select
o.id, b.book, a.author
from
`order` o
natural join
book b
natural join
author a
select `order`.id, book.book, author.author
from `order`
join book on (`order`.bookId = book.bookId)
join author on (author.bookAuthorId = book.bookId)
where `order`.id = 1;
Assuming that bookAuthorId can be linked to bookId, otherwise you'll need to add a foreign key.
I have three tables:
author (columns: aut_id, aut_name)
book (columns: book_id, book_title)
authorbook (linking table, columns: aut_id, book_id)
Each author can be associated with one or more books.
Each book can be associated with one or more authors.
I would like to select a book by the name(s) and the exact number of its authors.
Table structure:
author
aut_id | aut_name
1 Aname
2 Bname
3 Cname
book
book_id | book_title (the titles are identical on purpose)
1 Atitle
2 Atitle
3 Atitle
authorbook
aut_id | book_id
1 1
1 2
2 2
1 3
2 3
3 3
Here is my code (I left out the author table for better clarification):
SELECT authorbook.book_id
FROM authorbook
INNER JOIN book
ON authorbook.book_id = book.book_id
WHERE book_title='Atitle'
AND FIND_IN_SET (authorbook.aut_id,'1,2')
GROUP BY authorbook.book_id
HAVING (COUNT(authorbook.aut_id)=2)
Problem: This code not only returns the desired authorbook.book_id(2) with TWO authorbook.aut_ids (1,2) but also the authorbook.book_id(3) with THREE authorbook.aut_ids (1,2,3).
Question: How can I SELECT a book associated with exactly the authors in the FIND_IN_SET clause (and no additional authors)?
Thanks a lot for your help!
Try this:
SELECT a.book_id
FROM authorbook a
INNER JOIN book b
ON a.book_id = b.book_id
WHERE b.book_title='Atitle'
AND FIND_IN_SET (a.aut_id,'1,2')
GROUP BY a.book_id
HAVING COUNT(DISTINCT a.aut_id) = 2
AND COUNT(DISTINCT a.aut_id) = (SELECT COUNT(DISTINCT a2.aut_id)
FROM authorbook a2
WHERE a2.book_id = a.book_id);
SQL Fiddle Demo
This should work
SELECT COUNT(aut_id) AS authors, book_id FROM (SELECT authorbook.*
FROM authorbook
INNER JOIN book
ON authorbook.book_id = book.book_id
WHERE book_title='Atitle') AS t1 GROUP BY book_id HAVING authors='2'