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
Related
Using mysql. Database sakila.
Having two tables films and language. Film table has two foreign keys language_id and original_language_id that connects to language_id primary key in language.
So what I want is to learn how to get film, it's language and original language from such structure.
enter image description here
The query will display film in its language, without original.
SELECT
film.title, film.description, film.release_year, language.*
FROM
film
INNER JOIN
language ON film.language_id = language.language_id;
If I understood correctly, you want to select the films having language other than original language. I think a where clause will serve the purpose.
SELECT
film.title, film.description, film.release_year, language.*
FROM
film INNER JOIN language
ON film.language_id = language.language_id;
where film.language_id <> film.original_language_id
Or, If you just want to display language information for both the language you can follow one of below query (using subquery):
SELECT film.title, film.description, film.release_year,
(select name from language l where l.language_id=film.language_id) language_name,
(select name from language l where l.language_id=film.original_language_id) original_language_name
FROM film
Maybe you can try this
SELECT a.title, a.description, a.release_year, b.language_id, b.name, b.last_update
FROM
film a
INNER JOIN
language b ON a.language_id = b.language_id;
I am working on the first problem of the famous SQLzoos and am working on the using Null section: http://sqlzoo.net/wiki/Using_Null
The question is:
List the teachers who have NULL for their department.
The corresponding SQL query would be:
SELECT t.name
FROM teacher t
WHERE t.dept IS NULL
Is this a type of anti-join? Specifically, is this a left-anti-join?
This isn't a join at all.
The statement is filtering only records for teachers who don't have an assigned department.
Set Difference
The set difference of teachers and departments, teacher \ department would be a kind of "anti-join"
SELECT
t.name
FROM teacher t
LEFT JOIN department d ON d.id = t.dept_id
WHERE d.id IS NULL
At first glance, this statement does what your statement does, if the foreign key reference was enforced, it would guarantee to do exactly that. However, one use for this statement would be to retrieve teachers who are assigned to departments that have since been deleted (e.g. if the English Lit Dept. & English as 2nd Lang Dept. were reorganized as the English Dept.)
Symmetric Difference
Another "anti-join" would be the symmetric difference, which selects elements from both sets ONLY if they cannot be joined, i.e
(teacher \ department) U (department \ teacher)
I can't think of a motivating example using teachers and departments, but one way to write the symmetric difference on databases that support the FULL OUTER JOIN would be:
SELECT
t.name
FROM teacher t
FULL OUTER JOIN department d ON d.id = t.dept_id
WHERE d.id IS NULL OR t.id IS NULL
For MySQL, this statement would have to be written as the union of two statements.
SELECT
t.name teacher_name, d.name department_name
FROM teacher t
LEFT JOIN department d ON d.id = t.dept_id
WHERE d.id IS NULL
UNION ALL
SELECT
t.name teacher_name, d.name department_name
FROM teacher t
LEFT JOIN department d ON d.id = t.dept_id
WHERE t.id IS NULL
Looking through one of my projects, I found this one use of symmetric difference:
Context:
I have three tables: users, users_gameplay_summary, users_transactions_summary. I needed to email those users who created their accounts in the past 7 days AND one of the following
have transacted but have not played or played but have not transacted.
To get the list, I have this query (note, this was written for Postgresql, and won't work on MySQL, but it illustrates the symmetric difference use case):
SELECT
COALESCE(g.user_id, t.user_id) user_id
FROM users_gameplay_summary g
FULL OUTER JOIN users_transactions_summary t ON t.user_id = g.user_id
WHERE COALESCE(g.user_id, t.user_id) IN (
SELECT user_id
FROM users
WHERE created_at > CURRENT_DATE - '7 day'::interval)
AND (g.user_id IS NULL OR t.user_id IS NULL)
Not exactly, your not actually joining anything now,
in the case of a left anti join you would have access to the department name as well. (although it would be NULL)
Your sql code would be a correct answer for the question you gave though.
A left anti join would be:
SELECT t.name
FROM teacher t
LEFT JOIN dept d ON d.id = t.dept
WHERE d.id IS NULL
To solve this problem of listing teachers without assigned departments, you don't need a JOIN between teacher and dept tables.
dept table is basically a dictionary table that you join to, to translate ids to corresponding names.
teacher table has a dept column which normally could have a FOREIGN KEY constraint to id column in dept table.
Your query is not an ANTI-JOIN. This is a simple projection and selection query using one table.
SELECT t.name
FROM teacher t
WHERE t.dept IS NULL
For an ANTI-JOIN you would at least need a JOIN operation between more than one table at first.
Normally an ANTI-JOIN could look like:
Using LEFT JOIN
SELECT *
FROM table1 t1
LEFT JOIN table2 t2
ON t1.join_column = t2.join_column
WHERE t2.join_column IS NULL
Using NOT EXISTS
SELECT *
FROM table1 t1
WHERE NOT EXISTS (
SELECT 1
FROM table2 t2
WHERE t1.join_column = t2.join_column
)
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"
)
I'm stuck on a MySQL query. I have a temporary orders table with the following structure:
session_id
product_id
product_type -- 'institute','state','region','country'
For all institutes, states, regions and countries I have individual tables.
I want to create a MySQL query which fetches the data from my temp table and makes the join with the corresponding table depending upon product_type field.
If I use left join with 5 tables or use union it could be a really time consuming task; so I was looking for something different.
I would advise checking the answers in this question as they seem to match your specific problem https://stackoverflow.com/a/9327678/1213554
The short version though is that in order to be able to efficiently perform this request a database restructuring may well be required I'm afraid.
What you are looking for specifically is not possible, you'll have to use a UNION to do something along the lines of the following. As you say it will be a time consuming task though.
(
SELECT tempData.*
FROM tempData
INNER JOIN institutes
ON institutes.id = tempData.product_id
WHERE tempData.product_type = 'institute'
) UNION (
SELECT tempData.*
FROM tempData
INNER JOIN states
ON states.id = tempData.product_id
WHERE tempData.product_type = 'state'
) UNION (
SELECT tempData.*
FROM tempData
INNER JOIN regions
ON regions.id = tempData.product_id
WHERE tempData.product_type = 'region'
) UNION (
SELECT tempData.*
FROM tempData
INNER JOIN countries
ON countries.id = tempData.product_id
WHERE tempData.product_type = 'country'
)
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.