Where clause on a string rather than id - mysql

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.

Related

SELECT with different values for foreign key

I have 2 tables:
Products(..., category_id)
Categories(id, name, level_1_parent_id, level_2_parent_id)
category_id is foreign key for Categories(id)
If it's first level category level_1_parent_id is NULL,
If second level category level_2_parent_id is NULL, level_1_parent_id is set,
if third level both are set.
I'm selecting products like this
SELECT *
FROM Products
WHERE category_id = ${category_id}
What I need to achieve:
Select products from child categories if its first or second level category.
So for example if I'm selecting from category with id == 1 (which is first level id) I want to select products with category_id equals 1 and other categories with level_1_parent_id == 1 and the same for second level category.
Is that possible somehow?
we can use nested query here.
SELECT * FROM Products WHERE category_id = ${category_id} or
category_id in (select distinct id from Categories where level_1_parent_id = ${category_id} or level_2_parent_id = ${category_id})
You get the category list by joining the table to itself first, then get the products associated with each of the categories found. Note the use of alias's for each of the tables in the from and join clauses.
SELECT *
FROM Categories c1
LEFT JOIN Categories c2
ON c2.level_1_parent_id = c1.id
LEFT JOIN Categories c3
ON c3.level_1_parent_id = c1.id AND c3.level_2_parent_id = c2.id
JOIN Products p
ON p.category_id = c1.id OR p.category_id = c2.id OR p.category_id = c3.id
WHERE c1.category_id = ${category_id}

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

MySQL Join Subcategory

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)

select all books from one category except books which belong to other category

I have 3 tables: books, book_categories, categories.
book_categories table "joins" books and categories. It contains columns: id,book_id,category_id.
So one Book may belong to many categories and one Categorie may have many books.
I need query which retrieves all books from given_category except books which belongs to given_set_of_categories. So for example I want all books from category A but only if they don't belong also to category B or C. I need also sort (order) the result by Book.inserted column.
I know how to get all books from given_category with 2 joins but can't figure out how to exclude some books from other categories in result. I cant filter books in PHP because I am paginating the search result.
where
category_id = <given category>
and books.book_id not in
(
select book_id from book_categories
where category_id in (<given set of cat>)
)
order by books.inserted
So, if you mean it is in one category but not in any other:
AND EXISTS(SELECT * FROM books b JOIN book_categories bc ON b.id = bc.book_id JOIN categories c ON bc.category_id = c.id AND c.id = 'A')
AND NOT EXISTS(SELECT * FROM books b JOIN book_categories bc ON b.id = bc.book_id JOIN categories c ON bc.category_id = c.id AND c.id != 'A')
I think that this can be achieved through counting provided that book_categories entries are unique, thus the combination book_id & category_id are not repeating. Instead of trying directly to exclude records, we select from the combined set of categories [,] and then we'll count book_id entries that belong to the :
COUNT(IF(category_id = <given_category>, 1, NULL)) as cnt_exists
and after ensuring that it contains the required category, we count the total to see if it belongs to any other category as well:
COUNT(*) AS cnt_total
SELECT * FROM books b JOIN (
SELECT book_id,
COUNT(IF(category_id = <given_category>, 1, NULL)) as cnt_exists,
COUNT(*) AS cnt_total FROM book_categories WHERE
category_id IN(<given_category>, <given_set_of_categories>)
) bc ON b.id = bc.book_id AND
cnt_exists = 1 AND cnt_total = 1 ORDER BY b.inserted

mySQL SELECT FROM table WHERE ... AND ... AND ... AND

I have a table "articles" with columns and data:
article_id title body
1 This is the title This is the body text
2 Another title Another body text
Another table "category" with columns and data:
category_id category
1 localnews
2 visible
3 first10
And a table "categories" with columns and data:
categories_id article_id category_id
1 1 1
2 1 2
3 1 3
4 2 1
5 2 3
I want to SELECT the row(s) WHERE categories.category_id = 1 AND =2 AND =3
I'm using:
SELECT articles.article_id, articles.title, articles.body,
categories.article_id, categories.category_id
FROM articles, categories
WHERE articles.article_id = categories.article_id
AND categories.article_id = 1
AND categories.article_id = 2
AND categories.article_id = 3
but it doesn't work. Obviously mySQL needs another syntax.
Can someone help?
Thanks
SELECT
Articles.article_id,
COUNT( Categories.article_id ) AS total
FROM CategoryArticles
LEFT JOIN Articles USING (article_id)
WHERE
CategoryArticles.category_id IN (1,2,3)
GROUP BY CategoryArticles.article_id
HAVING total = 3
I used a bit different names for table because in your example the distinction between category and categories is hard to notice.
An column of a row cannot be 1, 2 or 3 at the same time, which is what AND stipulates. Use OR in your WHERE condition. Better yet - for readability - you can use IN:
SELECT ...
WHERE `categories`.`article_id` IN(1,2,3)
In addition to the commonly used IN() and using a HAVING count, I would be interested in the performance difference by doing a multiple-join as follows...
SELECT STRAIGHT_JOIN
articles.article_id,
articles.title,
articles.body
FROM
categories c1
JOIN articles
on c1.article_id = articles.article_id
JOIN categories c2
on c1.article_id = c2.article_id
AND c2.category_id = 2
JOIN categories c3
on c1.article_id = c3.article_id
AND c3.category_id = 3
WHERE
c1.Category_ID = 1
Yes, this may look obscure, but lets think about it... by doing a join FIRST on the categories table where ONE of your specific categories -- THIS FIRST FROM instance of categories should be representative of whichever category would have the smallest granularity. Ex: Your categories of Local News, Visible and First 10. Local news would probably have the most entries, while Visible and First 10 would have even less... of those, which would have even the smallest number of records. Use THIS category as the where clause.
So, say you have 100,000 articles, and 90,000 are in local news, 45,000 in Visible, and 12,000 in First 10. By starting your query on only those in the 12,000, you are eliminating most of the data.
By then joining to the articles table, and categories AGAIN as alias C2 and C3 respectively based on the other conditions, if found, done, if not, its excluded.
Again, I'm wondering the performance impact. I would also have a compound index on the categories table on both (article_id, category_id)
The value cannot be all three values simultaneously, so you'd better use an IN clause in your WHERE to define which you want to return. Give you've already got a join condition there, you'd want to move that to an ON clause instead as well; ie:
SELECT articles.article_id, articles.title, articles.body, categories.article_id, categories.category_id
FROM articles
INNER JOIN categories ON articles.article_id = categories.article_id
WHERE categories.article_id IN ( 1, 2, 3 )
Of course, you can go to the next step and do:
SELECT articles.article_id, articles.title, articles.body, category.category
FROM articles
INNER JOIN categories ON articles.article_id = categories.article_id
INNER JOIN category ON categories.category_id = category.category_id
WHERE categories.article_id IN ( 1, 2, 3 )
If instead you wanted to show only articles that appear in all three categories, then you could take an approach like:
SELECT articles.article_id, articles.title, articles.body
FROM articles
INNER JOIN categories AS c1
ON articles.article_id = c1.article_id
AND c1.category_id = 1
INNER JOIN categories AS c2
ON articles.article_id = c2.article_id
AND c2.category_id = 2
INNER JOIN categories AS c3
ON articles.article_id = c3.article_id
AND c3.category_id = 3