Select based on multiple ID's in the same column - sql

I have two SQL tables already built (not by me) :
One table that has all the ingredients with ID's assigned to them :
IngredientID | IngredientName
1 | Milk
2 | Eggs
3 | Flower
and the second table that has finished products that have reference to ingredientID in the ProductIngredients column but no the product name and they are built like this :
ProductID | ProductName | ProductIngredients
1 | Tomato Soup | NULL
2 | Pancakes | ;1;2;3
3 | Omlette | ;1;2
If they don't have any reference then its written NULL, and if they have, then they start with a ";" symbol and every ingredient is divided by a ";" symbol.
My goal is to join those two tables and make a select statement that returns me, instead of the ID's of the other column, the actual names of the used ingredients like this :
ProductID | ProductName | ProductIngredients
1 | Tomato Soup |
2 | Pancakes | Milk, Eggs, Flower
3 | Omlette | Milk, Eggs
Can anyone help out with this?

You need a left join of Products to ProductIngredients and group by product:
select p.ProductID, p.ProductName,
group_concat(i.IngredientName order by i.IngredientID) ProductIngredients
from Products p left join Ingredients i
on concat(p.ProductIngredients, ';') like concat('%;', i.IngredientID,';%')
group by p.ProductID, p.ProductName
The function group_concat() works in MySql but you can find similar functions to other databases.
See the demo.
Results:
| ProductID | ProductName | ProductIngredients |
| --------- | ----------- | ------------------ |
| 1 | Tomato Soup | |
| 2 | Pancakes | Milk,Eggs,Flower |
| 3 | Omlette | Milk,Eggs |

Related

SQL proble with join condition

I have a data base like here
I need to create a request to list the number of product available by product category.
However i should not list the product category without products.
We want at the end only column CATEGORY_NAME (product_category.name) and PRODUCT_COUNT
Example of result :
---------------------------------
| CATEGORY_NAME | PRODUCT_COUNT |
---------------------------------
| Books | 3 |
| Automotive | 2 |
| High-tech | 8 |
--------------------------------
I try this request but it's not good:
SELECT name AS CATEGORY_NAME, COUNT(available_stock) AS PRODUCT_COUNT
FROM product
GROUP BY name
WHERE available_stock IS NOT NULL

How can I query this as I want?

I have three table like this:
Product
| ID | Name |
+----------------+
| 1 | Product A |
| 2 | Product B |
ProductDescription
| ID | Description |
+------------------------------------+
| 1 | Product A English Description |
| 2 | Product A Spanish Description |
| 3 | Product B English Description |
| 4 | Product B Spanish Description |
And finally a table to connect 2 above table
ProductProductDescription
| ProductID | ProductDescriptionID |
+----------------------------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 4 |
When I joined 3 above table normally, I got this
| ProductID | ProductDescripton |
+-------------------------------------------+
| 1 | Product A English Description |
| 1 | Product A Spanish Description |
| 2 | Product B English Description |
| 2 | Product B Spanish Description |
But I want to query into this:
| ProductID | English Description | Spanish Description |
+---------------------------------------------------------------------------+
| 1 | Product A English Description| Product A Spanish Description |
| 2 | Product B English Description| Product B Spanish Description |
Can you help me?
I think you want conditional aggregation:
select ppd.id,
max(case when pd.description like '%English%' then pd.description end) english_description,
max(case when pd.description like '%Spanish%' then pd.description end) spanish_description
from ProductProductDescription ppd
inner join ProductDescription pd on pd.id = ppd.productdescriptionid
group by ppd.id
Notes:
design-wise, it would be far better to have another column in product description table that represents the language of the description, rather than relying on the description itself
you don't need the product table to generate the expected result; but if you do, then just add another join to the query
One option would be using PIVOT Clause (if available for your DBMS) as
SELECT *
FROM
(
SELECT ppd.ProductID, pd.Description, LTRIM(REPLACE(pd.Description,p.Name)) AS Title
FROM ProductDescription pd
JOIN ProductProductDescription ppd
ON ppd.ProductDescriptionID = pd.ID
JOIN Product p
ON ppd.ProductID = p.ID
)
PIVOT
(
MAX(Description) FOR Title IN ('English Description' AS " English Description",
'Spanish Description' AS " Spanish Description")
)
Demo

Conditionally return values from LEFT JOIN between 3 tables based on CASE

