Query returning multiple identical rows instead one - sql

I have the tables: juices, juice_ingredients and ingredients.
The juices table has the attributes:
name
barcode
The juice_ingredients table has the attributes:
juice_id
ingredient_id
And the ingredients table has
name
optional (boolean)
For the sake of customer's logistics various juices may have the same barcode, but with different ingredients, some of which are optional
I need select, by barcode, the single juice that has no contains optional ingredient among its ingredients.
I signed up four ingredients: water (optional: false), sugar (optional: true), pineapple pulp (optional: false) and mint (optional: true). And signed up four juices: one only with water and pineapple pulp, other with water, pineapple pulp and sugar, other with water, pineapple pulp and mint, and other with water, pineapple pulp, mint and sugar. All with the same barcode. I make a query to select only the juice make with non optional ingredients, in this case water and pineapple pulp.
SELECT *
FROM juices
INNER JOIN juice_ingredients ON (juice_ingredients.juice_id = juices.id)
INNER JOIN ingredients ON (juice_ingredients.ingredient_id = ingredients.id)
WHERE juices.barcode = '000000000001' AND ingredients.optional = false
But it was returning multiple rows. What should change this query to bring only one, or the juice containing no optional ingredients in the composition?

You could do it with a having clause:
SELECT juices.*
FROM juices
JOIN juice_ingredients ON juice_ingredients.juice_id = juices.id
JOIN ingredients ON juice_ingredients.ingredient_id = ingredients.id
WHERE juices.barcode = '000000000001'
GROUP BY 1, 2
HAVING MAX(ingredients.optional::text) = 'false'

Since you didn't specify which database you are using, you may have to adjust the SQL for your specific database:
select *
from juices j
where j.barcode = '000000000001'
and not exists (select *
from juice_ingredients ji
inner join ingredients i
on (i.ingredient_id = ji.ingredient_id
and i.optional = true)
where ji.juice_id = j.juice_id)

Related

PostgreSQL join duplicates rows

I am using PostgreSQL and I am new to it. I am attempting to join a table to two other tables but the results are being duplicated. I have the following tables.
MEAL
id
name
ingredients
flavors
abc
Creamy Chicken Soup
{def,ghi,jkl}
{mno}
INGREDIENT
id
name
def
chicken
ghi
corn
jkl
pepper
FLAVOR
id
name
mno
spicy
And here is my query
SELECT
meal.id,
meal.name,
JSON_AGG(i) as ing,
JSON_AGG(f) as flav,
FROM meal LEFT JOIN
(SELECT
ingredient.id,
ingredient.name
FROM ingredient) i
ON (i.id = ANY(meal.ingredients)) LEFT JOIN
(SELECT
flavor.id,
flavor.name
FROM flavor) f
ON (f.id = ANY(meal.flavors))
GROUP BY
meal.id,
meal.name
And the results are:
id
name
ing
flav
abc
Creamy Chicken Soup
[{id: "def",name:"chicken"},{id:"ghi",name:"corn"},{id:"jkl",name:"pepper"}]
[{id:"mno",name:"spicy"},{id:"mno",name:"spicy"},{id:"mno",name:"spicy"}]
As you can see the flavors are being duplicated the same number of times as the ingredient count. How can I do this query without the duplicates. Unfortunatly I do not have any control over the table structure as it is being pulled in from a third party. I can maniputlate the data in code but I would prefer to query it and get back the correct data set.

Use of USING in SQL

A restaurant provides wine pairings for most food items on its menu. The structure of two of the tables containing this information is shown below
Join these two tables by their id columns to find the country that the recommended wine is produced in.
Here is the code I have tried:
SELECT country, item
FROM regions
INNER JOIN pairing
regions.id = pairing.id
ORDER BY item
LIMIT 5;
But the compiler gives the solution as:
SELECT country, item
FROM regions
INNER JOIN pairing
USING(id)
ORDER BY item
LIMIT 5;
OUTPUT:
country
item
France
caviar
Italy
curry
Italy
grilled vegetables
Argentina
lamb
Germany
roast duck
Doubt:
I want to clear if there is any difference bwtween USING and equal statement on id or they are same?

