SQL INNER JOIN using CASE and 3 tables - sql

I have the following tables and want show only the types that are available, while counting the products in that category.
Types
Type_Id
Name
1
Candy
2
Chocolate Bar
Products
Product_Id
Name
Type_Id
AvailabilityId
1
Chocolate Name 1
1
1
2
Chcoolate Name 1
2
2
3
Candy Name 1
2
2
Availability
Availability_Id
Name
1
Available
2
Reserved
3
Sol d
Desired Result
Type_Id
Name
TotalAvailable
1
Candy
1
2
Chocolate
2

Start with a basic JOIN between all three tables, on the related columns. Note use of table aliases t - Types, p - Products, a - Availability
SELECT *
FROM Types t
INNER JOIN Products p ON p.Type_Id = t.Type_Id
INNER JOIN Availability a ON a.Availability_Id = p.Availability_Id
Next add a WHERE clause to filter results by the availability status:
SELECT *
FROM Types t
INNER JOIN Products p ON p.Type_Id = t.Type_Id
INNER JOIN Availability a ON a.Availability_Id = p.Availability_Id
WHERE a.Name IN ('Available', 'Reserved')
Results:
Type_Id | Name | Product_Id | Name | Type_Id | Availability_Id | Availability_Id | Name
------: | :------------ | ---------: | :------------- | ------: | --------------: | --------------: | :--------
1 | Candy | 1 | Chocolate Name | 1 | 1 | 1 | Available
2 | Chocolate Bar | 2 | Chcoolate Name | 2 | 2 | 2 | Reserved
2 | Chocolate Bar | 3 | Candy Name | 2 | 2 | 2 | Reserved
Finally COUNT(*) the total rows matched, grouping by category (i.e. [Type].[Name])
SELECT t.Type_Id
, t.Name AS Type_Name
, COUNT(*) AS Total_Products
FROM Types t
INNER JOIN Products p ON p.Type_Id = t.Type_Id
INNER JOIN Availability a ON a.Availability_Id = p.Availability_Id
WHERE a.Name IN ('Available', 'Reserved')
GROUP BY t.Type_Id
, t.Name
Type_Id | Type_Name | Total_Products
------: | :------------ | -------------:
1 | Candy | 1
2 | Chocolate Bar | 2
SQL Fiddle

You can use an inner query with inner join to achieve this. Here's a SQL fiddle: http://sqlfiddle.com/#!9/42bd7f/2

Related

Sql join multiple tables, get count of certain rows, and also check some rows satisfy condition

I have a Zoo, each Zoo has many Cages, each Cage has many Animals.
Zoo:
+----+
| Id |
+----+
| 1 |
| 2 |
+----+
Cage:
+----+-------+
| Id | ZooId |
+----+-------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
| 4 | 2 |
| 5 | 2 |
+----+-------+
Animal:
+----+--------+----------+
| Id | CageId | IsHungry |
+----+--------+----------+
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 3 | 1 | 0 |
| 4 | 2 | 1 |
| 5 | 3 | 0 |
| 6 | 4 | 0 |
| 7 | 5 | 0 |
+----+--------+----------+
I'm trying to design a query to show each Zoo, the number of cages in that Zoo, and whether or not the Zoo has hungry Animals.
Here is the results I expect:
+-------+-----------+--------------+
| ZooID | CageCount | AnyoneHungry |
+-------+-----------+--------------+
| 1 | 2 | 1 |
| 2 | 3 | 0 |
+-------+-----------+--------------+
I can get the number of Cages in a Zoo:
SELECT
[c].[ZooId],
COUNT(*) AS [NumCages]
FROM [Cage] [c]
GROUP BY [c].[ZooId]
ORDER BY [NumCages] DESC
I can determine if a Cage has a hungry animal or not:
SELECT CASE WHEN EXISTS (
SELECT NULL
FROM [Animal] [a]
WHERE [a].[CageId] = #CageId AND [a].[IsHungry] = 1
) THEN 1 ELSE 0 END
But I'm having trouble combining these two into a single query that runs efficiently (in this universe zoos are very popular and have millions of cages and animals).
SELECT
[c].[ZooId],
COUNT(*) AS [CageCount],
MAX(CONVERT(INT, [x].[AnyoneHungry])) AS [AnyoneHungry]
FROM [Cage] [c]
INNER JOIN (
SELECT [a].[CageId], MAX(CONVERT(INT, [a].[IsHungry])) AS [AnyoneHungry]
FROM [Animal] [a]
GROUP BY [a].[CageId]
) [x] on [x].[CageId] = [c].[Id]
GROUP BY [c].[ZooId]
I feel like I'm missing something and it should be possible do run this query using a simpler statement.
This should do
SELECT
Z.Id,
COUNT(DISTINCT C.Id) AS CageCount,
COALESCE(MAX(CAST(A.IsHungry AS INT)), 0) AS AnyHungry /*The cast is only required if A.IsHungry is BIT and not INT*/
FROM Zoo Z
LEFT JOIN Cage C ON Z.Id = C.ZooId
LEFT JOIN Animal A ON C.Id = A.CageId
GROUP BY Z.Id
If you only need the zoo id and hungry animals:
SELECT c.zooid,
COUNT(DISTINCT C.Id) as CageCount,
COALESCE(MAX(CONVERT(int, a.IsHungry)), 0) AS AnyHungry
FROM Cage C LEFT JOIN
Animal A
ON c.Id = a.CageId AND a.IsHungry = 1
GROUP BY c.zooid;

