SQL Server - OR clause in join confusion - sql

I have the table structure like below
Package
PACK_ID | DESCR | BRAND_ID
1 | Shoes | 20
2 | Cloths| NULL
ITEMS
ITEM_ID | PACK_ID | BRAND_ID
100 | 1 | 10
101 | 1 | NULL
102 | 1 | 10
BRANDS
NAME | BRAND_ID
A | 10
B | 20
I want to write a query to list how many items are there in a package grouped by same brand. If the brand is not defined in the item it should get it from package.
Note: Brand_id in both package and items are nullable
My query is this
SELECT count (*) as count,p.descr as descr,b.name FROM [items] item
inner join [package] p on item.pack_id= p.pack_id
inner join [brands] b on b.brand_id = item.brand_id or b.brand_id = p.brand_id
where p.pack_id = 1
group by b.name,p.descr
and my result is
COUNT | descr | NAME
2 | Shoes | a
3 | Shoes | B
whereas i expect the result to be something like this
COUNT | descr | NAME
2 | Shoes | a
1 | Shoes | B
could you please suggest what is wrong with my code? Thanks in advance.

Try using ISNULL on your join condition:
SELECT count (*) as count,p.pack_id as pack_id,b.name FROM [items] item
inner join [package] p on item.pack_id= p.pack_id
inner join [brands] b on b.brand_id = ISNULL(item.brand_id, p.brand_id)
where p.pack_id = 1
group by b.name,p.pack_id
Your OR was causing it to join to multiple rows, this should use the item by default and then fall back to the package.

I would tend to approach this by getting the brand for both the item and the package. Then decide which one to use in the select:
SELECT count(*) as count, p.descr as descr, coalesce(bi.name, bp.name) as name
FROM [items] item inner join
[package] p
on item.pack_id= p.pack_id left join
[brands] bi
on bi.brand_id = item.brand_id left join
brands bp
on b.brand_id = p.brand_id
where p.pack_id = 1
group by coalesce(bi.name, bp.name), p.descr;
One key advantage to this approach is performance. Databases tend to do a poor job when joins are on expression or or conditions.

Related

SQL join on table twice not bringing expected results

I have two tables (extraneous columns removed to exemplify the issue):
-People-
PID | CarID1 | CarID2
----------------------
1 | 1 | 3
2 | 5 | NULL
3 | 1 | NULL
4 | NULL | 1
-Cars-
CarID
-----
1
3
5
I'm creating a view based on the CarID so using:
SELECT
c.CarID,
COUNT(p.PID) AS pCount
FROM
Cars c
LEFT JOIN People p ON p.CarID1 = c.CarID OR p.CarID2 = c.CarID
Group By c.CarID
Brings back the expected results:
CarID | pCount
--------------
1 | 3
3 | 1
5 | 1
The issue being that on a table with 1000+ car id's and 25,000 people, this can take a long time (taking out the OR clause means it takes milliseconds)
So I was trying to do it another way like this:
SELECT
c.CarID,
COUNT(p1.PID) AS pCount1,
COUNT(p2.PID) AS pCount2
FROM
Cars c
LEFT JOIN People p1 ON p1.CarID1 = c.CarID
LEFT JOIN People p2 ON p2.CarID2 = c.CarID
Group By c.CarID
It's many times quicker, but because CarID 1 exists in both CarID1 and CarID2 I'm getting this:
CarID | pCount1 | pCount2
-------------------------
1 | 3 | 3
3 | 0 | 1
5 | 1 | 0
When I would expect this:
CarID | pCount1 | pCount2
-------------------------
1 | 2 | 1
3 | 0 | 1
5 | 1 | 0
And I could just sum the pCount1 and pCount2
Is there any way I can achieve the results of the first query using the 2nd method? I'm presuming the GROUP BY clause has something to do with it, but not sure how to omit it.
How about unpivoting the columns and then joining:
SELECT v.CarID, COUNT(p.PID) AS pCount
FROM People p CROSS APPLY
(VALUES (p.CarID1), (p.CarID2)) v(CarID) JOIN
Cars c
ON v.CarID = c.CarId
WHERE v.CarID IS NOT NULL
GROUP BY v.CarID;
If you want to keep cars even with no people, then you can express this as a LEFT JOIN:
SELECT c.CarID, COUNT(p.PID) AS pCount
FROM Cars c LEFT JOIN
(People p CROSS APPLY
(VALUES (p.CarID1), (p.CarID2)) v(CarID)
)
ON v.CarID = c.CarId
GROUP BY c.CarID;
Here is a db<>fiddle.
Is the p.CarID1 a Primary Key?
If so it would explain that a join on the carID1 is fast but on the carID2 it's slow.
Try creating an Index on CarID2 and see if that solves your performance issues.
The index would turn it from a full table scan into an index lookup. Which is a lot faster.
CREATE NONCLUSTERED INDEX CarId2Index
ON p.CarID2;
If that solves it you can keep your query as it is.
Alternatively you can send us the query explain plan so we can see what is slowing it down.
Try using SUM with condition like below.
SELECT
c.CarID,
SUM(IIF(p1.PID IS NULL, 0, 1)) AS pCount1,
SUM(IIF(p2.PID IS NULL, 0, 1)) AS pCount2
FROM
Cars c
LEFT JOIN People p1 ON p1.CarID1 = c.CarID
LEFT JOIN People p2 ON p2.CarID2 = c.CarID
Group By c.CarID
Try with COALESCE function:
SELECT
c.CarID,
COUNT(p.PID) AS pCount
FROM
Cars c
LEFT JOIN People p ON COALESCE(p.CarID1, p.CarID2) = c.CarID
Group By c.CarID

