Count frequency displaying also "0" values - mysql

I want to count and order the frequency of one main category (see SQLFiddle for table structures). But I want to display also "0" values, so if a categoryId isn't assigned by a product, this categoryId should have a frequency of "0".
My current SQL looks like this.
SELECT
category.categoryId,
category.name,
COUNT(*) AS frequency
FROM
Categories category
LEFT JOIN
Product entry ON entry.categoryId = category.categoryId
WHERE
category.parentId = 1
GROUP BY category.categoryId
ORDER BY COUNT(*) DESC
Result
| categoryId | name | frequency |
|------------|------------------|-----------|
| 2 | Sub Category 1-2 | 3 |
| 4 | Sub Category 1-4 | 1 |
| 3 | Sub Category 1-3 | 1 |
If I make a RIGHT JOIN the category, which hasn't been assigned, will not be displayed at all (but I need it in the result).
The result I need should look like this:
| categoryId | name | frequency |
|------------|------------------|-----------|
| 2 | Sub Category 1-2 | 3 |
| 4 | Sub Category 1-4 | 1 |
| 3 | Sub Category 1-3 | 0 |
Is there a way to display "0" frequency like in the result above?
SQLFiddle

You need to do count(entity.categoryId)
SELECT
c.categoryId,
c.name,
COUNT(e.categoryId) AS frequency
FROM
Categories c
LEFT JOIN
Product e ON e.categoryId = c.categoryId
WHERE
c.parentId = 1
GROUP BY c.categoryId
ORDER BY frequency DESC

I think tis is the query you need:
SELECT
category.categoryId,
category.name,
COUNT(category.parentId) AS frequency
FROM
Categories category
LEFT JOIN
Product entry ON entry.categoryId = category.categoryId
WHERE
category.parentId = 1 or category.parentId is null
GROUP BY category.categoryId
ORDER BY COUNT(*) DESC

Related

Match only if all elements of a column are in another table