Replace subqueries in where statement

I've built a query that intends to find products (products table) with both a 'used' offer and a 'new' offer, and get the lowest price for each. A product can have multiple offers (link_prices table). The offer's condition is determined by the name of the merchant (merchants table): a name without used and occasion is a 'new' offer, a name with used is a 'used' offer.
Here's a sample of the tables (PostgreSQL):
merchants
+----+---------------+
| id | name |
+----+---------------+
| 1 | amazon_used |
| 2 | ebay_location |
| 3 | amazon |
| 4 | target |
| 5 | target_used |
+----+---------------+
link_prices
+----+-------------+------------+-------+
| id | merchant_id | product_id | price |
+----+-------------+------------+-------+
| 1 | 1 | 1 | |
| 2 | 1 | 2 | 20 |
| 3 | 4 | 2 | 30 |
| 4 | 5 | 2 | 5 |
| 5 | 2 | 3 | 10 |
| 6 | 1 | 4 | 80 |
| 7 | 1 | 3 | 100 |
+----+-------------+------------+-------+
In this case, I'm expecting my query to return
+------------+----------------+---------------+
| product_id | min_used_price | min_new_price |
+------------+----------------+---------------+
| 2 | 5 | 30 |
+------------+----------------+---------------+
I've got the following query to work but I feel like I shouldn't need to use subqueries to achieve this. I just can't work my head around it. Any help would be appreciated to optimize this query.
SELECT products.id,
MIN(CASE WHEN merchants.name ILIKE '%used%' THEN link_prices.price END) as min_used_price,
MIN(CASE WHEN merchants.name NOT ILIKE '%used%' THEN link_prices.price END) as min_new_price
FROM products
INNER JOIN link_prices ON link_prices.product_id = products.id
INNER JOIN merchants ON merchants.id = link_prices.merchant_id
WHERE
products.id IN (
SELECT products.id
FROM products
INNER JOIN link_prices ON link_prices.product_id = products.id
INNER JOIN merchants ON merchants.id = link_prices.merchant_id
AND merchants.name ILIKE '%used%'
AND link_prices.price IS NOT NULL
AND link_prices.price <> 0
)
AND products.id IN (
SELECT products.id
FROM products
INNER JOIN link_prices ON link_prices.product_id = products.id
INNER JOIN merchants ON merchants.id = link_prices.merchant_id
AND merchants.name NOT ILIKE '%used%'
AND merchants.name NOT ILIKE '%location%'
AND link_prices.price IS NOT NULL
AND link_prices.price <> 0
)
GROUP BY products.id
Thanks a ton!
Your description makes this sound like conditional aggregation:
select lp.product_id,
min(lp.price) filter (where m.name like '%used') as min_used_price,
min(lp.price) filter (where m.name not like '%used') as min_new_price
from merchants m join
link_prices lp
on lp.merchant_id = m.id
group by lp.product_id;
You sample query is much more complicated and has conditions that are not mentioned in the text of the question. But I think this structure will work for what you want to do.

Count within the result set of a subquery