In a PostgreSQL query how to filter results from a field in a join

My table structure is as follows:
Restaurants
| id | name |
|----|---------|
| 1 | The Hut |
| 2 | T Burger|
Dishes:
| id | name |
|----|---------|
| 1 | Pizza |
| 2 | Caramel |
Orders:
| id | locatio |
|----|---------|
| 1 | New York|
| 2 | London |
_RestaurantDishes:
In here, B represents restaurants and A represents dishes.
A restaurant can have many dishes. A dish can only have one restaurant.
| id | A | B |
|----|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
_DishOrders:
In here, B represents orders and A represents dishes.
A dish can have many orders. An order can have many dishes.
| id | A | B |
|----|---|---|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
What I want to do is, get a list of dishes from a selected list of restaurants and sort them according to the orders count. I tried to do it like this:
SELECT count(dishOrder.id) as myCount, dish.id, name
FROM "default$default"."Dish" dish
left join "default$default"."_RestaurantDishes" dishRestaurant on dish.id = dishRestaurant."A"
left join "default$default"."_DishOrders" dishOrder on dish.id = dishorder."A"
where "dishRestaurant"."B" in ("1", "2")
group by dish.id order by mycount desc;
But it gives me the error ERROR: missing FROM-clause entry for table "dishRestaurant". I tried many other approaches but didn't work.
you are getting error ERROR: missing FROM-clause entry for table "dishRestaurant" because
you used double quote in number in ("1", "2") which turned into column name for using double quote. you have to change it like in (1,2)
SELECT count(dishOrder.id) as myCount, dish.id, name
FROM "default$default"."Dish" dish
left join "default$default"."_RestaurantDishes" dishRestaurant on dish.id = dishRestaurant."A"
left join "default$default"."_DishOrders" dishOrder on dish.id = dishorder."A"
where "dishRestaurant"."B" in (1, 2)
group by dish.id ,name
order by myCount desc;
you also used dish.id, name in selection but not used name in group by, so you have to use that in group by
Don't escape identifiers if you can avoid it. The escaping fixes the casing -- and strange things happen.
I suspect that you want:
select count(*) as myCount, d.id, d.name
from "default$default"."Dish" d left join
"default$default"."_RestaurantDishes" dr
on dr."A" = d.id and
dr."B" in (1, 2) left join
"default$default"."_DishOrders" do
on d.id = do."A"
group by d.id, d.name
order by mycount desc;
Notes:
I simplified the table aliases and removed the double quotes from the aliases defined in the query.
You should remove the double quotes from the column names -- if you can.
I added d.name back into the group by. This is not needed if d.id is unique.
I use count(*), so no columns are referenced.
I moved the filtering condition to the on clause. This keeps the left join intact.

Postgres SQL: getting group count

