Database Table Relations Issue - mysql

Situation:
I got news. NEws are posted in multiple categories.
Issue:
How to store relations between news and categories? Should I create three tables (news, categories, news_categories)? Is this the best method possible?
Let's see examples:
I want to query database for latest news, so I need to:
1. query for the news using join to query for news categories
2. in the loop, query to get categories name.
So, I have 10 news (10 queries) X amount of categories = total queries. Not too much?
Update
Here's my example. There are three following tables.
-- news --
ID
Title
-- categories --
ID
Name
-- news_categories --
ID
NewsID
CategoryID
No matter what query I use, it returns all projects and projects categories names correctly, however... projects are displayed few times, according to amount of categories. Maybe I'm too tired or something, but I really can't see any solution.
Update 2
Here's my query:
SELECT N.Title, C.Name
FROM x_news_categories AS NC
INNER JOIN x_news AS N
ON N.ID = NC.NewsID
INNER JOIN x_categories AS C
ON C.ID = NC.CategoryID
and here's its result:
Title Name
Test PHP
Test2 MySQL
Test2 CSS
Anybody has any suggestions how to solve that issue?
Regards,
M.

Yes three tables is the correct implemenation with foreign keys between news and newscategory and category and newscategory.
You're query could be something like this to return all the News Titles for a specific category name:
SELECT N.Title
FROM news_categories AS NC
INNER JOIN news AS N
ON N.ID = NC.NewsID
INNER JOIN Categories AS C
ON C.ID = NC.CategoryID
WHERE C.Name = #Category
You could simplify it if you already knew the CategoryID, which you probably would from a dropdown or something, then the Join to the Category table would be unnecessary and you write something like:
SELECT N.Title
FROM news_categories AS NC
INNER JOIN news AS N
ON N.ID = NC.NewsID
WHERE NC.CategoryID = #CategoryID
To select All News items with their categories in a comma separated list you would need a User Defined Function. Here the SQL code needed to help with the logic, but the actual implementation is up to you:
CREATE FUNCTION fnCategoryList
(
#NewsID INT
)
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #CategoryList VARCHAR(1000)
SET #CategoryList = ''
SELECT #CategoryList = COALESCE(#CategoryList + ',','') + C.[Name]
FROM news_categories AS NC
INNER JOIN categories AS C
ON NC.CategoryID = C.ID
WHERE NC.NewsID = #NewsID
RETURN #CategoryList
END
Using the above UDF your query would look like this:
SELECT Title, fnCategoryList(ID) AS Categories
FROM news
Depending on the number of records in your news table, this query will not perform very well. You should have some form of WHERE clause on nearly every query you write.

Related

MySQL JOIN one of the rows from second table

I have 2 tables: one for the restaurants containing general information, and the other - for it's description translations. The second table has such columns:
restaurant_id, description, language
The client application makes request for the list of restaurants, and passes the language it wants the result for. So now I need to JOIN those 2 tables. Everything would be easy, if each restaurant had description in each of available languages, but it does not. Some restaurants are missing descriptions in some languages.
So in case if the description is missing in selected language, I should return a description in default language (let it be eng for example).
So the question is how can I JOIN the tables the way that if the description in selected language is available - join with it, otherwise - join with eng?
What you can do is left join onto your translations table with the chosen language and also inner join onto translations again with the default language.
Then you can use COALESCE to chose the first, non-null value. For example
SELECT r.name, COALESCE(t.description, t_default.description) as description
FROM restaurant r
LEFT JOIN translation t ON r.id = t.restaurant_id
AND t.lang = 'fr' -- the chosen language
INNER JOIN translation t_default ON r.id = t_default.restaurant_id
AND t_default.lang = 'en' -- the default language
Demo ~ http://sqlfiddle.com/#!9/aa68c0/1
I've used an INNER JOIN for the default language to ensure there's a value to display. If you're fine with a possible NULL result, change it to a LEFT JOIN.
One more solution is to use temporary table to select IDs of the restaurants which have translation, and then using UNION to combine the restaurants with translation + all default restaurants excluding restaurant IDs having translations:
Create temporary table with IDs:
CREATE TEMPORARY TABLE IF NOT EXISTS existing_ids AS (SELECT r.id FROM restaurant r JOIN translation t ON r.id = t.restaurant_id WHERE t.lang = 'fr');
Select rows having translation:
SELECT r.name, t.description FROM restaurant r JOIN translation t ON r.id = t.restaurant_id WHERE t.lang = 'fr'
Union with rows with default translation excluding IDs from the temporary table:
UNION
SELECT r.name, t.description FROM restaurant r JOIN translation t ON r.id = t.restaurant_id WHERE t.lang = 'en' AND id not in (SELECT id from existing_ids)
Demo:
http://sqlfiddle.com/#!9/d3e66dc/1

