Check if matching record exists in joined table - mysql

I have two tables: categories and prodvscats
categories prodvscats
id | id
title | categories_id
each table has some rows for example:
categories prodvscats
categ1 | categ1
categ2 | categ2
categ3
categ4
I would like to return all categories but I want to know which of them are existing in the prodvscats table.
Desired result:
title boolean variable exists
categ1 | 1
categ2 | 1
categ3 | 0
categ4 | 0
I tried with join but it returns only the first two rows. Maybe it needs something like case but I don't know how to do.
SELECT categories.id, categories.title
FROM categories
INNER JOIN prodvscats ON prodvscats.categories_id = categories.id
ORDER BY id

Use LEFT JOIN to join the two tables. This will return all rows including those where category does not exist in the other table. Then use GROUP BY to consolidate categories and COUNT to determine if match(es) exist:
SELECT
categories.id,
categories.title,
CASE WHEN COUNT(prodvscats.id) = 0 THEN 0 ELSE 1 END AS product_exists
FROM categories
LEFT JOIN prodvscats ON categories.id = prodvscats.categories_id
GROUP BY categories.id, categories.title

Try like this
SELECT categories.id, categories.title,
IF(prodvscats.categories_id IS NULL, '0', '1') AS exists_value
FROM categories
LEFT JOIN prodvscats ON prodvscats.categories_id = categories.id
ORDER BY id

Related

SQL filter with AND rule spanning multiple rows

I have a MySQL database which looks something like:
item table
| id | name |
item_category link table
| item_id | category_id |
category table
| id | name |
If I want to fetch items that are related to one of many categories I can simply do:
SELECT item.*
FROM item
JOIN item_category ON item_category.item_id = item.id
LEFT JOIN category ON category.id = item_category.category_id
WHERE category.name in ("category_one", "category_two")
However, if I want to get items which are related to all of a list of categories then the problem becomes a little more complicated because the rows returned from my query contain a single category each. How can I write a query which contains only the items which are related to all categories?
I tried writing a query with a nested select like this:
SELECT item.*
FROM item
WHERE EXISTS (
SELECT item.id
FROM item_category ON item_category.item_id = item.id
LEFT JOIN category ON category.id = item_category.category_id
WHERE item_category.id = item.id
AND category.name = "category_one"
)
AND EXISTS (
SELECT item.id
FROM item_category ON item_category.item_id = item.id
LEFT JOIN category ON category.id = item_category.category_id
WHERE item_category.id = item.id
AND category.name = "category_two"
)
But this is incredibly unperformant even with indexes on the relevant fields.
Thank you for any input on this issue.
The typical ways to do this are to either (1) join to the "category" cable once for each value that must be matched, or (2) aggregate your first query (grouping on item) and filter where count(distinct category.name) = number of items in your value list.
Consider the following...
DROP TABLE IF EXISTS category;
CREATE TABLE category
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO category VALUES
(101,'animals'),
(102,'minerals'),
(103,'vegetables');
DROP TABLE IF EXISTS item_category;
CREATE TABLE item_category
(item_id INT NOT NULL
,category_id INT NOT NULL
,PRIMARY KEY(item_id,category_id)
);
INSERT INTO item_category VALUES
(1,101),
(1,102),
(1,103),
(2,102),
(3,101),
(3,103);
By inspection, we can see that only item 1 is associated with all categories.
So, how do we select those items that aren't?
SELECT DISTINCT ic.item_id
FROM item_category ic
JOIN category c ON c.id <> ic.category_id
LEFT
JOIN item_category x
ON x.item_id = ic.item_id
AND x.category_id = c.id
WHERE x.item_id IS NULL;
+---------+
| item_id |
+---------+
| 2 |
| 3 |
+---------+
The list of items pertaining to ALL categories, is the inverse of this set.

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.

select count items each group

I have two tables category and adverts, i need to select all categories and the number of adverts it has in the adverts table that has at least count greater than zero
the category table
cat_id | Name
----------------
1 | Toys
2 | fashion
3 | electronics
The adverts table
cat_id | title
----------------
1 | a
2 | b
2 | c
1 | d
2 | e
what i expect
cat_id | count | Name
-----------------------
1 |2 | a
2 |3 | b
The query i tried
Select
c.name, c.cat_id,c.parent_id, #count:= (Select Count(av.cat_id) From adsview av Where av.cat_id = c.cat_id)
from
category c WHERE #count > 0
i am getting and empty result, what am i doing wrong?
If you want to make sure that the cat_id from category table are in adverts table you need to join as
select
c.cat_id,
c.Name,
count(a.cat_id) as `count`
from category c
join adverts a on a.cat_id = c.cat_id
group by c.cat_id ;
select cat_id, count(*)
from adverts
group by cat_id;
So the mySQL query engine will grab every single row from the adverts table, it'll put them into neat piles where all rows in the pile have the same category, it'll count the number of rows in each pile, and then it'll return to you a result row for each pile with the pile's id and the number of rows.
Now lets add something: we want to also get the category's name. So we indicate that in the select clause, and add a join to the from clause. The join says "for every row in table a, consider it alongside every row in table b. if some condition holds, put this combined row into the from set". You can see that joins are actually quite slow in SQL (relatively).
select c.cat_id, count(*) as count, c.name
from adverts as a join categories as c on a.cat_id = c.cat_id
group by c.cat_id;
Note also that I've aliased the tables as a and c respectively, so as to remove the ambiguity over the column name cat_id (otherwise the mySQL query engine may get confused).
You can try this, mate:
SELECT
c.cat_id,
COUNT(a.cat_id) AS count,
a.title
FROM
category c
LEFT JOIN adverts a ON a.cat_id = c.cat_id
GROUP BY
c.cat_id
HAVING
count > 0;
or this:
SELECT
c.cat_id,
COUNT(a.cat_id) AS count,
a.title
FROM
category c
INNER JOIN adverts a ON a.cat_id = c.cat_id
GROUP BY
c.cat_id;
You have to use group by function like below
select cat_id, count(*) as count
from adverts
group by cat_id;

SELECT all items in common between two users on three tables

I have three tables
item_to_user (to store the relations between user and item)
| item_to_user_id | user_id | item_id |
-----------------------------------------
item_tb
| item_id | item_name |
-----------------------
user_tb
| user_id | user_name |
-----------------------
An item can belong to one or more user and viceversa, that's why I'm using the first table.
So, given the user_id = A and user_id = B how can I do a mysql query to select all the items the belong both to user A and user B?
note: I wrote a similar question yesterday but was about two tables not three.
SELECT i.*
FROM item_tb AS i
LEFT JOIN item_to_user AS iu
ON iu.item_id = i.item_id
LEFT JOIN user_tb AS u
ON iu.user_id = u.user_id
WHERE u.user_name IN ('A', 'B')
GROUP BY i.item_id
HAVING COUNT(i.item_id) > 1
By prequerying common items between A and B (via count(*) = 2) will pre-limit the final list of items possible to get details from. Then joining that to the items table as SECOND table in the query should help performance. Especially if A&B have 50 common items, but your items table consists of 1000's of items.
select straight_join
i.item_id,
i.item_name
from
( select iu.item_id
from item_to_user iu
join user_tb u
on iu.user_id = u.user_id
and u.user_name in ( 'A', 'B' )
group by 1
having count(*) = 2 ) Matches,
item_tb i
where
Matches.item_id = i.item_id
If a user can't have repeated items then this simple one will work:
select item_id
from item_to_user
where user_id in ('A', 'B')
group by item_id
having count(*) > 1