MySQL: displaying averages or null - mysql

I have two tables product and review, some products have reviews, others do not. I want to display the products and for products which have reviews I want to calculate their average rating, if a product has no reviews the average rating should be displayed as null.
Example:
Product:
| Id | Name |
| 1 | dog |
| 2 | cat |
Review:
| Id | Rating|
| 1 | 1 |
| 1 | 5 |
What should be displayed:
| Id | Name | Avg_rating |
| 1 | dog | 3 |
| 2 | cat | NULL |
How can this be achieved?

You can left join, aggregate and round():
select p.id, p.name, round(avg(r.rating)) avg_rating
from product p
left join review r on r.id = p.id
group by p.id, p.name

Related

How to Query for COUNT on a join table and return records with BOTH count=0 AND count>0

I have a Products table and a Reviews table.
I want to write a query to return the COUNT and AVG of the reviews of each product.
AND if there are no reviews I want it to return 0/null for COUNT and AVG.
product table
+--------+----------+
| prodId | prodName |
+--------+----------+
| 1 | apple |
| 2 | banana |
| 3 | cacao |
+--------+----------+
review table
+----------+----------+--------+
| reviewId | prodId | rating |
+----------+----------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 2 | 5 |
| 4 | 2 | 5 |
+----------+----------+--------+
this is what I want the result to look like:
+--------+----------+--------+-------+
| prodId | prodName | avg | count |
+--------+----------+--------+-------+
| 1 | apple | 1 | 2 |
| 2 | banana | 5 | 2 |
| 3 | cacao | null | 0 |
+--------+----------+--------+-------+
I am able to get the COUNT and AVG for the products with reviews
SELECT p.prodid, p.prodname, avg(r.stars), count(r.productid)
FROM products p
INNER JOIN reviews r ON p.productid=r.productid
GROUP BY p.productid
+--------+----------+--------+-------+
| prodId | prodName | avg | count |
+--------+----------+--------+-------+
| 1 | apple | 1 | 2 |
| 2 | banana | 5 | 2 |
+--------+----------+--------+-------+
I am also able to get the COUNT and AVG for a product with no reviews
SELECT p.prodid, p.prodname, avg(r.stars), count(r.stars)
FROM products p
LEFT OUTER JOIN reviews r
ON (p.productid=r.productid) WHERE r.productid IS NULL
GROUP BY p.productid
+--------+----------+--------+-------+
| prodId | prodName | avg | count |
+--------+----------+--------+-------+
| 3 | cacao | null | 0 |
+--------+----------+--------+-------+
But I don't know if MySql has a way to query and count results that match and count results that have no match in one query.
just remove the where condition from your 2nd query
SELECT p.prodid, p.prodname, avg(r.stars), count(r.stars)
FROM products p
LEFT OUTER JOIN reviews r
ON p.productid=r.productid
GROUP BY p.productid,p.prodname
In practice for MySQL "LEFT JOIN" is the same as LEFT OUTER JOIN. OUTER keyword is optional.
To achieve the result you just need to do simple "LEFT JOIN" and to group by ID (or any other unique key):
SELECT
`p`.`prodId` AS `ID`,
`p`.`prodName` AS `Product`,
avg(`r`.`rating`) AS `AVG Rating`,
count(`r`.`reviewId`) AS `Vote Count`
FROM `products` AS `p`
LEFT JOIN `reviews` AS `r` ON `p`.`prodId` = `r`.`prodId`
GROUP BY `p`.`prodId`

How to sum numbers through a relationship table?

