Reputation: 3254
There are 5 tables
books
------
id
name
year
author
------
id
name
store
------
id
name
books_authors
------
id
book_id
author_id
books_stores
------
id
store_id
book_id
Every book can have many authors and every book can be in a few stores. I would like to get book with all its authors and with all stores where it can be found
My code (I use PDO)
if(isset($_POST['books'])){
$books_ids = $_POST["books"];
}
$books_ids_in = implode(',', array_fill(0, count($books_ids), '?'));
$query = "SELECT b.id, b.name, b.year, a.name as author_name, s.name as store_name
FROM books as b
LEFT JOIN books_authors as b_a
ON b.id = b_a.book_id
LEFT JOIN authors as a
ON a.id = b_a.author_id
LEFT JOIN books_stores as b_s
ON b.id = b_a.book_id
LEFT JOIN stores as s
ON s.id = b_s.store_id
WHERE b.id IN (". $books_ids_in .")
ORDER BY b.id";
$stmt = $conn->prepare($query);
foreach ($books_ids as $k => $id) {
$stmt->bindValue(($k+1), $id);
}
$stmt->execute();
$results = $stmt->fetchAll();
but in this case I get two string if book has two author (for example) and a lot of string for every store. How can I get one result for every book with all its authors and stores?
Upvotes: 2
Views: 74
Reputation: 108651
JOIN
operations have the purpose of generating combinatorial explosions. Your extra rows are the result of such explosions.
You want one row per book, showing authors and stores. Each of these rows contains three kinds of information: book, authors, stores. So, you need subqueries that can generate exactly one result per book for each type of information. Then you need to join those subqueries together.
It's easy for books
: you already have a table with one row per.
For authors
you need this:
SELECT ba.book_id, GROUP_CONCAT(a.name) author_names
FROM books_authors ba
LEFT JOIN authors a ON ba.author_id = a.id
GROUP BY ba.book_id
If you know the authors for a book should be listed in order of id, use GROUP_CONCAT(a.author_name ORDER BY a.id)
. The order of authors is considered very important by bibliographers and the authors themselves, by the way.
That result set contains rows like
17 James Watson, Francis Crick
19 Dewey, Cheatham, Howe
You do the same sort of thing for your stores.
SELECT bs.book_id, GROUP_CONCAT(s.name) store_names
FROM books_stores bs
LEFT JOIN stores s a ON bs.store_id = s.id
GROUP BY bs.book_id
Then you JOIN your books table to these two subqueries (a/k/a virtual tables) and you have what you want.
SELECT b.id, b.name, aa.author_names, ss.store_names
FROM books b
LEFT JOIN (
SELECT ba.book_id, GROUP_CONCAT(a.name) author_names
FROM books_authors ba
LEFT JOIN authors a ON ba.author_id = a.id
GROUP BY ba.book_id
) aa ON b.id = aa.book_id
LEFT JOIN (
SELECT bs.book_id, GROUP_CONCAT(s.name) store_names
FROM books_stores bs
LEFT JOIN stores s a ON bs.store_id = s.id
GROUP BY bs.book_id
) ss ON b.id = ss.book_id
That will give you
17 The Double Helix James Watson, Francis Crick Barnes, Amazon, Harvard Coop
19 Faking Contracts Dewey, Cheatham, Howe Law Bookstore, Amazon
Upvotes: 1
Reputation: 5316
You can use GROUP_CONCAT()
function:
SELECT
b.id,
b.`name`,
b.`year`,
GROUP_CONCAT(DISTINCT a.`name`) AS author_names,
GROUP_CONCAT(DISTINCT s.`name`) AS store_names
FROM
books AS b
LEFT JOIN books_authors AS b_a ON b.id = b_a.book_id
LEFT JOIN authors AS a ON a.id = b_a.author_id
LEFT JOIN books_stores AS b_s ON b.id = b_a.book_id
LEFT JOIN stores AS s ON s.id = b_s.store_id
WHERE
b.id IN (". $books_ids_in .")
GROUP BY b.id
ORDER BY b.id
Upvotes: 2