First off, apologies for a long post. It's really more simple than it looks ;-)
I'm trying to do something that I think is conceptually simple, and I believe I'm most of the way there, but there's one last part that I can't implement without errors that I can't figure out how to fix.
I have three related tables.
Orders:
Each row is an Order with a unique ID, there will never be duplicates.
+---------+---------+
| OrderID | Name |
+---------+---------+
| 1 | Order 1 |
| 2 | Order 2 |
| 3 | Order 3 |
+---------+---------+
Order Details:
Relational table where each row is a product line on an order.
+---------+-----------+
| OrderID | ProductID |
+---------+-----------+
| 1 | a |
| 2 | b |
| 2 | c |
| 3 | a |
| 3 | b |
| 3 | b |
+---------+-----------+
As you can see some orders have just one product (1), some will have multiple products (2) and some will have duplicate products (3).
Products
Each row is a product with a unique ID, there will never be duplicates.
+-----------+-------------+
| ProductID | Description |
+-----------+-------------+
| a | Chicken |
| b | Fish |
| c | Beef |
+-----------+-------------+
I want to return all rows from the Orders table and conditionally return some information about the related Products in one column.
The condition is that I look at how many DISTINCT products each Order has. If it's just 1 then I want to return the Product Description value. If it's more than 1 then I want to return some placeholder text such as 'Multi'.
I think that I need to use CASE to get this working, but I can't figure it out.
I can count the unique products successfully like this:
SELECT
o.Name
,COUNT(DISTINCT d.ProductId) as 'Unique Products'
FROM Orders o
LEFT JOIN OrderDetails d ON o.OrderID = d.OrderID
LEFT JOIN Products p on d.ProductId = p.ProductId
GROUP BY o.Name
ORDER BY o.Name DESC
GO
Results are like this:
+---------+-----------------+
| Name | Unique Products |
+---------+-----------------+
| Order 1 | 1 |
| Order 2 | 2 |
| Order 3 | 2 |
+---------+-----------------+
What I want is this:
+---------+-----------------+
| Name | Unique Products |
+---------+-----------------+
| Order 1 | Chicken |
| Order 2 | Multi |
| Order 3 | Multi |
+---------+-----------------+
I have been trying to use CASE which I believe I've gotten correct:
CASE WHEN (COUNT(DISTINCT d.ProductId)) > 1 THEN 'Multi' ELSE p.Description END AS 'Products'
However unless I add p.Description to GROUP BY then I get the error (which I understand):
Column 'Product.Description' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
But if I do add it the results aren't what I want, for example:
+---------+----------+
| Name | Products |
+---------+----------+
| Order 1 | Chicken |
| Order 2 | Fish |
| Order 2 | Beef |
| Order 3 | Chicken |
| Order 3 | Fish |
| Order 3 | Fish |
+---------+----------+
When it should just say "Order 2 - Multi" on one row for example. This is the bit I don't understand.
If can get some help on this bit alone it would solve my problem and I'd accept the answer. However...
Bonus Round
The above is fine and all, but if this bit is possible I'd accept this as an answer above the others.
Can I concatenate the product names? I've been looking at COALESCE and FOR XML PATH but I can't wrap my head around them at all so I don't even have any code to show.
Results would look something like this:
+---------+--------------+
| Name | Products |
+---------+--------------+
| Order 1 | Chicken |
| Order 2 | Fish;Beef |
| Order 3 | Chicken;Fish |
+---------+--------------+
If you've made it this far I commend you! Thanks!
You are pretty close. You just need some case logic and an aggregation function around the description:
SELECT o.Name,
(CASE WHEN COUNT(DISTINCT d.ProductId) = 1
THEN MAX(p.description)
ELSE 'Multi'
END) as Descriptions
FROM Orders o LEFT JOIN
OrderDetails d
ON o.OrderID = d.OrderID LEFT JOIN
Products p
ON d.ProductId = p.ProductId
GROUP BY o.Name
ORDER BY o.Name DESC
The second part is a very different question. In SQL Server, you need to use an XML subquery:
select o.Name,
stuff((select distinct ',' + p.description
from OrderDetails d left join
Products p
on d.ProductId = p.ProductId
where o.OrderID = d.OrderID
for xml path (''), type
).value('.', 'nvarchar(max)'
), 1, 1, ''
) as descriptions
from Orders o
order by o.Name desc

Find supplier which supplies a product that others don't

I'm trying to write an SQL query which selects the supplier based on the fact that it can supply a product other suppliers cannot.
I have 2 columns:
Supplier and Product
How would I select all the suppliers which supply at least 1 product which other suppliers do not supply?
I currently have:
SELECT incart.product, incart.supplier
FROM incart
WHERE incart.product
HAVING count(incart.supplier)=1
;
Try this:
SELECT
i1.supplier
FROM incart i1
WHERE i1.product NOT IN(SELECT product
FROM incart i2
WHERE i1.supplier <> i2.supplier);
For example, for the following sample data:
| PRODUCT | SUPPLIER |
|---------|----------|
| 1 | a |
| 2 | b |
| 3 | b |
| 2 | c |
| 3 | c |
| 4 | c |
It will select suppliers a and c, because supplier a supplies product 1 which others don't, and supplier c supplies product 4 which others don't.
SQL Fiddle Demo