I have a table of PRODUCTS
+------------+---------------+---------------+
| ProductCod | unitPrice | name |
+------------+---------------+---------------+
| 1 | 30 | some |
| 2 | 20 | poor |
| 3 | 10 | example |
+------------+---------------+---------------+
Another of SALES (which I believe it's not needed) and some other to register a n..m relationship
+------------+----------+------------+
| quantity | sellCode | productCod |
+------------+----------+------------+
| 3 | 1 | 1 |
| 5 | 1 | 2 |
| 4 | 2 | 2 |
| 4 | 2 | 3 |
| 4 | 2 | 3 |
+------------+----------+------------+
How can I select a list of products and how many were sold at all registers?
I would like something like:
+---------+------+
| name | sold |
+---------+------+
| some | 3 |
| poor | 9 |
| example | 8 |
+---------+------+
Using a INNER JOIN is more exact here.
Query
SELECT
products.name
, SUM(sales.quantity) AS sold
FROM
products
INNER JOIN
sales
USING(productCod)
GROUP BY
products.name
ORDER BY
products.ProductCod ASC
Or
SELECT
products.name
, SUM(sales.quantity) AS sold
FROM
products
INNER JOIN
sales
ON
products.productCod = sales.productCod
GROUP BY
products.name
ORDER BY
products.ProductCod ASC
Results
| name | sold |
|---------|------|
| some | 3 |
| poor | 9 |
| example | 8 |
see demo http://sqlfiddle.com/#!9/10cd3e/5
select p.Name, sum(s.Quantity) as sold
from Products p
left join Sales s on p.ProductCod = s.ProductCod
group by p.ProductCod, p.Name;
What we are doing is first to join two tables using productCod common field. We are using LEFT join, because there might be products that are not sold at all yet. Then we sum the quantities grouping by productCod (and Name. We had to include it in the list because it is not an aggregation expression - and there is a single Name per productCod). This works right, because there is a 1 to many relation between products and sales. If there were a many to many relation then the result would be wrong.
EDIT: Check this SQLFiddle sample for a good formatting.

Retrieving last record of a table inside inner join

I have these three tables:
products
+----+--------+
| id | QRCode |
+----+--------+
| 1 | 1000 |
| 2 | 1001 |
+----+--------+
prices
+----+---------+------------+
| id | price | product_id |
+----+---------+------------+
| 2 | $100001 | 1 |
| 3 | $100002 | 1 |
| 4 | $90001 | 2 |
| 5 | $90002 | 2 |
+----+---------+------------+
colors
+----+--------+-------------+
| id | color | product_id |
+----+--------+-------------+
| 1 | ffffff | 1 |
| 2 | f2f2f2 | 1 |
| 4 | aaaaaa | 2 |
| 5 | a3a3a3 | 2 |
+----+--------+-------------+
I would like to merge these three in a way that returns:
group_concat colors based on product_id
retrieves last record of each grouped price
This is the desired output:
+--------+----------------+-------------+-------------+
| QRCode | colors | price | product_id |
+--------+----------------+-------------+-------------+
| 1000 | ffffff, f2f2f2 | $100002 | 1 |
| 1001 | aaaaaa, a3a3a3 | $90002 | 2 |
+--------+----------------+-------------+-------------+
Things I tried:
The query below returns product_id of last record of each grouped price
SELECT product_id FROM price where id IN
(SELECT max(id) FROM price
GROUP BY product_id)
Then I tried to put query above in this query as a subquery
SELECT products.QRCode, priceSubQ.price, GROUP_CONCAT(colors.color) as colors FROM products
INNER JOIN colors on colors.product_id = products.id
INNER JOIN ( /* I put query above here */ ) as priceSubQ ON priceSubQ.product_id = products.id
GROUP BY products.id
What am I doing wrong?
I came across this link which helped me understand the problem
Changed inner query to:
SELECT product_id FROM ANY_VALUE(price) where id IN
(SELECT max(id) FROM price
GROUP BY product_id) group by product_id
solved my problem.
Something like the following should work (not tested)..
SELECT
products.QRCode,
priceSubQ.price,
GROUP_CONCAT(colors.color) as colors
FROM
products
LEFT JOIN colors
ON colors.product_id = products.id
LEFT JOIN (
SELECT
MAX(p1.id) as p1maxId,
p2.price AS price,
p2.product_id AS product_id
FROM
prices p1
INNER JOIN prices p2
ON p1.p1maxId = p2.id
GROUP BY
p1.product_id
) AS priceSubQ
ON priceSubQ.product_id = products.id
GROUP BY
products.id

Group by id, but order by name

I have a table like this (created from joining different tables)
+------------+--------------+-------------+
| product_id | product_name | category_id |
+------------+--------------+-------------+
| 1 | orange | 3 |
| 1 | orange | 4 |
| 2 | banana | 2 |
| 3 | apple | 2 |
| 3 | apple | 3 |
| 3 | apple | 4 |
+------------+--------------+-------------+
I'd like it grouped together by ID, but sorted by name.
Like this
+------------+--------------+-------------+
| product_id | product_name | category_id |
+------------+--------------+-------------+
| 3 | apple | 2 |
| 3 | apple | 3 |
| 3 | apple | 4 |
| 2 | banana | 2 |
| 1 | orange | 3 |
| 1 | orange | 4 |
+------------+--------------+-------------+
Is this possible? I tried using GROUP BY, but then it only keeps one row per product, so I can't see all the categories a product.
edit: My query so far
SELECT products.id as product_id, products.name as product_name, categories.id as category_id
FROM produkte
LEFT JOIN prod_cat ON products.id = prod_cat.product_id
JOIN categories ON prod_cat.category_id = categories.id
ORDER BY products.id
With "prod_cat" being the table that assigns categories to products.
This will order them by name then category id, giving the results as you have shown them.
SELECT products.id as product_id, products.name as product_name, categories.id as category_id
FROM produkte
LEFT JOIN prod_cat ON products.id = prod_cat.product_id
JOIN categories ON prod_cat.category_id = categories.id
ORDER BY product_name ASC, category_id ASC
I think you've misunderstood GROUP BY, but if you want to group them (reduce records to one where the field given in the GROUP BY clause matches), you can show the categories as a list using GROUP_CONCAT(categories.id) with GROUP BY product_id
Select * from products order by product_id desc, product_name asc;

Mysql nested left joins with counts

I have a set of MySQL three tables in a "has many" relationship: deals, orders, and coupons.
Deals
|----|--------------|
| id | title |
|----|--------------|
| 1 | Some deal |
| 2 | Another deal |
|----|--------------|
Orders
|----|---------|-----------|
| id | deal_id | state |
|----|---------|-----------|
| 1 | 1 | purchased |
| 2 | 1 | purchased |
| 3 | 1 | expired |
| 4 | 2 | purchased |
|----|---------|-----------|
Coupons
|----|----------|
| id | order_id |
|----|----------|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 2 |
| 6 | 4 |
|----|----------|
So, deals have many orders, which have many coupons.
What I'd like to do is select on the deals table while counting the number of purchased orders and coupons.
I know how to get a count on paid orders already:
SELECT deals.*, count(orders.id) AS orders_purchased_count FROM deals
LEFT JOIN orders ON deals.id=orders.deal_id AND orders.state='purchased'
WHERE deal_id < 3
GROUP BY deals.id
Deals
|----|--------------|------------------------|
| id | title | orders_purchased_count |
|----|--------------|------------------------|
| 1 | Some deal | 2 |
| 2 | Another deal | 1 |
|----|--------------|------------------------|
Similarly, I can get a count of coupons for orders:
SELECT orders.*, count(coupons.id) AS coupons_count FROM orders
LEFT JOIN coupons ON orders.id=couoons.orders_id
WHERE orders.state='purchased'
GROUP BY orders.id
Orders
|----|-----------|---------------|
| id | state | coupons_count |
|----|-----------|---------------|
| 1 | purchased | 3 |
| 2 | purchased | 2 |
| 4 | purchased | 1 |
|----|-----------|---------------|
My question is: How do I combine these so that I can add coupons_count next to orders_purchased_count?
Deals
|----|--------------|------------------------|---------------|
| id | title | orders_purchased_count | coupons_count |
|----|--------------|------------------------|---------------|
| 1 | Some deal | 2 | 5 |
| 2 | Another deal | 1 | 1 |
|----|--------------|------------------------|---------------|
The tricky thing, in my case, will be to run the WHERE deal_id < 3 filter when selecting from deals before I join on orders and to run the WHERE orders.state='purchased' filter when selecting from orders before I join on coupons. It's a large dataset and I don't want to load all my orders and coupons into memory for the purpose of joining.
At a loss for how to do this.
Does this work? I couldn't understand your concerns which part was going to be tricky.
SELECT deals.*, COUNT(DISTINCT(orders.id)) AS orders_purchased_count, COUNT(coupons.id) AS coupons_count
FROM deals
LEFT JOIN orders
ON deals.id=orders.deal_id
AND orders.state='purchased'
LEFT JOIN coupons
ON orders.id=coupons.orders_id
WHERE deal_id < 3
GROUP BY deals.id;
Try this one with a co related subquery and join
SELECT deals.*, count(orders.id) AS orders_purchased_count
(
SELECT count(id) FROM coupons WHERE orders_id = orders.id
) AS coupons_count
FROM deals
LEFT JOIN orders ON deals.id=orders.deal_id AND orders.state='purchased'
WHERE deal_id < 3
GROUP BY deals.id