Understanding joins: Listing categories without specified product

I am new to MySQL, trying to understand how join works.
I have two tables: categories and products each with id and name fields.
Third table is product_to_category (many to many) with category_id and product_id.
I wanted to list all categories containing specified product (with id = 5).
To do this, I made a following query:
SELECT `categories`.`id`, `categories`.`name`
FROM `categories`
LEFT JOIN `product_to_category`
ON `categories`.`id` = `product_to_category`.`category_id`
WHERE `product_to_category`.`product_id` = 5
I am wondering if it is possible to something opposite: to list all categories which does not contains specified product?
Many thanks!
I'd use the not exists operator for this:
SELECT c.`id`, c.`name`
FROM `categories` c
WHERE NOT EXISTS (SELECT *
FROM `product_to_category` ptc
WHERE c.`id` = ptc.`category_id` AND
ptc.`product_id` = 5)

SQL Join involving 3 tables, how to?

SQL newbie here.
So we have 3 tables:
categories(cat_id,name);
products(prod_id,name);
relationships(prod_id,cat_id);
It is a one-to-many relationship.
So, given a category name say "Books". How do I find all the products that come under books?
As an example,
categories(1,Books);
categories(2,Phones);
products(302,Sherlock Holmes);
relationships(302,1);
You need to JOIN the three tables.
SELECT p.*
FROM relationships r
INNER JOIN products p
ON p.prod_id = r.prod_id
INNER JOIN categories c
ON c.cat_d = r.cat_id
WHERE c.name = 'Books'
You have to join tables on related columns and specify WHERE clause to select all records where category name = 'Books'
SELECT p.*
FROM categories c
JOIN relationships r ON c.cat_id = r.cat_id
JOIN products p ON r.prod_id = p.prod_id
WHERE c.name = 'Books' -- or specify parameter like #Books
In SQL you often join related tables and beginners tend to join, whatever the situation. I would not recommend this. In your case you want to select products. If you only want to show products data, select from products only. You want to select products that are in the category 'Books' (or for which exists an entry in category 'Books'). Hence use an IN or EXISTS clause in order to find them:
select * from products
where prod_id in
(
select prod_id
from relationships
where cat_id = (select cat_id from categories where name = 'Books')
);
Thus you get a well structured query that tells the reader easily how the tables are related and what data you are actually interested in. Later, with different tables and data to select, this may keep you from duplicate result rows that you must get rid of by using DISTINCT or from getting wrong aggregates (sums, counts, etc.), because of mistakenly considering records multifold.
try this:
select p.Prod_id,p.name
from products p inner join relationships r on
p.prod_id = r.prod_id
where r.cat_id = (select cat_id from categories where name = 'books')
or
select p.Prod_id,p.name
from products p inner join relationships r on
p.prod_id = r.prod_id inner join categories c on c.cat_id = r.cat_id
where c.name = 'books'

Incomprehensible query behaviour