I have the following relations in my database:
Invoice InvoiceMeal
--------------------- ---------------------------
| InvoiceId | Total | | Id | InvoiceId | MealId |
--------------------- ---------------------------
| 1 | 22.32 | | 1 | 1 | 3 |
--------------------- ---------------------------
| 2 | 12.18 | | 2 | 1 | 2 |
--------------------- ---------------------------
| 3 | 27.76 | | 3 | 2 | 2 |
--------------------- ---------------------------
Meal Type
----------------------------------- -------------------
| Id | Name | TypeId | | Id | Name |
----------------------------------- -------------------
| 1 | Hamburger | 1 | | 1 | Meat |
----------------------------------- -------------------
| 2 | Soja Beans | 2 | | 2 | Vegetarian |
----------------------------------- -------------------
| 3 | Chicken | 2 |
-----------------------------------
What I want to query from the database is InvoiceId and Total of all Invoices which consist of at least two Meals where at least one of the Meals is of Type Vegetarian. I have the following SQL query and it works:
SELECT
i."Id", i."Total"
FROM
public."Invoice" i
WHERE
(SELECT COUNT(*)
FROM public."InvoiceMeal" im
WHERE im."InvoiceId" = i."Id" AND
(SELECT COUNT(*)
FROM public."Meal" m, public."Type" t
WHERE im."MealId" = m."Id" AND
m."TypeId" = t."Id" AND
g."Name" = 'Vegetarian') > 0
) >= 2;
My problem with this query is that I can not easily modify the condition that there must at least one vegetarien Meal. I want to be able, for example, to change it to at least two vegetarian meals. How can I achieve this with my query?
I would approach this by joining the tables together and using aggregation. The having clause can handle the conditions:
select i.Id, i.Total
from InvoiceMeal im join
Invoice i
on i.InvoiceId = im.InvoiceId join
Meal m
on im.mealid = m.mealid join
Type t
on m.typeid = t.typeid
group by i.Id, i.Total
having count(distinct im.mealid) >= 2 and
sum(case when t.name = 'Vegetarian' then 1 else 0 end) > 0;
I also see no reason to put double quotes around column names. That just makes the query harder to write and read.

Oracle ordering by several same meaning columns

I have to make sortable table like this:
Sortable table:
building_id | building_age | title |
-------------------------------------------------
1 | 100 | New york buil |
2 | 50 | House 1 |
3 | 50 | House 10 |
From these tables:
Building Table:
building_id | building_age | building_type_1_FK | building_type_2_FK
---------------------------------------------------------
1 | 100 | null | 1
2 | 50 | 1 | null
3 | 50 | 2 | null
building_type_1:
type_id | title | diff1 |
-------------------------------------------------
1 | New york buil| blablabla |
building_type_2:
building_id | title |
----------------------------
1 | House 1 |
2 | House 10 |
3 | House 500 |
While joining these tables I get several title columns where one of them is not null. Is there any way to sort by title and select top 10 results without fetching all the data and then sorting in the app?
p.s.. I know that in general this architecture is not good, but I can't change it.
Yes. You want to do a left outer join to the two tables, and then bring the results together:
select b.building_id, b.building_age, coalesce(bt1.title, bt2.title) as title
from building b left outer join
building_type_1 bt1
on b.building_type_1_FK = bt1.type_id left outer join
building_type_2 bt2
on b.building_type_2_FK = bt2.building_id;
To get the top 10 results in Oracle:
select *
from (select b.building_id, b.building_age, coalesce(bt1.title, bt2.title) as title
from building b left outer join
building_type_1 bt1
on b.building_type_1_FK = bt1.type_id left outer join
building_type_2 bt2
on b.building_type_2_FK = bt2.building_id
order by title
) b
where rownum <= 10;

Inner join with multiple tables

