MySQL: AVG when there are no matching rows - mysql

I'm creating a website where users can rate items (say books), from 1 to 5 stars. I have created a MySQL database and a table which stores info about each rating (itemid, userid, ratingValue).
Each item has a dedicated webpage, and I'd like to put on this page info about: (1) number of ratings and (2) average rating. I thought that this was best done with a view. In thinking about MySQL queries to use in the view, I came to:
SELECT ItemID, COUNT(ratingValue), IFNULL(AVG(ratingValue),0) FROM reviews GROUP BY ItemID
Problem: if an item is not yet rated, it will not appear on the query. What I would like is that all items appear in the query result (view), and if there is an item with no reviews, it should appear with the information that there are 0 reviews and that the average rating is some arbitrary value, let's say 0.
From the official documentation,
AVG() returns NULL if there were no matching rows
and for that reason I tried to use the IFNULL() function. However, it doesn't work. For example, in a database with items 1,2,3,4,5, where item 5 has no reviews, I'll get:
ItemID COUNT(Rating) IFNULL(AVG(ratingValue),0)
1 4 4.0000
2 2 4.0000
3 2 3.5000
4 3 5.0000
Any ideas on how to overcome this problem?

You would have to have another table that lists all of your items (without regard to whether they have reviews) and left join that table to your reviews table. Without that, there is no way to infer from the reviews table alone which items may exist that have no reviews.
So something like:
SELECT
i.itemID AS itemID,
COUNT(r.rating) AS ratingCount,
AVG(ratingValue) AS averageRating
FROM items AS i
LEFT JOIN reviews AS r
ON i.itemID = r.itemID
GROUP BY i.itemID
This should give results like:
ItemID ratingCount averageRating
1 4 4.0000
2 2 4.0000
3 2 3.5000
4 3 5.0000
5 0 NULL

Related

Find the number of orders related to each order size in MySQL

Need some help with a MySQL query to be used in a larger database. Simplified here, I need to find the number of orders related to each order size.
I've been trying to get the query to work with a lot of combinations like: COUNT(DISTINCT item) or GROUP_CONCAT(DISTINCT order_id), GROUP BYs, ORDER BYs, HAVING COUNT(DISTINCT item_id), etc. but it's not turning out like I really need it to. Any help toward getting me going in the right direction would be greatly appreciated.
In this example table named items, the person with an order_id of 1 ordered three items, the person with an order_id of 4 ordered only one item, the person with an order_id of 5 ordered two items, etc. At the moment, they can only order up to three items max, but in the future, more items could be added so the query needs to be written in a way that can scale to 4 items, 5 items, etc.
Table name is: items
item_id order_id item
-------------------------------
1 1 apple
2 1 orange
3 1 grape
4 2 grape
5 3 apple
6 3 orange
7 4 apple
8 5 orange
9 5 apple
10 6 apple
11 6 orange
12 6 grape
13 7 orange
14 8 grape
In this example, the query result would be:
Number_of_Orders Total_Order_Size
----------------------------------------
4 1
2 2
2 3
You have to group by twice.
select item_count,count(*)
from (select order_id,count(*) as item_count
from tbl
group by order_id
) t
group by item_count
You can use two levels of aggregation:
select num_items, count(*) as num_orders
from (select order_id, count(*) as num_items
from t
group by order_id
) o
group by num_items
order by num_items;

Select sum of zero if no records in second table?

I did some research and learned about the COALESCE(sum(num), 0) function. The issue is the example I found only related to using one table.
I am calculating a sum from a second table, and if there are no records for an item in the second table, I still want it to show up in my query and have a sum of zero.
SELECT note.user, note.product, note.noteID, note.note, COALESCE(sum(noteTable.Score), 0) as points
FROM note, noteTable
WHERE note.user <> 3 AND note.noteID = noteTable.noteID
I am only recieving results if there is an entry in the second table noteTable. If there are scores added for a note, I still want them to show up in the result with a points value of zero.
Table Examples:
Note
user | product | noteID |note
3 1 1 Great
3 2 2 Awesome
4 1 3 Sweet
NoteTable
noteID | score
1 5
The query should show me this:
user | noteID | sum(points)
3 1 5
3 2 0
4 3 0
But I am only getting this:
user | noteID | sum(points)
3 1 5
http://sqlfiddle.com/#!9/aae812/2
SELECT
note.user,
note.product,
note.noteID, note.note,
COALESCE(sum(noteTable.Score),0) as points
FROM note
LEFT JOIN noteTable
ON note.noteID = noteTable.noteID
WHERE note.user <> 3
and I guess you should add:
GROUP BY note.noteid
if you expect to get SUM for every user. So you want to get more then 1 record back.
First, learn to use proper JOIN syntax and table aliases. The answer to your question is SUM() along with COALESCE():
SELECT n.user, n.product, n.noteID, n.note,
COALESCE(sum(nt.Score), 0) as points
FROM note n LEFT JOIN
noteTable nt
ON n.noteID = nt.noteID
WHERE n.user <> 3
GROUP BY n.user, n.product, n.noteID, n.note;
You also need a GROUP BY.

Count Category products with mysql