I've made a little database in SQL that as 2 tables Product (Name, Ingredient and Available (Ingredient):
| Product | Available |
| Name | Ingredient | Ingredient |
| 1 | a | a |
| 1 | b | c |
| 2 | a |
| 2 | c |
I want the name of a product only if ALL its ingredients are inside the Available table.
For the previous example, the result should be: Product "2"
and not Product "1", because I don't have the ingredient "b" in the Available table.
Thanks for the help
You can try with left join (to figure out which Products don't have necessary Ingredients) and group by + having to filter Products that have at least one missing Ingredient:
select p.Name
from Products p
left join Available a on a.Ingredient = p.Ingredient
group by p.Name
having sum(a.Ingredient is null) = 0
You can try something like this also:
WITH TEMP_PRODUCTS AS
(
SELECT NAME, COUNT(1) AS NUMBER_OF_INGREDIENTS
FROM PRODUCT
GROUP BY PRODUCT
)
SELECT PRD.NAME, COUNT(1) AS NUMBER_OF_AVAILABLE_INGREDIENTS
FROM PRODUCT PRD
JOIN TEMP_PRODUCTS TMP ON PRD.NAME = TMP.NAME
WHERE EXISTS (SELECT 1
FROM INGREDIENT ING
WHERE ING.INGREDIENT = PRD.INGREDIENT)
GROUP BY PRD.NAME
HAVING COUNT(1) = TMP.NUMBER_OF_INGREDIENTS;

MySQL Join table row based on lowest cell value

I have two tables in a MySQL database like this:
PRODUCT:
product_id | product_name
-----------+-------------
1 | shirt
2 | pants
3 | socks
PRODUCT_SUPPLIER: (id is primary key)
id | supplier_id | product_id | part_no | cost
----+---------------+--------------+-----------+--------
1 | 1 | 1 | s1p1 | 5.00
2 | 1 | 2 | s1p2 | 15.00
3 | 1 | 3 | s1p3 | 25.00
4 | 2 | 1 | s2p1 | 50.00
5 | 2 | 2 | s2p2 | 10.00
6 | 2 | 3 | s2p3 | 5.00
My goal is a query that joins the tables and outputs a single row for each product joined with all fields from the corresponding supplier row with the lowest cost like this:
product_id | product_name | supplier_id | part_no | cost
-----------+---------------+---------------+------------+---------
1 | shirt | 1 | s1p1 | 5.00
2 | pants | 2 | s2p2 | 10.00
3 | socks | 2 | s3p3 | 5.00
At present I do have the following query written which seems to work but I'd like to know from any of the more experienced SQL users if there is a cleaner, more efficient or otherwise better solution? Or if there is anything essentially wrong with the code I have?
SELECT p.product_id, p.product_name, s. supplier_id, s.part_no, s.cost
FROM product p
LEFT JOIN product_supplier s ON
(s.id = (SELECT s2.id
FROM product_supplier s2
WHERE s2.product_id = p.product_id
ORDER BY s2.cost LIMIT 1));
I would run:
select p.product_id, p.product_name, s.supplier_id, s.part_no, s.cost
from product p
join product_supplier s
on p.product_id = s.product_id
join (select product_id, min(cost) as min_cost
from product_supplier
group by product_id) v
on s.product_id = v.product_id
and s.cost = v.min_cost
I don't see the point in an outer join. Is every product is on the product_supplier table? If not then the outer join makes sense (change the join to inline view aliased as v above to a left join if that is the case).
The above may run a little faster than your query because the subquery is not running for each row. Your current subquery is dependent and relative to each row of product.
If you want to eliminate ties and don't care about doing so arbitrarily you can add a random number to the end of the results, put the query into an inline view, and then select the lowest/highest/etc. random number for each group. Here is an example:
select product_id, product_name, supplier_id, part_no, cost, min(rnd)
from (select p.product_id,
p.product_name,
s.supplier_id,
s.part_no,
s.cost,
rand() as rnd
from product p
join product_supplier s
on p.product_id = s.product_id
join (select product_id, min(cost) as min_cost
from product_supplier
group by product_id) v
on s.product_id = v.product_id
and s.cost = v.min_cost) x
group by product_id, product_name, supplier_id, part_no, cost
If for some reason you don't want the random # to come back in output, you can put the whole query above into an inline view, and select all columns but the random # from it.

MySQL Filtering rows from three tables

Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2
This is what I've got so far:
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, the data at the begining is just a sample.
SQLFiddle
Any help will be really appreciated.
You could double join with a constraint on the category id:
SELECT a.* FROM book AS a
INNER JOIN book_categ AS b ON a.idBook = b.idBook AND b.idCateg = 1
INNER JOIN book_categ AS c ON a.idBook = c.idBook AND c.idCateg = 2
You could use a subquery:
SELECT a.* FROM book AS a
WHERE
(SELECT COUNT(DISTINCT idCateg) FROM book_categ AS b
WHERE b.idBook = a.idBook AND b.idCateg IN (1,2)) = 2
If you are on MySQL as your fiddle implies, you should prefer the join variant, since most joins are much faster in MySQL than subqueries.
edit
This one should also work:
SELECT a.* FROM book a
INNER JOIN book_categ AS b ON a.idBook = b.idCateg
WHERE b.idCateg IN (5, 6)
GROUP BY idBook
HAVING COUNT(DISTINCT b.idCateg) = 2
and should be faster than the two above, although you have to change the last number according to the number of category ids you are requesting.

Produce a list of people that have more than one item

Say I have a table of people...
person:
-----------
id | person
---+-------
1 | Jim
2 | Bob
3 | Frank
...and I have a table of items...
item:
----------------
id | item | type
---+------+-----
1 | 21 | 2
2 | 10 | 5
3 | 11 | 1
4 | 9 | 1
...and I also have a table describing who has what...
person_item:
-------------
item | person
-----+-------
1 | 2
2 | 1
3 | 1
How can I create a single query that will tell me when an individual has more than one item of a particular type? I only want the query to concern itself with items of type (1, 2, 3).
The results from the query should be in the following format:
---------------
person | item
| item
--------+------
person | item
| item
| item
--------+------
... etc.
This is what I have tried... but it produces garbage...
SELECT person.id, item.id FROM person_item AS pi
JOIN item AS i ON i.id = pi.item
JOIN person AS p ON p.id = pi.item
WHERE item.type IN (1,2,3)
HAVING COUNT(pi.person) > 1;
The query is suspect because you have a having clause but not a group by clause. Also, you are using table names when you have very reasonable aliases. And, you want to count distinct items within a person/type combination, not just for a person.
Taking these into account, try this query:
SELECT p.id, i.type, group_concat(i.item) as items
FROM person_item pi join
item i
ON i.id = pi.item join
person p
ON p.id = pi.person
WHERE i.type IN (1,2,3)
group by p.id, i.type
HAVING COUNT(distinct i.id) > 1;
This also provides the list of items as the third things returned.
If you only want to see person and item id's, you don't need to join to person - just access person_item (with a link to item for item_type). However, if you want each combination on a separate line, you will have to access person_item twice - like so:
select pi.person, pi.item
from person_item pi
join (select p.person
from person_item p
join item i on p.item = i.item_id and i.type in (1,2,3)
group by p.person
having count(*) > 1) c
on pi.person = c.person

Advanced count with join SQL

I have two tables, items & categories, sample data below:
Items:
Title category_id
Item A 1
Item B 2
Item C 3
Item D 2
Item E 3
Item F 2
Categories
category_id category
1 wood
2 plastic
3 metal
What I need to do is to count the total number of items, then list how many are in each category and what % that is of the total
I know I can count each item and total e.g.
select
count(*) as total,
sum(category_id=1) as cat_1,
sum(category_id=2
.... etc etc
But is there a way to do it all without counting each (there maybe new categories added and would like this to stay working) and then joining with categories table to produce the name?
Ideally this is what I'd like to return:
Category how many % of total
wood 1 17%
plastic 3 50%
metal 2 33%
Total 6 100%
(17% is 1/6th => 16.666666667% rounded).
select ifnull(c.category, 'TOTAL') as Category,
count(i.category_id) AS `how many`,
round(count(i.category_id) / (select count(*) from Items) * 100) as `% of total`
from Categories c
left outer join Items i on c.category_id = i.category_id
where c.category is not null
group by c.category
with rollup
Note, this will also correctly handle empty categories.
SQL Fiddle Example
Output:
| CATEGORY | HOW MANY | % OF TOTAL |
------------------------------------
| glass | 0 | 0 |
| metal | 2 | 33 |
| plastic | 3 | 50 |
| wood | 1 | 17 |
| TOTAL | 6 | 100 |
here this is initial start for you. this will give you first and second column. to get 3 column you will have do some calculation.
select c.Category, Count(Category_id)
from dbo.Items i
INNER JOIN dbo.Categories c
ON i.Category_Id = c.Category_ID
GROUP BY c.Category
This cross-join style of counting will account for categories that have zero items:
select c.Category
, (select count(*) from Items where category_id = c.category_id) as HowMany
, (select count(*) from Items where category_id = c.category_id)
/ (select count(*) from Items) * 100
as PctOfTotal
from Categories as c;