I have multiple tables, related by multiple foreign keys as in the following example:
Recipes(id_recipe,name,calories,category) - id_recipe as PK.
Ingredients(id_ingredient,name,type) - id_ingredient as PK.
Contains(id_ingredient,id_recipe,quantity,unit) - (id_ingredient,id_recipe) as PK, and as Foreign Keys for Recipes(id_recipe) and Ingredients(id_ingredient).
You can see this relations represented in this image.
So basically Contains is a bridge between Recipes and Ingredients.
The query I try to write it's supposed to give as result the names of the recipes whose ingredients type are "bovine" but not "lactic".
My attempt:
SELECT DISTINCT Recipes.name
FROM Ingredients JOIN Contains USING(id_ingredient) JOIN Recipes USING (id_recipe)
WHERE Ingredients.type = "bovin"
AND Ingredients.type <> "lactic";
The problem is it still shows me recipes that have at least one lactic ingredient.
I would appreciate any help!
This is the general form of the kind of query you need:
SELECT *
FROM tableA
WHERE tableA.ID NOT IN (
SELECT table_ID
FROM ...
)
;
-- EXAMPLE BELOW --
The subquery gives the id values of all recipes that the "lactic" ingredient is used in, the outer query says "give me all the recipes not in that list".
SELECT DISTINCT Recipes.name
FROM Recipes
WHERE id_recipe IN (
SELECT DISTINCT id_recipe
FROM `Ingredients` AS `i`
INNER JOIN `Contains` AS `c` USING (id_ingredient)
WHERE `i`.`type` = "lactic"
)
;
Alternatively, using your original query:
You could've changed the second join to a LEFT JOIN, changed it's USING to an ON & included AND type = "lactic" there instead, and ended the query with HAVING Ingredients.type IS NULL (or WHERE, I just prefer HAVING for "final result" filtering). This would tell you which items could not be joined to the "lactic" ingredient.
A common solution of this type of question (checking conditions over a set of rows) utilizes aggregate + CASE.
SELECT R.Name
FROM Recipes R
INNER JOIN Contains C
on R.ID_Recipe = C.ID_Recipe
INNER JOIN Ingredients I
on C.ID_Ingredient = I.ID_Ingredient
GROUP BY R.name
having -- at least one 'lactic' ingredient
sum(case when type = 'lactic' then 1 else 0 end) = 0
and -- no 'bovin' ingredient
sum(case when type = 'bovin' then 1 else 0 end) > 0
It's easy to extend to any number of ingredients and any kind of question.
Hijacked the fiddle of xQbert
SELECT R.NAME
FROM CONTAINS C
INNER JOIN INGREDIENTS I
ON I.ID_INGREDIENTS = C.ID_INGREDIENTS AND I.TYPE = 'bovine' AND I.TYPE <> "lactic"
INNER JOIN RECIPES R
ON R.ID_RECIPE = C.ID_RECIPE
GROUP BY R.NAME
That should work, maybe you need to escape 'contains'. It could be recognized as a SQL function.
SQL Fiddle
In my example burgers and pasta have 'Bovin' and thus show up. So do cookies but cookies also have 'lactic' which is why they get excluded.
SELECT R.Name
FROM Recipes R
INNER JOIN Contains C
on R.ID_Recipe = C.ID_Recipe
INNER JOIN Ingredients I
on C.ID_Ingredient = I.ID_Ingredient
LEFT JOIN (SELECT R2.ID_Recipe
FROM Ingredients I2
INNER JOIN Contains C2
on C2.ID_Ingredient = I2.ID_Ingredient
INNER JOIN Recipes R2
on R2.ID_Recipe = C2.ID_Recipe
WHERE Type = 'lactic'
GROUP BY R2.ID_Recipe) T3
on T3.ID_Recipe = R.ID_Recipe
WHERE T3.ID_Recipe is null
and I.Type = 'Bovin'
GROUP BY R.name
There likely is a more elegant way of doing this. I really wanted to CTE this and join it to itself.. but no CTE in mySQL. Likely a way to do this using exists too.... I'm not a big fan of using IN clauses as the performance generally suffers. Exists fastest, Joins 2nd fastest, in slowest (generally speaking)
The inline view (sub query) returns the ID_recipe of those you don't want to include.
The outer query returns the Name of the recipes with ingredients you want.
By joining these two together using an outer join we return all recipes and only those with the undesired ingredient. We then limit the results to only those where the recipe ID doesn't exist for the undesired ingredient. (undesired ingredient not found) you'll get only those recipes having all desired ingredients.
You can use NOT EXISTS for this.
Try this:
SELECT DISTINCT Recipes.`name`
FROM Recipes JOIN Contains AS C1 USING (id_recipe) JOIN Ingredients USING(id_ingredient)
WHERE Ingredients.type = "bovin"
AND NOT EXISTS (
SELECT 1
FROM Contains AS C2 JOIN Ingredients USING(id_ingredient)
WHERE C1.id_recipe = C2.id_recipe
AND Ingredients.type = "lactic"
)

mysql query with multiple left join in 3 tables?

i have a search query which will retrieve information from 3 tables i made the query so it retrieve the information from 2 tables and i don't know if i can combine the third one or not
SELECT *
FROM articles
INNER JOIN terms
ON articles.ArticleID = terms.RelatedID AND terms.TermType = 'article'
the third query is
SELECT * FROM categories where CategoryID in (something)
something is a filed in the articles tables which have value like '3,5,8'
i want do this 2 queries into 1 query and i don't know if it can be done by 1 query or not
without looking at your schema (which would be helpful) and some sample data try this query
SELECT *
FROM categories,articles
INNER JOIN terms
ON (articles.ArticleID = terms.RelatedID AND terms.TermType = 'article')
WHERE
FIND_IN_SET(categories.CategoryID,articles.categories)
here is the definition for FIND_IN_SET()
http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_find-in-set
If i understand you correctly. Looks like you have multiple categories for each article with the Category IDs all stored as a concatenated string.
SELECT A.*
FROM articles A
INNER JOIN terms T on A.ArticleID = T.RelatedID AND T.TermType = 'article'
LEFT JOIN categories C on C.CategoryID IN (3,5,8 OR A.CategoryIDs)
GROUP BY C.CategoryName
You want to LEFT JOIN since you may or may not have multiple categories, you can group by Categories to get disticnt category article pairs and CONCAT() to recombine article records as needed.