SQL query without join

I have the following tables
Table food Table Race Table animal
+------------+--------------+ +------------+--------------+ +------------+--------------+
| Quantity | animal_id | | race_code | race_name | | animal_id | race_code |
+------------+--------------+ +------------+--------------+ +------------+--------------+
I was asked to calculate the average food quantity for every race (race_name). The challenge here is that I should not use JOIN because we have not studied it yet.
I have written the following query:
select AVG(f.quantity),r.race_name from food f, race r
group by r.race_name;
but it doesn't work as I want it to be since it returns the same average food quantity for all races. I know I have to use the animal table to link the other 2 but I didn't know how. I should be using subqueries
That question is exactly the same as your previous, where you had to use SUM (instead of AVG). No difference at all.
Ah, sorry - it wasn't you, but your school colleague, here
Saying that you "didn't learn joins", well - what do you call what you posted here, then? That's a cross join and will produce Cartesian product, once you fix the error you got by not including non-aggregated column into the group by clause and include additional joins required to return desired result.
The "old" syntax is
select r.name,
avg(f.quantity) avg_quantity
from race r, animal a, food f
where a.race_code = r.race_code
and f.animal_id = a.animal_id
group by r.name;
What you "didn't learn yet" does the same, but looks differently:
from race r join animal a on a.race_code = r.race_code
join food f on f.animal_id = a.animal_id
The rest of the query remains the same.
Nowadays, you should use JOINs to join tables, and put conditions into the WHERE clause. For example, condition would be that you want to calculate averages for donkeys only. As you don't have it, you don't need it.
You still have to do some matching of related rows. If not explicitly with JOIN you can do it in the WHERE clause. Ie something like
select AVG(f.quantity),r.race_name
from food f, race r, animal a
where f.animal_id = a.animal_id and a.race_code = r.race_code
group by r.race_name;
select race_name ,(select avg(quantity) from food where animal_id in (select animal_id from animal a where r.race_code = a.race_code))
from race r

Select if distinct count > 2?

[EDITED 10/03/2019 as per request]
Have a large dataset and need to map 2 ids, on different rows, to one 'conflict id'.
For example.
These rows must be attributed to a single value, "Apples & Pears".
I then need to know which persons have both "Apple" and "Pears", through this value, not just one of then.
It's a many to one relationship, that must be complete. Users must not be attributed "Apples & Pears“ if they do not have BOTH requirements.
I have master table of one to one relation for each fruit to each combination, but only want them to be joined with user table if the user has both fruits.
Table 1: User, Fruit
Josh, Apple
Josh, Pear
Tom, Apple
Kate, Pear
Table 2: Fruit, Product
Apple, Apples&Pears
Pear, Apples&Pears
Table 3 (OUTCOME I WANT TO ACHIEVE):
Josh, Apple, Apples&Pears
Josh, Pear, Apples&Pears
Tom, Apple, NULL
Kate, Pear, NULL
Welcome to S/O. Typically you want to provide more realistic sample table structures and data to portray at least critical to your question. if ever private data, make up, but keep it in accurate context.
That being said, I don't think you actually have a table of apples, pears and apples & pears, but I will play along.
I assume (example) you have a table of people, and another table of things they are associated with in a child table. I would do a join per person FIRST, to those that DO have an association with the "apples & pears" record, then do a LEFT-JOIN to same child table once for apples, another for pears. You can determine what you want to do with results (such as delete from the individual apples, pears child records).
Select
p.LastName,
p.FirstName,
case when justApples.PersonID IS NULL
then 'No' else 'Yes' end as AlsoHasApples,
case when justPears.PersonID IS NULL
then 'No' else 'Yes' end as AlsoHasPears
from
Person p
JOIN Fruits f
on p.PersonID = f.personID
AND f.Description = 'Apples & Pears'
LEFT JOIN Fruits justApples
on p.PersonID = justApples.PersonID
AND f.Description = 'Apples'
LEFT JOIN Fruits justPears
on p.PersonID = justPears.PersonID
AND f.Description = 'Pears'
Since the first "(inner) JOIN" explicitly is looking for those that ARE marked as both apples & pears record, that minimizes the list to just those people pre-qualified.
THEN, the two "LEFT-JOIN" are explicitly looking for the OTHER individualized "Apples" and "Pears" respectively.
By testing the IS NULL will indicate if the person does (or not via NULL) have that item.
If you ONLY WANT to return those that are Apples & Pears AND have one or both of the other individual items, you can add a WHERE clause at the end such as
WHERE
justApples.PersonID IS NOT NULL
OR justPears.PersonID IS NOT NULL
An inverse would be to pre-qualify the justApples and justPears and NOT marked as associated with Apples & Pears such as by REQUIRING the JOINS to apples and pears individually and LEFT-JOIN the combination record
Select
p.LastName,
p.FirstName,
case when f.PersonID IS NULL
then 'No' else 'Yes' end as IsAssociatedWithApplesAndPearsRecord
from
Person p
JOIN Fruits justApples
on p.PersonID = justApples.PersonID
AND f.Description = 'Apples'
JOIN Fruits justPears
on p.PersonID = justPears.PersonID
AND f.Description = 'Pears'
LEFT JOIN Fruits f
on p.PersonID = f.personID
AND f.Description = 'Apples & Pears'
The two joins to the individual REQUIRE each individual part, and the LEFT-JOIN shows IF they are also marked as the combination entry. As you can see by the ambiguity in your question, providing a little more detail in what you want can significantly help getting a more accurate answer to your true needs. Try not to mask details to context, but mask the sample data in itself where more private issues are of concern.

