How to limit values when using distinct - mysql

PHP
SELECT DISTINCT bk.title AS Title, bk.year AS Year, aut.authorname AS Author, cat.category AS Category
FROM book bk
JOIN book_category bk_cat
ON bk_cat.book_id = bk.bookid
JOIN categories cat
ON cat.id = bk_cat.category_id
JOIN books_authors bk_aut
ON bk_aut.book_id = bk.bookid
JOIN authors aut
ON aut.id = bk_aut.author_id
ORDER BY bk.title ASC
My data base is echoing out the following data you can see that it prints the book out more than once if it has more than one category. from my php code can anyone tell me how i can make it distinct from the category column. Thanks.

The easiest way to achieve different data only in the columns you want is using GROUP BY clause. By default, it'll group the rows, depending on the value of the column, showing only distinct values so, if you want to group and show only different titles and categories, you should write your query as:
SELECT bk.title AS Title, bk.year AS Year, aut.authorname AS Author, cat.category AS Category
FROM book bk
JOIN book_category bk_cat
ON bk_cat.book_id = bk.bookid
JOIN categories cat
ON cat.id = bk_cat.category_id
JOIN books_authors bk_aut
ON bk_aut.book_id = bk.bookid
JOIN authors aut
ON aut.id = bk_aut.author_id
GROUP BY bk.title, cat.category
ORDER BY bk.title ASC
As you may see, no DISTINCT is used, but you'll get all books with distincts title and categories. The more fields you added into the GROUP BY clause, the more distinct data you'd get.
Same way, if you only wanted list books by title, you should only leave bk.title in the GROUP BY clause

DISTINCT works on the entire row, not a single column. The only way to ensure not repeating rows is to exclude category column from the select.
If your objective is to list all categories, you will have to either look up categories as you're looping over the query result set during output (not very efficient though) OR process your result set in the code before you output it by collapsing repeating rows and merging/concatenating category values into a string.
You could try some fancy SQL with subqueries to convert categories into a single delimited string but that will sure make your code hard to read and query hard to debug.

SELECT DISTINCT bk.title AS Title, bk.year AS Year, aut.autName AS Author, cat.category AS Category
FROM book bk
JOIN book_category bk_cat
ON bk_cat.book_id = bk.bookid
JOIN categories cat
ON cat.id = bk_cat.category_id
JOIN (SELECT GROUP_CONCAT(aut.authorname) as autName FROM authors JOIN books_authors ON books_authors.author_id=authors.author_id WHERE books_authors.book_id=bk.bookid) as aut
ORDER BY bk.title ASC

I think you might want to look into subquery-ing with the group_concat() so please give this a try:
group_concat() will merge the results of the sub-query and provide you with one string of authors separated by a comma-space like this author1, author2, author3
SELECT
DISTINCT bk.title AS Title,
bk.year AS 'Year',
(
SELECT
group_concat(aut.authorname, ', ')
FROM
authors aut
LEFT OUTER JOIN
books_authors bk_aut
ON
aut.id=bk_aut.author_id
WHERE
bk_aut.book_id = bk.book_id
) AS Author
FROM
book bk
ORDER BY
bk.title ASC

Related

How to select all the authors from database with the number of books assigned to them?

I've the following DB structure:
Authors(id,name);
Books(id,title,authorId);
I want to select all fields from authors and the number of books they are assigned to. I've managed to get the result, but only for the authors that are assigned to at least one book, which is not what I want. I tried with the following query:
SELECT books.*,authors.*
FROM authors
FULL OUTER JOIN books
ON authors.id = books.authorId;
but it doesn't work.
I guess that you want a left join and aggregation:
select a.id, a.name, count(*)
from authors a
left join books b on b.authorId = a.id
group by a.id, a.name
outer join will bring back authors without books. Instead use inner join and your results will only bring back authors with at least 1 book.
I would recommend a correlated subquery:
SELECT a.*,
(SELECT COUNT(*)
FROM books b
WHERE a.id = b.authorId
) as num_books
FROM authors;
This allows you to use SELECT a.* from authors. If you put a GROUP BY in the outer query, you either need to list all the columns separately or be using a database that allows you to aggregate by a primary key, while selecting other columns (this is standard functionality but most databases do not support it).
Definitely you need LEFT JOIN and GROUP BY, but details is not clear enough from the task description. Let's try a kind of
SELECT b.*, ab.count
FROM authors AS a
LEFT JOIN (
SELECT authorId, COUNT(*) AS count
FROM books
GROUP BY authorId
) AS ab ON a.id = ab.authorId;
also, if you don't want to get NULL for some authors, you can apply such expression:
IFNULL(ab.count, 0) AS count