I have the following table
>> tbl_category
id | category
-------------
0 | A
1 | B
...|...
>>tbl_product
id | category_id | product
---------------------------
0 | 0 | P1
1 | 1 | P2
...|... | ...
I can use the following query to count the number of products in a category.
select category, count(tbl.product) from tbl_product
join tbl_category on tbl_product.category_id = category.id
group by catregory
However, there are some categories that never have any product belonging to. How do I get these to show up in the query result as well?
Use a left join:
select c.category, count(tbl.product)
from tbl_category c left join
tbl_product p
on p.category_id = c.id
group by c.category;
The table where you want to keep all the rows goes first (tbl_category).
Note the use of table aliases to make the query easier to write and to read.

Counting Distinct Records Using Multiple Criteria From Another Table In MySQL

This Is Not Homework. I have changed the names of the tables and fields, for illustrative purposes only. I admit that I am completely new to MySQL. Please consider that in your answer.
The best way to illustrate the function of the query I need is like this:
I have two tables.
One table has a 0..1 to 0..n relationship to the other table.
For Simplicities Sake Only, Suppose that the two tables were Recipe and Ingredient.
One of the fields in the Ingredient table refers to the Recipe table, but may be null.
Just For Example:
I want to know the SQL for something like: How many recipes call for "Olives" in the amount of "1" AND "Mushrooms" in the amount of "2"
Being brand new to The Structured Query Language, I'm not even sure what to google for this information.
Am I on the right track with the following?:
SELECT COUNT(DISTINCT Recipe.ID) AS Answer FROM Recipe, Ingredient
WHERE Ingredient.RecipeID=Recipe.ID AND Ingredient.Name='Olives'
AND Ingredient.Amount=1 AND Ingredient.Name='Mushrooms'
AND Ingredient.Amount=2
I realize this is totally wrong because Name cannot be BOTH Olives And Mushrooms... but don't know what to put instead, since I need to count all recipes with both, but only all recipes with both.
How can I properly write such a query for MySQL?
You were close.
Try something like
SELECT COUNT(Recipe.ID) Answer
FROM Recipe INNER JOIN
Ingredient olives ON olives.RecipeID=Recipe.ID INNER JOIN
Ingredient mushrooms ON mushrooms.RecipeID=Recipe.ID
WHERE olives.Name='Olives'
AND mushrooms.Name='Mushrooms'
AND olives.Amount = 1
AND mushrooms.Amount = 2
You can join to the same table twice, all you need to do is give the table an appropriate alias.
Use:
SELECT COUNT(r.id)
FROM RECIPE r
WHERE EXISTS(SELECT NULL
FROM INGREDIENTS i
WHERE i.recipeid = r.id
AND i.name = 'Olives'
AND i.amount = 1)
AND EXISTS(SELECT NULL
FROM INGREDIENTS i
WHERE i.recipeid = r.id
AND i.name = 'Mushrooms'
AND i.amount = 2)
SELECT COUNT(DISTINCT Recipe.ID) AS Answer
FROM Recipe, Ingredient as ing1, Ingredient as ing2
WHERE
Ing1.RecipeID=Recipe.ID AND Ing1.Name="Olives" AND ing1.Amount=1 AND
Ing2.RecipeID=Recipe.ID AND Ing2.Name="Mushrooms" AND ing2.Amount=2;
Hope this help
First of all, you use the old join syntax. INNER JOIN creates the same execution plan, but is much more clear. Then, your query is all about a plain, old condition. You just have to write them correctly! (It takes some practice, I agree.)
SELECT COUNT(DISTINCT R.ID) Answer
FROM Recipe R
INNER JOIN Ingredient I
ON R.ID = I.RecipeID
AND (R.Name = 'Olives' AND I.Amount = 1)
OR (R.Name = 'Mushrooms' AND I.Amount = 2);
Here is my sample data :
mysql> SELECT * FROM Ingredient;
+------+------+--------+----------+
| ID | Name | Amount | RecipeID |
+------+------+--------+----------+
| 1 | I1 | 1 | 1 |
| 1 | I2 | 2 | 1 |
| 1 | I3 | 2 | 1 |
| 1 | I4 | 3 | 1 |
| 1 | I1 | 1 | 2 |
| 1 | I2 | 1 | 2 |
| 1 | I3 | 3 | 2 |
| 1 | I4 | 2 | 2 |
| 1 | I1 | 2 | 3 |
| 1 | I2 | 1 | 3 |
+------+------+--------+----------+
10 rows in set (0.00 sec)
mysql> SELECT * FROM Recipe;
+------+-----------+
| ID | Name |
+------+-----------+
| 1 | Mushrooms |
| 2 | Olives |
| 3 | Tomatoes |
+------+-----------+
3 rows in set (0.00 sec)
And my query outputs 2, as it should.
Edit: Actually, I realised my query selected wrong rows. This works correctly :
SELECT COUNT(DISTINCT R.Id) Answer
FROM Recipe R
INNER JOIN Ingredient I
ON R.ID = I.RecipeID
WHERE
(R.Name = 'Mushrooms' AND I.Amount = 2)
OR (R.Name = 'Olives' AND I.Amount = 1);
Which outputs 2 as well.
Just for reference...
How many recipes call for "Olives" in the amount of "1" AND "Mushrooms" in the amount of "2"
Looking at your table structure above, you may want an associative entity to go between recipes and ingredients. This makes it a lot easier to do the kind of query you're looking for with an inner join.
In between your two tables, I would imagine something like this...
Recipe_Ingredient
-----------------
(PK, FK) RecipeID
(PK, FK) IngredientID
If you have that, then each recipe may have many ingredients, and each ingredient may be a part of many recipes. Once you have a table like that to properly associate your two tables, you can join them together and get a complete recipe. For a more complicated query like this where you have separate conditions for two recipes, I would probably sub-query it to help understanding.
SELECT SUM(RecipeCount) as RecipeCount
FROM
(
SELECT COUNT(r.*) as RecipeCount
FROM Recipe r
INNER JOIN Recipe_Ingredient ri on r.ID = ri.RecipeID
INNER JOIN Ingredient i on i.ID = ri.IngredientID
WHERE i.Name = 'Olives' AND i.Amount = 1
UNION ALL
SELECT COUNT(r.*) as RecipeCount
FROM Recipe r
INNER JOIN Recipe_Ingredient ri on r.ID = ri.RecipeID
INNER JOIN Ingredient i on i.ID = ri.IngredientID
WHERE i.Name = 'Mushrooms' AND i.Amount = 2
) as subTable