I have to count products inside each category like I have a category A that have two childs B and C.
In B category have 10 products and C category have 5 Products.
I want to write a query that count product like A = 15 B = 10 C = 5.
I have wrote a query that count only for child categories.
My query is given below
SELECT
c.`id`,
c.`parent_id`,
c.`name`,
c.`slug`,
c.`route_id`,
c.`description`,
c.`excerpt`,
c.`sequence`,
c.`image`,
c.`seo_title`,
c.`meta`,
c.`enabled`,
(SELECT
COUNT(`product_id`)
FROM
HM_category_products
WHERE
HM_category_products.category_id IN (SELECT
HM_categories.`id`
FROM
HM_categories
WHERE
(id = c.`id` OR id = c.`parent_id`))) AS count
FROM
`HM_categories` AS c
EDITED :- Below is my tables structure. This is just an example.
Category Table
id parent_id name slug enabled
1 0 Mobiles & Tablets mobiles-tablets 1
2 1 Mobile Phones mobile-phones 1
3 1 Tablets tablets 1
4 1 Mobile Accessories mobile-accessories 1
5 0 Electronics & Computers electronics-computers 1
6 5 Cameras & Accessories cameras-accessories 1
7 5 TV - Video - Audio tv-video-audio 1
Category Product Table
product_id category_id
1 2
2 2
3 2
4 2
5 3
6 2
7 3
8 3
9 3
10 2
11 3
12 2
13 2
14 2
15 2
You hav to use the "Group by" with something like:
SELECT
category_id, COUNT(*)
FROM
HM_category_products
GROUP BY category_id
This would get your table HM_category_products Grouped by category_id so will know how mane rows of each category its in the table, whats mean you will know the number of product of each category. Then you can join this resulting table with category one to get the category info. (Sorry for my english)
The GROUP BY statement is used in conjunction with the aggregate
functions to group the result-set by one or more columns. SQL GROUP BY Statement
for more info abaut "Group by" read
12.16.2 GROUP BY Modifiers and
12.16.1 GROUP BY (Aggregate) Functions
EDIT: I see now what you whant. I already give you the direction but here its a step furder: DISCLAIMER: THIS QUERY ITS JUST EXAMPLE, IT DOSNT WORK
CREATE VIEW view_2 AS
SELECT
category_id, COUNT(*) AS product_sum
FROM
HM_category_products
GROUP BY category_id
CREATE VIEW view_1 AS
SELECT
*
FROM
HM_categories
LEFT JOIN
view_2 AS a ON HM_categories.id = a.category_id;
SELECT
id,
name,
(SELECT
SUM(product_sum)
FROM
view_1
WHERE
parent_id = final.id) as product_count
FROM
HM_categories AS final;
that way you will get the product of a parent category, your only missing a CASE on the last select "product_count" to the child categorys, but that sum its easy because you already hav it on the view_1 (again sorry for my english)

SQL Count subcategories

I am trying to figure out how to use the count function in order to count how many subcategories each of my categories have.
My original query is as follows(where name = the category name):
select
catid,
name,
pcatid
from category
I simply want to add a column (which is not included in my DB anywhere) called 'SubCategoryCount'. This column will count for each catid how many times it is being referenced my the pcatid column, if that makes sense.
A simple example DB would be
catid name pcatid
1 Base NULL
2 Computers 1
3 Phones 1
4 Laptops 2
5 Dell 4
And i would like to produce
catid name pcatid SubCategoryCount
1 Base NULL 2
2 Computers 1 1
3 Phones 1 0
4 Laptops 2 1
5 Dell 4 0
But how would i achive this?
Thanks for your help
John
You need to join the table itself using LEFT JOIN so it will display all records in the table even if it does not have subcategory.
SELECT a.catid,
a.NAME,
a.pcatid,
COUNT(b.pcatid) SubCategoryCount
FROM category a
LEFT JOIN category b
ON a.catid = b.pcatid
GROUP BY a.catid, a.NAME, a.pcatid
SQLFiddle Demo
PS: This query is just a projection of result without changing the original schema of the table. it doesn't physically add new column on table category.

MySQL order by points from 2nd table

So I have MySQL 3 tables, items (which in this case are lodging properties and the data is simplified below), amenities that the properties might offer, and amenities_index which is a list of item ids and amenity ids for each amenity offered. The end user can select any number of amenities they want and I want to return the results in order of the number of amenities that match what they are looking for. So, if they search for 3 different amenities, I want the items listed that offer all 3, then those that offer 2, 1 and finally the rest of the items. I have a query that I think is working for getting the results in the correct order, but I was hoping that I could also return a point value based on the matches, and that's where I'm running into trouble. My SQL skills are a bit lacking when it comes to more complex queries.
Here is an example query I have that returns the results in the correct order:
SELECT * FROM items
ORDER BY
(
SELECT count(*) AS points
FROM `amenities_index`
WHERE
(amenity_id = 1 || amenity_id = 2)
AND amenities_index.item_id = items.id
) DESC
And here is what the tables are structured like. Any help is appreciated.
items table
id name
1 location 1
2 location 2
3 location 3
4 location 4
amenities table
id name
1 fireplace
2 television
3 handicapped accessible
4 kitchenette
5 phone
amenities_index
item_id amenity_id
1 2
1 3
1 5
2 1
2 2
2 6
3 2
3 3
3 4
3 5
You want to move your expression into the select clause:
SELECT i.*,
(SELECT count(*) AS points
FROM `amenities_index` ai
WHERE amenity_id in (1, 2) AND
ai.item_id = i.id
) as points
FROM items i
ORDER BY points desc;
You can also do this as a join query with aggregation:
SELECT i.*, ai.points
FROM items i join
(select ai.item_id, count(*) as points
from amenities_index ai
where amenity_id in (1, 2)
) ai
on ai.item_id = i.id
ORDER BY ai.points desc;
In most databases, I would prefer this version over the first one. However, MySQL would allow the first in a view but not the second, so it has some strange limitations under some circumstances.