SQL Joins issue

I have 3 database tables.
First one containing Ingredients, second one containing Dishes and the third one which is conecting both Ingredients and Dishes.
Adding data to those tables was easy but I faced a problem while trying to select specific content.
Reurning all ingredients for specific dish.
SELECT *
FROM Ingredient As I
JOIN DishIngredients as DI
ON I.ID = DI.IngredientID
WHERE DI.DishID = 1;
But If i try to query for dish Name and Description no matter what kind o join I use i always get number of results equal to number of used Ingredients. If i have 4 ingredients in my dish then select returns Name and Description 4 times, how can I modify my slect to select those values just once?
Here is result of my query (same as hawk's) if i try to select Name and Description. I am using MS SQL.
ID Name Description DishID IngredientID
-- -------------------- -------------------------------------------------------------------- ------ ---------
1 Spaghetti Carbonara This delcitious pasta is made with fresh Panceta and Single Cream 1 1
1 Spaghetti Carbonara This delcitious pasta is made with fresh Panceta and Single Cream 1 2
Kuzgun's query worked fine for me. However from your sugestions I see that I dont really need join between DishIngredient and Dish.
When I need Name and Descritpion I can simply go for
SELECT * FROM Dish WHERE ID=1;
Wehn I need list of Ingredient I can use my above query.
If you need to display both dish details and ingredient details, you need to join all 3 tables:
SELECT *
FROM Ingredient As I
JOIN DishIngredients as DI
ON I.ID = DI.IngredientID
JOIN Dish AS D
ON D.ID=DI.DishID
WHERE DI.DishID = 1;
If you don't care about ingredient,you don't have to use the table DishIngredient.Just use tale Dish.select * from dish d where d.id=1.
If you want to know what the ingredient is ,the sql that you use just query the id of table ingredient.It's useless.Because of the design of your database ,a little redundancy is a must .
select * from dish d join dishingredient di on d.id=di.dishid join ingredient i on
i.id=di.ingredientid where d.id=1
Of course,you will get number of results that contain dish's name and description.
If you want to get the full information but the least redundancy,you can do it in two step:
select * from dish d where d.id=1;
select * from ingredient i join DishIngredient di on i.id=di.ingredientid where di.dishid=1
In java ,you can write a class to represent a dish and a list to represent the ingredients it use.
public class Dish {
BigDecimal id;
String name;
String description;
List<Ingredient> ingredient;
}
class Ingredient{
BigDecimal id;
String name;
.....
}