Oracle PL/SQL: referencing to a column name in inner joined query

I have the following SQL statement:
SELECT *
FROM cars car
LEFT JOIN (SELECT *
FROM cars auto
LEFT JOIN steeringwheels sw
ON auto.steeringwheelid = sw.ID
WHERE material = 'leather') innertable
ON innertable.ID = car.ID
LEFT JOIN steeringwheels sw
ON auto.steeringwheelid = sw.ID
WHERE sw.material='plastic'
This query delivers the columns from the table "Cars" twice, but with different values for the ID from the Car table (the purpose of the query is to map the values to see what the Car.ID would be id the material would change from leather to plastic).
------------------------------------
| ID | material | ID_1 | material_1 |
-------------------------------------
| 1 | leather | 4 | plastic |
| 2 | leather | 7 | plastic |
-------------------------------------
However, I would like to output only the ID columns (not the material columns), like this:
-------------
| ID | ID_1 |
-------------
| 1 | 4 |
| 2 | 7 |
-------------
I have not been able to do this, as I haven't found a way to refer to the ID column of the inner query in any way. For example
SELECT id, innertable.id
(...)
or
SELECT id, auto.id
(...)
or
SELECT id, id_1
(...)
don't seem to work. How can achieve this?
Try explicitly listing the column names of the inner table in the select statement. Like:
...(SELECT auto.ID autoid, auto.Whatever)....
and then in the main select:
SELECT innertable.autoid ....
Is this what you're after?
SELECT auto_id, steeringwheel_id
FROM cars car
LEFT JOIN (SELECT auto.ID AS auto_id, sw1.id AS steeringwheel_id
FROM cars auto
LEFT JOIN steeringwheels sw1
ON auto.steeringwheelid = sw1.ID
WHERE material = 'leather') innertable
ON innertable.auto_ID = car.ID
LEFT JOIN steeringwheels sw2
ON auto.steeringwheelid = sw2.ID
WHERE sw.material='plastic'