Select records order by count of another table not retrieving correct count in Mysql

In mysql db,i'm having two tables book and ratingtable.
book table has three fields ID,uniqueid,Title
ratingtable has three fields Book_ID,userid,rating
I need a query which retrieve records group by id (of book table) and order by count(rating) (of ratingtable).
I tried a query like this:
SELECT book.ID,book.Title,COUNT(ratingtable.rating) AS views
FROM book LEFT JOIN ratingtable ON book.ID = ratingtable.book_ID
GROUP BY book.ID
ORDER BY views DESC
But for three values it displays wrong count value. I dunno what happens.
EDIT:
ratingtable:
book:
Here the answer[count(rating)] should be 1 for all. But it displays
Help me to resolve this.
The result you're getting is actually quite correct, given that your book id's are not unique. But, to get the count you want you can use a subquery to first group the books, and then doing the counting:
SELECT b2.ID, b2.Title,COUNT(ratingtable.rating) AS views
FROM
(SELECT b1.id, b1.title FROM Book b1 Group by b1.id, b1.title) b2
LEFT JOIN ratingtable ON b2.ID = ratingtable.book_ID
GROUP BY b2.ID, b2.Title
ORDER BY views DESC
You need to group by both book id and title.
SELECT book.ID, book.Title, COUNT(ratingtable.rating) AS views
FROM book LEFT JOIN ratingtable ON book.ID = ratingtable.book_ID
GROUP BY book.ID, book.Title
ORDER BY views DESC
Or use correlated sub-query to count:
select distinct book.ID,
book.Title,
(select count(*) from ratingtable
where book.ID = ratingtable.book_ID) as views
from book
ORDER BY views DESC

Custom format mysql query and multiple values in one column

Forgive me if I get some terms wrong, still trying to learn advanced mySql queries. I am trying to export data from a ecommerce platform, and some of the data I need is in lookup tables, the issue is the one lookup table I have can have more than 1 value associated, and the system I need to get the data into requires a specific format for that column.
I am using zen cart as my source if that helps, and below is the query.
Select zp.products_id as id, zpd.products_name as name, products_price_w as cost, products_price as price, zp.products_date_added as date_created, zp.products_image as thumbnail
FROM `zen_products` as zp
LEFT JOIN `zen_products_description` as zpd ON zp.products_id = zpd.products_id
WHERE zp.products_status = 1
ORDER BY zp.products_id ASC;
What I need is theres a look up table that tells me what categories belong to each product, I know it's going to be another join, but I need it so if more than 1 category belongs to a product to put it in the same column, and join them with a # symbol, and its not the id's of the category, its the path, so I need to look up the category ID to another table to get the path.
So "category1/subcategory#category2" for example..
Thanks for any guidance.
EDIT: I still need to get the results merged as I am getting double the results I want, but I see a record for each category it's in with this..
Select zp.products_id as id, zpd.products_name as name, zcd.categories_name as categories, products_price_w as cost, products_price as price, zp.products_date_added as date_created, zp.products_image as thumbnail
FROM `zen_products` as zp
INNER JOIN `zen_products_description` as zpd ON zp.products_id = zpd.products_id
INNER JOIN `zen_products_to_categories`as zptc ON zp.products_id = zptc.products_id
INNER JOIN `zen_categories_description` as zcd on zptc.categories_id = zcd.categories_id
WHERE zp.products_status = 1
ORDER BY zp.products_id ASC;
What you are looking for is group_concat(). I think it would be something like this:
Select zp.products_id as id, zpd.products_name as name,
group_concat(zcd.categories_name separator '#') as categories,
products_price_w as cost, products_price as price,
zp.products_date_added as date_created, zp.products_image as thumbnail
FROM `zen_products` as zp
INNER JOIN `zen_products_description` as zpd ON zp.products_id = zpd.products_id
INNER JOIN `zen_products_to_categories`as zptc ON zp.products_id = zptc.products_id
INNER JOIN `zen_categories_description` as zcd on zptc.categories_id = zcd.categories_id
WHERE zp.products_status = 1
group by zp.products_id
ORDER BY zp.products_id ASC;