I have these four tables:
PRODUCTS
---------
PRODUCT_ID
PRODUCT_TITLE
(other fields)
COLORS
---------
COLOR_ID
COLOR_NAME
MATERIALS
---------
MATERIAL_ID
MATERIAL_NAME
IMAGES
---------
IMAGE_ID
BIG
MED
SMALL
THUMB
SIZE
---------
SIZE_ID
SIZE_NAME
And also:
PRODUCT_COLOR
---------
PRODUCT_ID
COLOR_ID
PRODUCT_MATERIAL
---------
PRODUCT_ID
MATERIAL_ID
PRODUCT_SIZE
---------
PRODUCT_ID
SIZE_ID
PRODUCT_IMAGE
---------
PRODUCT_ID
IMAGE_ID
COLOR_ID (can be null)
MATERIAL_ID (can be null)
All the products can have a different color and/or material. E.g. I can have a product that has one or more material options but no colors associated and vice versa. The output should be something like this:
-----------------------------------------------------------------------------
| PRODUCT_ID | PRODUCT_NAME | COLOR_ID | MATERIAL_ID | IMAGE_ID | SIZE_ID |
-----------------------------------------------------------------------------
| 1 | T-SHIRT | 1 | null | 1 | 1 |
| 1 | T-SHIRT | 1 | null | 1 | 2 |
| 1 | T-SHIRT | 1 | null | 1 | 3 |
| 1 | T-SHIRT | 1 | null | 1 | 4 |
| 2 | JEANS | null | 1 | 2 | 1 |
| 2 | JEANS | null | 1 | 2 | 2 |
| 2 | JEANS | null | 1 | 2 | 3 |
| 2 | JEANS | null | 1 | 2 | 4 |
| 2 | JEANS | null | 1 | 2 | 5 |
| 3 | T-SHIRT VNECK | 2 | 2 | 3 | 1 |
| 3 | T-SHIRT VNECK | 2 | 2 | 3 | 2 |
| 3 | T-SHIRT VNECK | 3 | 2 | 4 | 1 |
| 3 | T-SHIRT VNECK | 3 | 2 | 4 | 2 |
| 3 | T-SHIRT VNECK | 4 | 3 | 5 | 1 |
| 3 | T-SHIRT VNECK | 4 | 3 | 5 | 2 |
-----------------------------------------------------------------------------
I have tried the following statement but it returns 0 rows:
SELECT PRODUCTS.PRODUCT_ID, PRODUCTS.PRODUCT_TITLE, COLORS.COLOR_ID, MATERIALS.MATERIAL_ID, IMAGES.IMAGE_ID, SIZE.SIZE_ID from PRODUCTS
INNER JOIN PRODUCT_COLOR ON (PRODUCTS.PRODUCT_ID = PRODUCT_COLOR.PRODUCT_ID)
INNER JOIN COLORS ON (COLORS.COLOR_ID = PRODUCT_COLOR.COLOR_ID)
INNER JOIN PRODUCT_MATERIAL ON (PRODUCTS.PRODUCT_ID = PRODUCT_MATERIAL.PRODUCT_ID)
INNER JOIN MATERIALS ON (MATERIALS.MATERIAL_ID = PRODUCT_MATERIAL.MATERIAL_ID)
INNER JOIN PRODUCT_IMAGE ON (PRODUCTS.PRODUCT_ID = PRODUCT_IMAGE.PRODUCT_ID)
INNER JOIN IMAGES ON (IMAGES.IMAGE_ID = PRODUCT_IMAGE.IMAGE_ID)
INNER JOIN PRODUCT_SIZE ON (PRODUCTS.PRODUCT_ID = PRODUCT_SIZE.PRODUCT_ID)
INNER JOIN SIZE ON (SIZE.SIZE_ID = PRODUCT_SIZE.SIZE_ID)
ORDER BY PRODUCTS.id_PRODUCT;
Any ideas?
You could do something like this:
select p.product_id,
p.product_name,
c.color_id,
m.material_id,
i.image_id,
s.size_id
from products p
left join product_color pc
on p.product_id = pc.product_id
left join colors c
on pc.color_id = c.colorid
left join product_material pm
on p.product_id = pm.product_id
left join materials m
on pm.material_id = m.material_id
left join product_image pi
on p.product_id = pi.product_id
left join images i
on pi.image_id = i.image_id
or c.color_id = i.color_id
or m.material_id = i.material_id
left join product_size ps
on p.product_id = ps.product_id
left join size s
on ps.size_id = s.size_id
I would advise you reviewing JOINs. There is a great visual explanation of joins online that will help you write these queries.
Well, you need to learn how to build joins, and the way I normally do it is by selecting one table and join the next one and the next one and the next one until I have the result I want.
select product_id, product_name
from products
next I join the first one I need so I go ahead and say
select p.product_id, p.product_name, pc.color_id
from products p
join product_color pc on (pc.product_id = p.product_id)
On the join it is important to figure out if I maybe have nothing to join with and I still want to see the line. So I rather use a left join
select p.product_id, p.product_name, pc.color_id
from products p
left join product_color pc on (pc.product_id = p.product_id)
That way you add each table to join. By the way. Is this homework?
If you just need IDs, keep it simple
select p.product_id,
p.product_name,
pc.color_id,
pm.material_id,
pi.image_id,
ps.size_id
from products p,
PRODUCT_COLOR pc,
product_material pm,
PRODUCT_SIZE ps,
PRODUCT_IMAGE pi
where
p.product_id = pc.product_id(+)
and p.product_id = pm.product_id(+)
and p.product_id = ps.product_id(+)
and p.product_id = pi.product_id(+);