MySQL Join Subcategory - mysql

I have the categories and pages tables. My table is structure is something like this :
pages one has three columns
id | category_id | content
1 2 example page of subexample
2 1 example page of example
categories one has four
id | is_parent | parent | name
1 1 NULL example
2 0 1 subexample
I want to get the all pages of that category, if its parent, i want to include the pages which are the member of its sub categories also.
With the example i gave, think like, when user selected to see the whole contents of the example category, i want him to see the example page of example and example page of subexample.
$query = "SELECT * FROM `pages` WHERE `category_id` = :cid
union
select * from `pages` p
join `categories` k ON k.id = p.category_id
where k.parent = :cid";
i've tried the above code, but not worked for me. i'm not sure with my logic also.

You can try
SELECT *
FROM pages
WHERE category_id IN
(
SELECT c.id
FROM categories c LEFT JOIN categories p
ON c.parent = p.id
WHERE p.id = :cid OR c.id = :cid
)
or
SELECT *
FROM pages p
WHERE category_id IN
(
SELECT :cid
UNION ALL
SELECT id
FROM categories
WHERE parent = :cid
)
Here is SQLFiddle demo
Note: it will only work for one level deep meaning category and its subcategories only. If a subcategory were to have other subcategories then you need to use dynamic SQL to traverse the categories tree.

SELECT pages.* FROM pages,categories
WHERE pages.category_id=categories.id
AND (categories.id=:cid OR parent=:cid)

Related

Many-To-Many select only rows with exactly same tags

I have 3 tables: tags, products and relation table between them.
Relation table looks for example like this:
tagId | ProductId
1 | 1
2 | 1
2 | 9
The user can pick two options "All of these" or "One of these".
So if user picks All of these, it's means that the product must have exactly all of tags which the user chose.
So if user pick tags with id 1 and 2, it should select only product with id 1, because this product has exactly the same tags the user chose. (Another way is if the user picks the tag with id 2, it should select only product with id 9.)
So, the product has to have all tags which the user chose (no more, no less).
SQL that I already have for Any/One of these:
SELECT DISTINCT s.SKU
FROM SKUToEAN as s
LEFT JOIN ProductDetails as p ON s.ProductDetailID=p.id
JOIN ProductTagRelation as ptr ON (ptr.productId=p.id and ptr.tagId IN(Ids of selected tags))
Example behavior:
TagId = 1 it should select => None
TagId = 2 it should select => 9
TagId = 1,2 it should select = 1,9
So probably I need two queries. One for any/one of these ( I already have this one ) and the second for all of these.
With PHP I decide which query to use.
You can GROUP BY on the ProductID and use conditional aggregation based filtering inside the Having clause. MySQL automatically casts boolean values to 0/1 when using in numeric context. So, in order to have a specific tagID value available against a ProductID, its SUM(tagId = ..) should be 1.
All of these:
SELECT ptr.productId, s.SKU
FROM SKUToEAN AS s
LEFT JOIN ProductDetails AS p
ON p.id = s.ProductDetailID
JOIN ProductTagRelation AS ptr
ON ptr.productId = p.id
GROUP BY ptr.productId, s.SKU
HAVING SUM(ptr.tagID = 1) AND -- 1 should be there
SUM(ptr.tagID = 2) AND -- 2 should be there
NOT SUM(ptr.tagID NOT IN (1,2)) -- other than 1,2 should not be there
Is this you are looking for (for all condition)?
select product.id
from products
inner join <table> on products.id = <table>.productId
group by product.id
having group_concat(<table>.tagId order by <table>.tagId separator ',') = '1,2';

How to properly count rows in table2 related to items in table1 with clause non related to table1

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

Where clause on a string rather than id

I need to get all articles out of my database if they fall under a category or sub category.
articles:
id | title | content | category_id (fk)
categories
id | title | parent_id
1 toys 1
2 trains 1
3 pets 3
I perform:
SELECT * FROM categories LEFT JOIN articles ON categories.id = articles.category_id WHERE categories.id = ? OR WHERE categories.parent_id = ?
The above works, but now I want to use the category title instead of an id. So something like:
SELECT * FROM categories LEFT JOIN articles ON categories.id = articles.category_id WHERE **categories.title** = ? OR WHERE ??? not sure how to handle this bit
But im not sure how to handle the OR WHERE, as I don't know the categories id value.
Is there a way to do this without performing a category id lookup query first?
You could, first, get a list of categories that are child categories of a parent category and the parent category, then, join to find the matching articles.
SELECT * FROM(
SELECT *
FROM categories
WHERE title = 'toys'
UNION
select a.*
FROM categories a
JOIN categories b ON (a.parent_id = b.id)
WHERE b.title = 'toys'
) c
JOIN articles ON (c.id = articles.category_id);
SELECT * FROM categories c
LEFT JOIN articles ON c.id = articles.category_id
WHERE **c.title** = ? OR
c.title IN (select title from categories ca where c.title = ? AND ca.id = c.parent_id)
You can control if title of row's parent match whether or not matches with special title. There might be more efficient ways but this design is very similar to yours.

MySQL Select Where In Many to Many

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)
)

how i do this sql trick

I have a table in database for categories and sub categories. Its internal structure is:
id int not null primary
name text
subcatfrom int
it contains some of rows for categories and its sub categories. I want "SELECT" sql command to fetch categories and grouping their sub categories after it for every root category as following for example :
-cat1
--subcat1
--subcat2
-cat2
--subcat1
--subcat2
is it possible ?
The original question wants the subcategories on separate rows. Here is one way:
select name
from ((select category as name, 1 as iscat, category as sortorder
from t
) union all
(select tsub.category as name 0 as iscat, t.category as sortorder
from t join
tsub on
on t.subcategory_id = s.category_id
)
) a
where not exists (select 1 from category c where c.subcategory_id = a.sortorder limit 1)
order by sortorder, iscat desc, name
What is this doing? The inner union all is bringing together all categories and subcategories. It is assigning what you want in the table as well as information for sorting. The overall ordering is by "parent" category name.
The where clause is limiting this data to categories that are not the subcategory of anything else -- the top level categories.
I am making the assumptions that-
1. you have just one level of parent child relationship. ie subcategory can't have further sub-category
2. For top level category, value of subcatfrom is 0
SELECT * FROM
(
SELECT NAME AS parent_category_name, '' AS child_category_name FROM categories WHERE subcatfrom = 0
UNION
SELECT b.NAME AS parent_category_name, a.NAME AS child_category_name FROM categories a JOIN categories b ON a.subcatfrom = b.id
) a ORDER BY a.parent_category_name, a.child_category_name;
That's very easy but with this structure
Table: category_id , name , parent_id
Sample Data
category_id name parent_id
1 A 0
2 B 0
3 C 1
4 D 1
This means A is a category which has 2 subcategories C and D. And parent_id 0 means it is a parent category
Now the sql is
SELECT lc.category_id,
lc.name,
rc.subcategories
FROM categories
LEFT JOIN (
SELECT
subcategory_id ,
GROUP_CONCAT(name) AS subcategories
FROM categories) AS rc
ON lc.category_id = rc.parent_id
This will give you the following result
category_id name subcategories
1 A C,D
2 B (null)