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;
Related
I've two tables invoices and products.
invoices: store,
products: id, invoice_id
I want to have a result set that shows how many invoices exists for each quantity of products.
I mean, if I have 2 invoices with 3 products each on store A, it will show Store: A, Products qty: 3, Number of invoices (with three products): 2
Another example:
| store | products_qty | count |
| A | 1 | 10 |
| A | 2 | 7 |
| A | 5 | 12 |
| B | 5 | 12 |
Meaning, store A has 10 invoices with 1 product. 7 with 2 products, and 12 with 5 products...
I've tried with something like:
SELECT store, count(p.id), count(i.id) FROM invoices i
LEFT JOIN products p ON (p.invoice_id = i.id)
GROUP BY price, count(i.id)
however my group cause is not valid, it shows Invalid use of group function.
How can I accomplish this?
I was able to do using subqueries, I wonder if is possible without it:
SELECT store, products_qty, count(*) FROM (
SELECT store, count(p.id) as products_qty, count(i.id) as invoices_count
FROM invoices i
LEFT JOIN products p ON (p.invoice_id = i.id)
GROUP BY price, i.id
) AS temp GROUP BY store, products_qty;
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
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
What I like to achieve is
a: display all Items that are in all of the selected category's
b: return / update the category list with category's available based on selection
I like items to be stored and be found by use of the adjacency list model or nested sets.
I've experimented with both and may use advice what would be the best for this case.
Currently I'm using (testing with) the adjacency list model like this:
items:
ID | item_name
====================
1 | car
2 | boat
3 | bike
items_cats: (many to many)
iid | cid
====================
1 | 1
1 | 2
1 | 4
1 | 7
2 | 1
2 | 3
2 | 4
2 | 7
3 | 1
3 | 3
3 | 4
3 | 8
categorys:
ID | cat_name | parent_id
========================
1 | safety: | 0 (0 = no parent)
2 | safe | 1
3 | dangerous | 1
4 | fun: | 0
5 | a bit | 5
6 | boring | 5
7 | funny | 5
8 | cool | 5
So its no problem to get items based on cid but how would you:
1st: selection:
1- Display all items who have cat id: cid 7 (funny)?
2- return (array/object) of all category's who have items that also contain cid 7?
Would you all do this in one query or would two be more efficient?
2nd: selection:
3- Display all items who have cat id: cid 7 and also contain cat id '3' (dangerous)
4- return (array/object) of all category's who have items that contain cid 7 and cid 3?
For selecting on multiple category's I found the flowing solution. Is this a good one and would there be to gain any performance especially when the number of category's grow?
SELECT
DISTINCT t1.product_id, t1.category_id
FROM
items_cats t1
INNER JOIN
items_cats t1b
ON t1.iid =t1b.iid
WHERE
t1.cid=3 AND
t1b.cid=7
To get a list of all items that have category ID = 7, start with your many:many table
select
i.item_name
from
items_cat ic
join items i
on ic.iid = i.id
where
ic.cid = 7
to get all categories associated with any item that has the category ID of 7, you can expand from the first and get categories associate for those item IDs
select DISTINCT
ic2.cid,
c.cat_name,
coalesce( CatParent.cat_name, "" ) as ParentCategoryName
from
( select distinct ic.iid
from items_cat ic
where ic.cid = 7 ) QualifiedItems
JOIN items_cat ic2
on QualifiedItems.iid = ic2.iid
JOIN categorys c
on ic2.cid = c.id
LEFT JOIN categorys CatParent
on c.parent_id = CatParent.ID
For 3 and 4, it would be similar, but to qualify BOTH (or anytime, more than one), you need to apply an OR, a GROUP BY and make sure that the final count matches those you were trying to qualify
select
i.item_name
from
items_cat ic
join items i
on ic.iid = i.id
where
ic.cid in( 3, 7 )
group by
i.item_name
having
count(*) = 2
So you can better understand and apply these principles, I'll leave the last one for you to try and implement... If you really get stuck, let me know... :)
Say I have two tables I want to join.
Categories:
id name
----------
1 Cars
2 Games
3 Pencils
And items:
id categoryid itemname
---------------------------
1 1 Ford
2 1 BMW
3 1 VW
4 2 Tetris
5 2 Pong
6 3 Foobar Pencil Factory
I want a query that returns the category and the first (and only the first) itemname:
category.id category.name item.id item.itemname
-------------------------------------------------
1 Cars 1 Ford
2 Games 4 Tetris
3 Pencils 6 Foobar Pencil Factory
And is there a way I could get random results like:
category.id category.name item.id item.itemname
-------------------------------------------------
1 Cars 3 VW
2 Games 5 Pong
3 Pencils 6 Foobar Pencil Factory
Thanks!
Just done a quick test. This seems to work:
mysql> select * from categories c, items i
-> where i.categoryid = c.id
-> group by c.id;
+------+---------+------+------------+----------------+
| id | name | id | categoryid | name |
+------+---------+------+------------+----------------+
| 1 | Cars | 1 | 1 | Ford |
| 2 | Games | 4 | 2 | Tetris |
| 3 | Pencils | 6 | 3 | Pencil Factory |
+------+---------+------+------------+----------------+
3 rows in set (0.00 sec)
I think this would fulfil your first question. Not sure about the second one - I think that needs an inner query with order by random() or something like that!
Mysql lets you to have columns not included in grouping or aggregate, in which case they've got random values:
select category.id, category.name, itemid, itemname
inner join
(select item.categoryid, item.id as itemid, item.name as itemname
from item group by categoryid)
on category.id = categoryid
Or, for minimums,
select category.id, category.name, itemid, itemname
inner join
(select item.categoryid, min(item.id) as itemid, item.name as itemname
from items
group by item.categoryid)
on category.id = categoryid
Mysql does let include non aggregate columns and there is no guarantee of determinism, but in my experience I nearly always get the first values.
So usually (but not guaranteed) this will give you the first
select *
from categories c, items i
where i.categoryid = c.id
group by c.id;
If you want guaranteed you will need to do something like
select categories.id, categories.name, items.id, items.name
from categories inner join
items on items.categoryid = categories.id and
items.id = (select min(items2.id) from items as items2 where items2.categoryid = category.id)
If you want random answers you will have to change the subquery a little bit
select categories.id, categories.name, items.id, items.name
from categories inner join
items on items.categoryid = categories.id and
items.id = (select items2.id from items as items2 where items2.categoryid = category.id order by rand() limit 1)