Get all values from intermediate table - mysql

I have a mySQL three-table many-to-many setup with link table, simplified here:
categories
category_id
category_name
categories2entries
category_id,
entry_id
entries
entry_id
entry_text
I need to return all entries for a given category name but I want to include a list of all the categories a given entry is attached to. For example, if a search is for Addictions and the entry is also listed in categories Mental health and Young people, I want an output field for each result with all three categories listed.
How do I do this?

You need direct lookup for to get the list of entries, and backward lookup for to get categories list for each entry. Backward lookup must use another tables copies.
SELECT e.entry_text,
GROUP_CONCAT(c2.category_name) belongs_to
FROM categories c1
JOIN categories2entries ce1 ON c1.category_id = ce1.category_id
JOIN entries e ON ce1.entry_id = e.entry_id
JOIN categories2entries ce2 ON ce2.entry_id = e.entry_id
JOIN categories c2 ON c2.category_id = ce2.category_id
WHERE c1.category_name = 'Category Name'
GROUP BY e.entry_text
If you need the same for more than one category (maybe even all) then
SELECT c1.category_name,
e.entry_text,
GROUP_CONCAT(c2.category_name) belongs_to
FROM categories c1
JOIN categories2entries ce1 ON c1.category_id = ce1.category_id
JOIN entries e ON ce1.entry_id = e.entry_id
JOIN categories2entries ce2 ON ce2.entry_id = e.entry_id
JOIN categories c2 ON c2.category_id = ce2.category_id
/* WHERE c1.category_name IN ({Category Names list}) */
GROUP BY c1.category_name,
e.entry_text

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}

Pull data from 3 tables

I have 3 tables as follows :
Table 1: Product
id_product [Primary Key],added_time.
Table 2: Category
id_category [Primary Key],Category_name.
Table 3: product_category
id_category,id_product [Both Foreign Keys]
I want to pull Data as
Category_name,No Of Products in this Category,Last time when product was added to Category(Latest product added_time).
You could use this SQL:
SELECT Category.Category_name,
Count(DISTINCT Product.id_product) AS num_products,
Max(Product.added_time) last_added_time
FROM Category
LEFT JOIN product_category
ON product_category.id_category = Category.id_category
LEFT JOIN Product
ON Product.id_product = product_category.id_product
GROUP BY Category.Category_name;
Note that by using LEFT JOIN you will be certain to list all categories even those for which no products exist. If you don't want those, replace both LEFT keywords with INNER.
Note also that in standard SQL you need to GROUP BY any columns you mention in the SELECT list, unless they are aggregated, like with MAX or COUNT.
SELECT C.`Category_name`,
(SUM(IF(P.`id_product`IS NULL,0,1))) AS No_of_Products,
MAX(P.`added_time`) AS Latest_time
FROM
Category C
LEFT JOIN
product_category P_C ON C.`id_category` = P_C.`id_category`
LEFT JOIN
Product P ON P.`id_product` = P_C.`id_product`
GROUP BY C.`id_category`
Hope this helps.

Left Join not returning all rows in left table

I have a film database, one of the tables is 'categories' which holds all of the genres a movie in the database could have.
Running the following query:
SELECT category.name FROM category
Gives back the rows:
Action
Animation
Children
Classics
Comedy
Documentary
Drama
Family
Foreign
Games
Horror
Music
New
Sci-Fi
Sports
Travel
I am trying to query the database to find the # of each particular genre a specific actor has a role in, but I want it to return ALL of the genres with the # in the next column. This was my initial query:
SELECT q1.name AS 'Film Category', COUNT(q2.name) AS '# of Films Ed Chase has appeared in'
FROM (
SELECT category.name FROM category
) AS q1
LEFT JOIN (
SELECT category.name FROM category
INNER JOIN film_category ON film_category.category_id = category.category_id
INNER JOIN film ON film.film_id = film_category.film_id
INNER JOIN film_actor ON film.film_id = film_actor.film_id
INNER JOIN actor ON actor.actor_id = film_actor.actor_id
WHERE actor.first_name = 'ED' AND actor.last_name = 'CHASE'
) AS q2
ON q1.name = q2.name AND '# of Films Ed Chase has starred in' >= 0
GROUP BY q2.name
ORDER BY q2.name;
And it gives back this table, missing some of the rows (I want it to give all the genres, not just the one's that the actor had a role in):
Film Category # of Films Ed Chase has appeared in
Animation 0
Action 2
Classics 2
Documentary 6
Drama 3
Foreign 2
Music 1
New 2
Sci-Fi 1
Sports 2
Travel 1
Interestingly it returned '0' for 'Animation' but no rows for 'Children' or 'Comedy' which is the result I was looking for (all genres returned). What am I doing wrong?
COUNT() doesn't count NULLs, and your left join puts NULLs in the records where there isn't a value in the right-hand table. Your outer join actually is (or should be - check this) returning all the desired rows, but the outer select with the aggregate function is eating them.
Try moving your count() into the right-hand subquery, and then in your outer query replace it with IFNULL(q2.count, '0') to replace NULLs with zeros.
I will note that you should be grouping on q1.name, not q2.name.

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

Join to give list with optional relationship column

Tables:
users: id INT
items: id INT
setid INT
sets: id INT
relationships: userid INT
itemid INT
relationship ENUM('owner', 'participant')
Given a userid and a setid, we need to generate a list of all the items in the set, and the user’s relationship to each item, if a relationship exists. i.e. the results would
setid itemid relationship
---------------------------------
1 1 NULL
1 2 owner
....
The following doesn't work, because the second where clause eliminates rows where relationship is null:
select
sets.id as setid,
items.id as itemid,
relatonships.relationship as relationship
from sets
inner join items on sets.id = items.setid
left join relationships on relationships.itemid = items.id
where
sets.id = 5
and relationships.userid = 27
However, the second where clause eliminates rows where there is no existing relationship between that item and the given user. How can this be done with a single query?
It was very simple, I didn't understand you could test against a constant in a 'join ... on' clause:
select
sets.setid,
items.itemid,
relationships.relationship
from sets
inner join items on items.setid = sets.id
left join relationships
on relationships.itemid = items.id
and relationships.userid = 5
where
sets.id = 1
Not 100% what you are asking, by try this
select
sets.id as setid,
items.id as itemid,
relatonships.relationship as relationship
from sets
inner join items on sets.id = items.event
LEFT join relationships on relationships.itemid = items.id
where
sets.id = [say, 5]
and relationships.userid = [say, 27]
Keep in mind that the relationship column will be NULL if none exists. Also, the relationship reference in the WHERE clause could eliminate rows as well. Hope this gets you start, again I might misunderstand what you are trying to achieve. From your table design, there does not seem to be a relationship between the user and the sets, could you possibly the list of fields in the tables.
Try the following query:
select sets.id as setId,
items.id as ItemId,
relationships.relationship
from sets
join users on 1=1
join items on sets.id=items.setid
left join relationships on relationships.itemId = items.id
where
sets.id=5 and users.id = 27
By adding the users table to the join with 1=1, you do not need the where clause to reference the relationship table at all
Change the left join to:
left join relationships on relationships.itemId = items.id
AND relationships.userId=users.id