I have 3 tables:
Table posts
post_id
category_id
title
Table visits
visit_id
post_id
visit_date
Table categories
category_id
category_name
I need to get something like this (assuming that table visits has 10 rows)
Category 1 (categories.category_name) / visits(count) = 5
Category 2 (categories.category_name) / visits(count) = 1
Category 3 (categories.category_name) / visits(count) = 4
How can I get a similar result using only MySQL?
I tried with INNERs, no success
It's a simple query. You need to join all three tables, and finally group the result by category name. It should look like:
select
c.category_name,
count(*)
from categories c
join posts p on p.categoryid = c.category_id
join visits v on v.post_id = p.post_id
group by c.category_name
order by c.category_name
I have simple magazine, and have tables with posts, comments, categories, etc. When listing single category, I want to have sum of comments per post in a listing, but that number is just wrong and it is driving me crazy. Note that single post can be in multiple categories.
Here are the simple tables structures
posts
id | title | categoryid | content | published
---------------------------------------------
comments
id | postid | comment
---------------------
category_rel
postid | categoryid
-------------------
categories
id | category
-------------
I use following sql (simplified to this example):
SELECT posts.*, categories.id AS categoryid,
categories.category AS categorytitle,
COUNT(comments.postid) AS CNT
FROM posts
LEFT JOIN comments ON posts.id = comments.postid
INNER JOIN category_rel ON posts.id = category_rel.postid
INNER JOIN categories ON category_rel.categoryid = categories.id
WHERE posts.published=1
GROUP BY posts.id;
This statement is giving me wrong results, sometning like it's cumulating number of categories post is member of and multiplying with actual number of comments. If I remove category part of SQL (which is not good, I need category Id and name) I receive proper values:
SELECT posts.*, COUNT(comments.postid) AS CNT
FROM posts
LEFT JOIN comments ON posts.id = comments.postid
WHERE posts.published=1
GROUP BY posts.id;
To be more specific:
One of posts have 1 comment and it is member of 7 categories. value CNT is going to 7, not 1.
Any idea how to change first SQL to get proper values?
You want to count the comments per post - not per category. So one way of achieving this would be to do the count first (in a subselect as MySQL has no CTE so far) and then join the results into category table:
SELECT countpost.*, categories.id AS categoryid,
categories.category AS categorytitle
FROM
-- subselect post and comment count
(
SELECT posts.*, count(comments.postid) as CNT FROM posts
LEFT JOIN comments ON posts.id = comments.postid
WHERE posts.published = 1
GROUP BY posts.id
) as countpost
-- join category table
INNER JOIN category_rel ON countpost.id = category_rel.postid
INNER JOIN categories ON category_rel.categoryid = categories.id ;
See this fiddle: http://sqlfiddle.com/#!9/f9c6f/1
For the sake of clarity and this question i will rename the tables so it is a bit clearer for everybody and explain what i want to achieve:
There is an input form with options that return categories ID's. If a 'Product' has 'Category', i want to return/find the 'Product' which lets say has multiple categories(or just 1) and all of its categories are inside the array that is passed from the form.
Products table
ID Title
1 Pizza
2 Ice Cream
Categories table
ID Title
1 Baked food
2 Hot food
ProductsCategories table
ID ProductId CategoryId
1 1 1
2 1 2
So if i pass [1,2] the query should return Product with id 1 since all ProductsCategories are inside the requested array, but if i pass only 1 or 2, the query should return no results.
Currently i have the following query which works, but for some reason if i create a second Product and create a ProductCategory that has a CategoryId same as the first product, the query returns nulll...
SELECT products.*
FROM products
JOIN products_categories
ON products_categories.product_id= products.id
WHERE products_categories.category_id IN (1, 2)
HAVING COUNT(*) = (select count(*) from products_categories pc
WHERE pc .product_id = products.id)
All help is deeply appretiated! Cheers!
In order to match all values in IN clause, you just need to know in addition the number of passed categories which you must use it in HAVING clause:
SELECT
p.*,
GROUP_CONCAT(c.title) AS categories
FROM
Products p
INNER JOIN ProductsCategories pc ON pc.productId = p.ID
INNER JOIN Categories c ON c.ID = pc.categoryId
WHERE
pc.categoryId IN (1,2)
GROUP BY
p.id
HAVING
COUNT(DISTINCT pc.categoryId) = 2 -- this is # of unique categories in IN clause
So in case IN (1,2) result is:
+----+-------+---------------------+
| id | title | categories |
+----+-------+---------------------+
| 1 | Pizza | Baked Food,Hot Food |
+----+-------+---------------------+
1 row in set
In case IN (1,3) result is Empty set (no results).
#mitkosoft, thanks for your answer, but sadly the query is not producing the needed results. If the product's categories are partially in the passed categories the product is still returned. Additionally i might not know how many parameters are sent by the form.
Luckily I managed to create the query that does the trick and works perfectly fine (at least so far)
SELECT products.*,
COUNT(*) as resultsCount,
(SELECT COUNT(*) FROM products_categories pc WHERE pc.product_id = products.id) as categoriesCount
FROM products
JOIN products_categories AS productsCategories
ON productsCategories.product_id= products.id
WHERE productsCategories.category_id IN (7, 15, 8, 1, 50)
GROUP BY products.id
HAVING resultsCount = categoriesCount
ORDER BY amount DESC #optional
That way the query is flexible and gives me exactly what I needed! - Only those products that have all their categories inside the search parameters(not partially).
Cheers! :)
I'm having trouble with a SQL query. My schema describes a many to many relationship between articles in the articles table and categories in the categories table - with the intermediate table article_category which has an id, article_id and category_id field.
I want to select all articles which only have the categories with id 1 and 2. Unfortunately this query will also select any articles which have those categories in addition to any others.
For example, this is a sample output from the SQL (with categories shown for descriptive purposes). You can see that while the query selected the article with id of 10, it also selected the article with an id of 11 despite having one extra category.
+-------+------------+
| id | categories |
+-------+------------+
| 10 | 1,2 |
| 11 | 1,2,3 |
+-------+------------+
This is the output that I want to achieve from selecting articles with only categories 1and 2.
+-------+------------+
| id | categories |
+-------+------------+
| 10 | 1,2 |
+-------+------------+
Likewise, this is the output what I want to achieve from selecting articles with only categories 1, 2 and 3.
+-------+------------+
| id | categories |
+-------+------------+
| 11 | 1,2,3 |
+-------+------------+
This is the SQL I have written. What am I missing to achieve the above?
SELECT articles.id
FROM articles
WHERE EXISTS (
SELECT 1
FROM article_category
WHERE articles.id = article_id AND category_id IN (1,2)
GROUP BY article_id
)
Many thanks!
Assuming you want more than just the article's id:
SELECT a.id
,a.other_stuff
FROM articles a
JOIN article_category ac
ON ac.article_id = a.id
GROUP BY a.id
HAVING GROUP_CONCAT(DISTINCT ac.category_id ORDER BY ac.category_id SEPARATOR ',') = '1,2'
If all you want is the article's id then try this:
SELECT article_id
FROM article_category
GROUP BY article_id
HAVING GROUP_CONCAT(DISTINCT category_id ORDER BY category_id SEPARATOR ',') = '1,2'
See it in action at http://sqlfiddle.com/#!2/9d213/4
Should also add that the advantage of this approach is that it can support the checking of any number of categories without having to change the query. Just make '1,2' a string variable and change what gets passed into the query. So, you could just as easily search for articles with categories 1, 2, and 7 by passing a string of '1,2,7'. No additional joins are needed.
You can left join category_id on category.id and then GROUP_CONCAT to get all categories, like you wrote in explanation (1st table) and than using HAVING match with any set you like ( '1,2' from example)
also with that approach you can easily make this query dynamic with php or any other language
SELECT articles.id
FROM articles
WHERE EXISTS (
SELECT GROUP_CONCAT(c.id) AS grp
FROM article_category
LEFT OUTER JOIN categories AS c ON c.id = article_category.category_id
WHERE articles.id = article_id
GROUP BY article_id
HAVING grp = '1,2'
)
Please Use Below Query You can do the Thing by Using Simple Query.
SELECT a.id, a.name
FROM articles a, categories c, articles_categories ac
WHERE
a.id = ac.article_id AND c.id = ac.category_id
AND c.id = 1 OR c.id = 2;
NOTE- If You have Many to many Relationship between two Tables Remove ID from the article_category table and make composite primary key Using article_id and category_id.
Thank you.
Maybe, something like:
select distinct article_id from article_cathegory
where category_id in (1,2)
minus
select distinct article_id from article_cathegory
where category_id not in (1,2)
Looks like simple solution for this could be the following:
SELECT
ac.article_id
, SUM(ac.category_id IN (1, 2)) AS nb_categories
, COUNT(ac.category_id) AS nb_all_categories
FROM
article_categories ac
GROUP BY
ac.article_id
HAVING
nb_categories=2 AND nb_all_categories=2
Here I count how many required categories we have and also count how many categories we have in total. We only need exactly 2 categories, so both required and total should be equal to 2.
This is quite flexible and for adding more categories just change categories list and numbers in HAVING statement.
SELECT articles.id
FROM articles
INNER JOIN articles_category ac ON articles.id = ac.article_id
WHERE articles.id IN (
SELECT ac1.article_id
FROM article_category ac1
WHERE ac1.category_id = 1;
)
AND ac.article_id = 2;
AND articles.id NOT IN (
SELECT ac2.article_id
FROM article_category ac2
WHERE ac2.category_id NOT IN (1, 2)
)
Far from the prettiest one I have written.
Basically, it limits first by ID that have a category id of 1, then it makes sure the records have a category of 2 as well, finally, it makes sure that it does not have any other categories
I like to approach these queries using group by and having. Here is an example:
select ac.article_id
from article_category ac
group by ac.article_id
having sum(case when category_id = 1 then 1 else 0 end) > 0 and
sum(case when category_id = 1 then 2 else 0 end) > 0;
Each condition in the having clause is testing for the presence of one of the categories.
I find that this approach is the most flexible for answering many different types of "set-within-sets" problems.
EDIT:
A slight variation on the above might be easier to generate:
having sum(category_id in (1, 2, 3)) = count(*) and
count(*) = 3
This will work assuming there are no duplicates in the data. You need to update the 3 to be the number of items in the in list.
to help just without changing your query very much, i think the logic has a bug. you dont want articles where a categegory 1,2 exists. You need articles where does not exist categories different than 1 and 2.
thanks & regards
In SQL Server I would do it with an INTERSECT and an EXCEPT. In MySQL, try this:
SELECT DISTINCT article_id
FROM article_category
WHERE category_id=1
AND article_id IN
(SELECT article_id
FROM article_category
WHERE category_id=2)
AND article_id NOT IN
(SELECT article_id
FROM article_category
WHERE category_id NOT IN (1,2))
Use this SQL query.
SELECT articles.id
FROM articles
WHERE articles.id in (
SELECT *
FROM article_category,articles
WHERE article_category.articles.id = articles.article_id AND article_category.category_id IN (1,2)
)
I'm trying to preform a SELECT query that grabs data from two tables only if certain data does not exist in a third table. Here are the three tables:
Blogs
id | blog_name
Featured Feeds
id | blog_id
Subscriptions
id | member_id | blog_id
I want to return the blog_id and blog_name of those listed in the Featured Feeds table, but exclude Featured Feeds that the user (member_id) is already subscribed to in the Subscriptions table. Here is the query I have thus far (using 3 for the member_id - please note the table names are slightly different than above):
SELECT featured_feeds.blog_id, featured_feeds.category_id, blogs.blog_name FROM featured_feeds LEFT JOIN blogs ON featured_feeds.blog_id = blogs.blog_id LEFT JOIN subscriptions ON subscriptions.blog_id = featured_feeds.blog_id WHERE subscriptions.blog_id != '3' ORDER BY blogs.blog_name ASC
This query doesn't exclude the Featured Feeds that the user is subscribed to if another user is subscribed to the same feeds. For example, if Subscriptions table had the following rows:
1 | 3 | 4
2 | 2 | 4
If blog_id 4 is listed in the featured feeds, my query will still return that blog_id due to the second row of data above. Do I need to use a GROUP?
Thanks in advance!
SELECT b.id, b.blog_name
FROM blogs b
INNER JOIN featured f on f.blog_id = b.id
WHERE b.id NOT IN (SELECT id FROM subscriptions WHERE member_id = 3)
Do a outer a left join, omitting the matching results.
SELECT * FROM `featured` f
LEFT OUTER JOIN `subscription` s
ON f.blog_id = s.blog_id
WHERE s.id IS null