How to write mysql subselect properly with conditions and limiting

I have three Tables:
Posts:
id, title, authorId, text
authors:
id, name, country
Comments:
id, authorId, text, postId
I want to run a mysql command which selects the first 5 posts which were written by authors, whose country is 'Ireland'. In the same call, I want to retrieve all the comments for those five posts, and also the author info.
I've tried the following:
SELECT posts.id as 'posts.id', posts.title as 'posts.title' (etc. etc. list all fields in three table)
FROM
(SELECT * FROM posts, authors WHERE authors.country = 'ireland' AND authors.id = posts.authorId LIMIT 0, 5 ) as posts
LEFT JOIN
comments ON comments.postId = posts.id,
authors
WHERE
authors.id = posts.authorId
I had to include every field with an alias ^ because there was a duplicate for id, and more fields in future may become duplicates as I'm looking for a generic solution.
My two questions are:
1) I am getting a duplicate field entry from within my subselect for id, so do I have to list out all my fields as aliases again within the subselect or is there only one field I need for a subselect
2) Is there a way to auto-alias my call? At the moment I've just aliased every field in the main select but can it do this for me so there are no duplicates?
Sorry if this isn't very clear it's a bit of a messy problem! Thanks.
You are doing an unnecessary join back to the author table in your query. You get all the fields you want in the posts subquery. I would rename this to something other than an existing table, perhaps pa to indicate posts and authors.
You say you want the first 5 posts, but have no order clause. A better form of the query is:
SELECT pa.id as 'posts.id', pa.title as 'posts.title' (etc. etc. list all fields in three table)
FROM (SELECT *
FROM posts join
authors
on authors.id = posts.authorId
WHERE authors.country = 'ireland'
order by post.date
LIMIT 0, 5
) pa LEFT JOIN
comments c
ON c.postId = pa.id
Note that this returns the first five posts and their authors (as specified in the question). But one author may be responsible for all five posts.
In MySQL, you can use * and it will get rid of duplicate aliases in the from clause. I think this is dangerous. It is better to list all the columns you want.
To answer your questions:
You can select as many (or as few) columns as you need from a sub-query
You do not need to join the authors table again since you already selected all fields in the sub-query (and so get rid of duplicate columns names).
A few additional remarks...
... about the JOIN syntax
Prefer the form
FROM t1 JOIN t2 ON (t1.fk = t2.pk)
to the obsolete, obscure
FROM t1, t2 WHERE t1.fk = t2.pk
... about the use of a LIMIT clause without an ORDER BY clause
The order in which rows are returned by a SELECT statement without an ORDER BY clause is undefined. Therefore, a LIMIT n clause without an ORDER BY clause could return any n rows in theory.
Your final query should look like this:
SELECT *
FROM (
SELECT *
FROM posts
JOIN authors ON (authors.id = posts.authorId )
WHERE authors.country = 'ireland'
ORDER BY posts.id DESC -- assuming this column is monotonically increasing
LIMIT 5
) AS last_posts
LEFT JOIN comments ON ( comments.postId = last_posts .id )

MySQL showing duplicates in a column

Hi I'm trying to write a query to get information about the author, title, category and medium.
However as the items can be in many mediums and categories, I'm getting the results appearing duplicated in the columns. How can I get the results so I don't see medium as book,book,book and category as Horror,Fantasy,Fiction. I'm assuming I will need some sort of subquery - if so how would I do it?
SELECT book.bookid, book.author, book.title, group_concat(category.categorydesc), group_concat(medium.mediumdesc)
FROM book
Inner JOIN bookscategories ON book.bookid = bookscategories.bookid
Inner JOIN category ON bookscategories.categoryid = category.categoryid
Inner JOIN booksmediums ON book.bookid = booksmediums.bookid
Inner JOIN medium ON booksmediums.mediumid = medium.mediumid
GROUP BY book.bookid
Thanks
Tom
So as stated in comments, solution is to add the DISTINCT keyword in the GROUP_CONCAT() instructions like that:
... book.title, group_concat(DISTINCT category.categorydesc), group_concat(DISTINCT medium.mediumdesc) ...