MySQL Query: LIMITing a JOIN - mysql

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)

Related

MySQL - Can't use GROUP_CONCAT and filter results

Hi have the following database schema:
items
- id
- item_name
items_cats
- item_id
- cat_id
categories
- id
- cat_name
From now, I can select the items and categories in a single query like that:
SELECT i.id, i.item_name, GROUP_CONCAT(c.cat_name) as cats
FROM items AS i, items_cats AS ic, categories AS c
WHERE ic.item_id = i.id AND ic.cat_id = c.id
GROUP BY i.id
This result something like this:
id| item_name | cats
1 | Item 1 | Cat 1, Cat 2
2 | Item 2 | Cat 1
3 | Item 3 | Cat 3, Cat 5
4 | Item 4 | Cat 2, Cat 3, Cat 4
Now I need the same result, but I want only the records containing "Cat 3". If I add "c.id = 3" in WHERE clause, results in this:
id| item_name | cats
3 | Item 3 | Cat 3
4 | Item 4 | Cat 3
But I want the other categories from the item, like this:
id| item_name | cats
3 | Item 3 | Cat 3, Cat 5
4 | Item 4 | Cat 2, Cat 3, Cat 4
How can I do that?
I have found a solution. Just added the following in the end of query:
HAVING cats LIKE CONCAT('%', (SELECT cat_name FROM categories WHERE id = 3), '%')
First, learn to use proper JOIN syntax. Simple rule: Never use commas in the FROM clause; always use proper explicit JOIN syntax with the join conditions in the ON clause.
Second, you can do this using a simple predicate in the HAVING clause:
SELECT i.id, i.item_name, GROUP_CONCAT(c.cat_name SEPARATOR ', ') as cats
FROM items i JOIN
items_cats ic
ON ic.item_id = i.id JOIN
categories c
ON ic.cat_id = c.id
GROUP BY i.id
HAVING MAX(c.cat_name = 'Cat 3') > 0;
Your separator appears to have a space in it.
If you want to do this by id, then use:
HAVING MAX(c.id = 3) > 0;

Count frequency displaying also "0" values

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

MySQL: Refine answer to Finding most frequently occuring values in table

Was posted yesterday at:
MySQL: Finding most frequently occuring values in table
But was wondering if there is a way to refine the answer, as you shouldn't have to repeat the code at the end to get the MAX(COUNT)
Question
I have two tables:
Purchases:
item
001
003
002
001
002
004
003
001
002
Item:
id | name
001 | Item 1
002 | Item 2
003 | Item 3
004 | Item 4
Expected output:
item name
001 Item 1
002 Item 2
I need to find the (multiple) items that occur most frequently in the purchases table, and output the name of the item. What should I have in my query?
Answer I have so far
SELECT t.cnt, t.name FROM
(SELECT COUNT(*) AS "cnt", item.name
FROM purchases
LEFT JOIN item ON item.id = purchases.item
GROUP BY item.name) t
WHERE t.cnt = (SELECT MAX(t2.cnt) FROM
(SELECT COUNT(*) AS "cnt"
FROM purchases
LEFT JOIN item ON item.id = purchases.item
GROUP BY item.name) t2
)
You must first obtain the maximal count, then use that to filter for matching items:
SELECT i.*
FROM purchases p
JOIN item i ON i.id = p.item
GROUP BY i.id
HAVING COUNT(*) = (
SELECT COUNT(*)
FROM purchases
GROUP BY item
ORDER BY COUNT(*) DESC
LIMIT 1
)
Note that this query relies on id being the PK of (or at very least unique within) your item table, for two reasons:
It ensures that the JOIN does not adversely affect the COUNT(*) in the outer query; and
It enables you to determinately select * from item despite the grouping operation (other RDBMS platforms wouldn't allow this, but would require you to group on every non-aggregated output column—but MySQL offers this "feature" as a performance improvement).
I believe something like this could work. You can using 'having' and 'order by' to get top results.
SELECT count(p.item), i.name
FROM purchases p, item i
WHERE p.item = i.id
GROUP BY p.item HAVING count(p.item) > 1
ORDER BY count(p.item) DESC;
Optionally you could add 'LIMIT 1' to the end.
Try this:
SELECT I.`id` AS id, COUNT(I.`id`) AS `id_count`, P.`name` AS name
FROM `item` I
JOIN `purchases` P
ON I.`id` = P.`id`
ORDER BY COUNT(I.`id`) DESC
GROUP BY I.`id`
Expected Output:
|-------|-------------|--------------------|
| id | id_count | name |
|-------|-------------|-------------------|
| 101 | 5 | Item 1 |
| 102 | 6 | Item 2 |
| 103 | 8 | Item 3 |
| 104 | 12 | Item 4 |
...
|------------------------------------------|

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;