Get SUM in GROUP BY with JOIN using MySQL

I have two tables in MySQL 5.1.38.
products
+----+------------+-------+------------+
| id | name | price | department |
+----+------------+-------+------------+
| 1 | Fire Truck | 15.00 | Toys |
| 2 | Bike | 75.00 | Toys |
| 3 | T-Shirt | 18.00 | Clothes |
| 4 | Skirt | 18.00 | Clothes |
| 5 | Pants | 22.00 | Clothes |
+----+------------+-------+------------+
ratings
+------------+--------+
| product_id | rating |
+------------+--------+
| 1 | 5 |
| 2 | 5 |
| 2 | 3 |
| 2 | 5 |
| 3 | 5 |
| 4 | 5 |
| 5 | 4 |
+------------+--------+
My goal is to get the total price of all products which have a 5 star rating in each department. Something like this.
+------------+-------------+
| department | total_price |
+------------+-------------+
| Clothes | 36.00 | /* T-Shirt and Skirt */
| Toys | 90.00 | /* Fire Truck and Bike */
+------------+-------------+
I would like to do this without a subquery if I can. At first I tried a join with a sum().
select department, sum(price) from products
join ratings on product_id=products.id
where rating=5 group by department;
+------------+------------+
| department | sum(price) |
+------------+------------+
| Clothes | 36.00 |
| Toys | 165.00 |
+------------+------------+
As you can see the price for the Toys department is incorrect because there are two 5 star ratings for the Bike and therefore counting that price twice due to the join.
I then tried adding distinct to the sum.
select department, sum(distinct price) from products
join ratings on product_id=products.id where rating=5
group by department;
+------------+---------------------+
| department | sum(distinct price) |
+------------+---------------------+
| Clothes | 18.00 |
| Toys | 90.00 |
+------------+---------------------+
But then the clothes department is off because two products share the same price.
Currently my work-around involves taking something unique about the product (the id) and using that to make the price unique.
select department, sum(distinct price + id * 100000) - sum(id * 100000) as total_price
from products join ratings on product_id=products.id
where rating=5 group by department;
+------------+-------------+
| department | total_price |
+------------+-------------+
| Clothes | 36.00 |
| Toys | 90.00 |
+------------+-------------+
But this feels like such a silly hack. Is there a better way to do this without a subquery? Thanks!
Use:
SELECT p.department,
SUM(p.price) AS total_price
FROM PRODUCTS p
JOIN (SELECT DISTINCT
r.product_id,
r.rating
FROM RATINGS r) x ON x.product_id = p.id
AND x.rating = 5
GROUP BY p.department
Technically, this does not use a subquery - it uses a derived table/inline view.
The primary reason you are having trouble finding a solution is that the schema as presented is fundamentally flawed. You shouldn't allow a table to have two rows that are complete duplicates of each other. Every table should have a means to uniquely identify each row even if it is the combination of all columns. Now, if we change the ratings table so that it has an AUTO_INCREMENT column called Id, the problem is easier:
Select products.department, Sum(price) As total_price
From products
Left Join ratings As R1
On R1.product_id = products.id
And R1.rating = 5
Left Join ratings As R2
On R2.product_id = R1.product_id
And R2.rating = R1.rating
And R2.Id > R1.Id
Where R2.Id Is Null
Group By products.department
You can do two queries. First query:
SELECT DISTINCT product_id FROM ratings WHERE rating = 5;
Then, take each of those ID's and manually put them in the second query:
SELECT department, Sum(price) AS total_price
FROM products
WHERE product_id In (1,2,3,4)
GROUP BY department;
This is the work-around for not being able to use subqueries. Without them, there is no way to eliminate the duplicate records caused by the join.
I can't think of any way to do it without a subquery somewhere in the query. You could perhaps use a View to mask the use of a subquery.
Barring that, your best bet is probably to find the minimum data set needed to make the calculation and do that in the front end. Whether or not that's possible depends on your specific data - how many rows, etc.
The other option (actually, maybe this is the best one...) would be to get a new ORM or do without it altogether ;)
This view would allow you to bypass the subquery:
CREATE VIEW Distinct_Product_Ratings
AS
SELECT DISTINCT
product_id,
rating
FROM
Ratings