MySQL - Selecting single entry per category - mysql

My apologies. I have edited the below into 2 table, Im just having a bit of confusion.
I have a tables very similar as the ones below and I wanted to show all the table2-class 1 but only 1 random item per each table1-category
Sample Item Table1
+---------+---------------+---------------+
| ID | Item Name | Category |
+---------+---------------+---------------+
| 01 | Item A | Cat 1 |
| 02 | Item B | Cat 1 |
| 03 | Item C | Cat 2 |
| 04 | Item D | Cat 2 |
| 05 | Item E | Cat 3 |
| 06 | Item F | Cat 3 |
+---------+---------------+---------------+
Sample Item Table2
+---------------+---------------+
| Category | Class |
+---------------+---------------+
| Cat 1 | 1 |
| Cat 2 | 1 |
| Cat 3 | 2 |
+---------------+---------------+
I wanted to show all the table2-class 1 but only 1 random item per each table1-category
Desired Result
+---------+---------------+---------------+
| 02 | Item B | Cat 1 |
| 03 | Item C | Cat 2 |
+---------+---------------+---------------+
(This is within my PHP script)
Thanks in advance

You can do something like this
SELECT t.id, itemname, category
FROM
(
SELECT
(
SELECT id
FROM table1
WHERE category = t.category
ORDER BY RAND()
LIMIT 1
) id
FROM table1 t
GROUP BY category
) q JOIN table1 t
ON q.id = t.id
Note: using RAND() is very costly
Output:
| ID | ITEMNAME | CATEGORY |
|----|----------|----------|
| 1 | Item A | Cat 1 |
| 3 | Item C | Cat 2 |
| 6 | Item F | Cat 3 |
Here is SQLFiddle demo

Try something like this:
SELECT id, itemname, category FROM (
SELECT id, itemname, category FROM sample_table
ORDER BY RAND()
) AS tmp
GROUP BY category
Note that this query is totally valid in MySQL
http://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html

The safest way to do this is with a correlated subquery. To get the item_id:
select category,
(select item_id from sample s where s2.category = s.category order by rand() limit 1) as item_id
from sample s
group by category;
To get the rest of the item information, join that back in:
select s.*
from (select category,
(select item_id from sample s where s2.category = s.category order by rand() limit 1) as item_id
from sample s
group by category
) c join
sample s
on s.item_id = c.item_id;

Prior to the above edited scenario, I used the below query and it works fine except that it doesn't randomize the entry of each category:
SELECT * FROM Table1,Table2
WHERE Table2.Class = '1'
AND Table1.Category = Table2.Category
GROUP BY Table1.Category ORDER BY RAND()

Related

sql many-to-many query result only if matches only and exactly all items in array

I have three tables on a mysql database:
| pieces | line_up | instrument |
--------------------------------------------
| id_piece | piece_id | id_instrument |
| title | instrument_id | instrument |
now what I'm trying to achieve is: I'd like to query those pieces whose line_up is made up by exactly the instruments given by a list, not one less not one more.
This sql query reduces the result to those piece who are only played by the 2 instruments, but it includes the solos
SELECT id_piece, title FROM pieces WHERE
(SELECT COUNT(*) FROM line_up WHERE line_up.piece_id = pieces.id_piece)
=
(SELECT COUNT(*) FROM line_up
INNER JOIN instruments ON instruments.id_instrument = line_up.instrument_id
WHERE line_up.piece_id = pieces.id_piece
AND instruments.instrument IN ('guitar', 'drums'));
For example with these tables:
| pieces | | line_up | | instruments |
----------------------- --------------------------- ------------------------------
| id_piece | title | | piece_id | instrument_id | | id_instrument | instrument |
----------------------- ---------------------------- ------------------------------
| 1 | hello | | 1 | 1 | | 1 | guitar |
| 2 | goodbye | | 1 | 2 | | 2 | drums |
| 3 | goodnight | | 2 | 1 | ------------------------------
------------------------ | 3 | 2 |
----------------------------
the only actual piece for both guitar and drums, hence the result of my query, should be 1 | hello.
Any suggestions? Thank you!
You could use aggregation like so:
select p.id_piece, p.title
from pieces as p
inner join line_up as l on l.piece_id = p.id_piece
inner join instruments as i on l.instrument_id = i.id_instrument
group by p.id_piece
having sum(i.instrument in ('guitar', 'drums')) = 2
and sum(i.instrument not in ('guitar', 'drums')) = 0
If you don't have too many instruments to search for, an alternative is string aggregation in the having clause:
having group_concat(i.instrument order by i.instrument) = 'drums,guitar'
This second expression requires that you give the query an alphabetically-ordered list of instruments.
Another approach. Match by instrument combination.
SELECT title
FROM pieces
WHERE id_piece IN (
SELECT piece_id
FROM line_up
WHERE instrument_id IN (
SELECT id_instrument
FROM instruments
WHERE instrument IN ('Guitar', 'Drums')
)
GROUP BY piece_id
HAVING group_concat(instrument_id order by instrument_id separator ',') = (
SELECT group_concat(id_instrument order by id_instrument separator ',')
FROM instruments
WHERE instrument IN ('Guitar', 'Drums')
)
);
You need to count the number of rows that were filtered with instruments and compare it with the total number of instruments and number of instruments you've selected.
db<>fiddle
with pieces as (
select 1 as id_piece, 'hello' as title union all
select 2 as id_piece, 'goodbye' union all
select 3 as id_piece, 'goodnight'
)
, line_up as (
select 1 as piece_id, 1 as instrument_id union all
select 1 as piece_id, 2 as instrument_id union all
select 2 as piece_id, 1 as instrument_id union all
select 3 as piece_id, 2 as instrument_id
)
, instruments as (
select 1 as id_instrument, 'guitar' as instrument union all
select 2 as id_instrument, 'drums' as instrument
)
select p.id_piece, p.title
from pieces as p
join line_up as l
on l.piece_id = p.id_piece
join instruments as i
on l.instrument_id = i.id_instrument
group by p.id_piece, p.title
having sum(case when i.instrument in ('guitar', 'drums') then 1 else 0 end) = count(1)
and count(1) = (
/*To select only valid instruments*/
select count(1)
from instruments as f
where f.instrument in ('guitar', 'drums')
)
-----------+--------
| id_piece | title |
|----------+-------|
| 1 | hello |
-----------+--------

Mysql sum count occurrences in multiple columns from another table

I need to find the sum of occurrences of ids in a join table. The id could be present in two different columns (id_type_1 and id_type_2).
Table types
id | name
1 | Test1
2 | Test2
3 | Test3
Table products
id | name | id_type_1 | id_type_2
1 | Product1 | 1 | 2
2 | Product2 | 3 | 1
3 | Product3 | 1 | 3
I need to get a result like this:
Type | Total
Test1 | 3
Test2 | 1
Test3 | 2
Here's my query, but it takes several seconds to execute:
SELECT t.name,
(SELECT COUNT(p.id) FROM products p WHERE p.id_type_1 = t.id || p.id_type_2 = t.id) AS total
FROM types t
WHERE 1
ORDER BY total DESC
Is there a more effective way to achieve the result?
Join the tables and aggregate:
select t.id, t.name,
sum((t.id = p.id_type_1) + (t.id = p.id_type_2)) Total
from types t inner join products p
on t.id in (p.id_type_1, p.id_type_2)
group by t.id, t.name
If there is no case for the id to exist in both id_type_1 and id_type_2 in the same row then:
select t.id, t.name,
count(*) Total
from types t inner join products p
on t.id in (p.id_type_1, p.id_type_2)
group by t.id, t.name
See the demo.
Results:
> id | name | Total
> -: | :---- | ----:
> 1 | Test1 | 3
> 2 | Test2 | 1
> 3 | Test3 | 2

How do I return a specific number of rows (larger than 1), for each of the joined rows from the other table

An SqlFiddle showcasing sample data for my use case is here: http://sqlfiddle.com/#!9/ae57c5d/6
I have a table category (id, title) containing a list of categories, and a table item(id,name,category_id) with a foreign key category_id that is pointing to a category, and cannot be NULL.
If I now wanted to select the latest item from each category, I could do that by running the following query:
SELECT item.id, item.name, category.title
FROM category
JOIN (
SELECT MAX(id) AS max_id, category_id
FROM item
GROUP BY category_id
) AS i_max ON (i_max.category_id = category.id)
JOIN item ON (item.id = i_max.max_id)
ORDER BY item.id DESC
That gets me this:
+----+------------+-------+
| id | name | title |
+----+------------+-------+
| 15 | Sydney | City |
| 10 | Tesla | Car |
| 5 | Pear | Fruit |
+----+------------+-------+
But how would I write the query if I wanted 3 latest items from each category?
My expected output in this case would be something like this (order of categories in the output is irrelevant; order of items should be descending when focusing on any given category from the output):
+----+------------+-------+
| id | name | title |
+----+------------+-------+
| 15 | Sydney | City |
| 14 | London | City |
| 13 | Helsinki | City |
| 10 | Tesla | Car |
| 9 | Ferrari | Car |
| 8 | Mitsubishi | Car |
| 5 | Pear | Fruit |
| 4 | Watermelon | Fruit |
| 3 | Apple | Fruit |
+----+------------+-------+
Assuming you are using MySQL 8+, you could use ROW_NUMBER here:
WITH cte AS (
SELECT i.id, i.name, c.title,
ROW_NUMBER() OVER (PARTITION BY c.title ORDER BY i.id DESC) rn
FROM category c
INNER JOIN item i ON i.category_id = c.id
)
SELECT id, name, title
FROM cte
WHERE rn <= 3
ORDER BY title, id DESC;
If you upgrade DB to 8+ as mentioned, you'll be able to use analytic functions such as DENSE_RANK()
SELECT id, name, title
FROM
(
SELECT i.id, i.name, c.title,
DENSE_RANK() OVER (PARTITION BY i.category_id ORDER BY i.id DESC ) AS dr
FROM category c
JOIN item i
ON i.category_id = c.id
) t
WHERE dr <= 3
ORDER BY t.id DESC
Demo
The results with the ties(the values with the same ranking) also included in the result set for the case with using DENSE_RANK() function, while for ROW_NUMBER() it doesn't.

How to calculate max values of groups?

I have a table like so (I'm not sure how to format tables)
Category / Products / Purchases
1 | A | 12
1 | B | 13
1 | C | 11
2 | A | 1
2 | B | 2
2 | C | 3
Expected output:
1 | B | 13
2 | C | 3
However I keep on getting
1 | A | 13
2 | A | 3
ie. It just selects the first occurrence of the second column.
Here is my code:
SELECT Category, Products, MAX(Purchases) FROM myTable GROUP BY Category;
Use filtering in the where clause:
select t.*
from t
where t.purchases = (select max(t2.purchases) from t t2 where t2.category = t.category);
With NOT EXISTS:
select m.* from myTable m
where not exists (
select 1 from myTable
where category = m.category and purchases > m.purchases
)
See the demo.
Results:
| Category | Products | Purchases |
| -------- | -------- | --------- |
| 1 | B | 13 |
| 2 | C | 3 |
You can use row_number() to identify max purchase for each group or replace rownumber() to rank() if there are ties of max purchases for each group
Select Category, Products,
Purchases from (Select Category,
Products,
Purchases,
row_number() over (partition by
category, products order by
purchases desc) rn from table) t
where t.rn=1
)

Mysql query with multiple conditions on FK

I have slight problem with mysql query. I have two tables:
bioshops
+------------+-------------+
| bioshop_id | name |
+------------+-------------+
| 1 | Bioshop1 |
| 2 | Bioshop2 |
+------------+-------------+
bioshop_have_product
+----+-----------------+--------------+
| id | bioshop_id | product_id |
+----+-----------------+--------------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 2 | 3 |
+----+-----------------+--------------+
The tables are much more complex but this is the important structure. prodict_id in bioshop_have_product is also FK. I need to select bioshops witch contains all products that I ask. Example:
if I need bioshops with product 1 it should return Bioshop1 and Bioshop2 with all products
if I need bioshops with product 1 and 2 it should return Bioshop1 with all products
My query is:
SELECT bs.name AS name,
bs.id AS bioshop_id,
bshd.id AS id,
bshd.product_id AS product_id
FROM bioshops bs
JOIN bioshop_have_product bshp
ON bs.bioshop_id = bshp.bioshop_id
WHERE (bshp.bioshop_id = bs.bioshop_id AND bshp.product_id = '1')
AND (bshp.bioshop_id = bs.bioshop_id AND bshp.product_id = '2')
but this returns nothing and I want it to return Bioshop1 because only Bioshop1 countains both objects.
You can try something like this:
SELECT bs.name AS name,
bs.id AS bioshop_id,
bshp.id AS id,
bshp.product_id AS product_id
FROM bioshop bs
JOIN bioshop_have_product bshp
ON bs.id = bshp.bioshop_id AND
(SELECT COUNT(*) FROM bioshop_have_product WHERE product_id IN (1, 2) AND bs.id = bioshop_id) = X
where X should be equal to the count of different products you whant to check, for instance 2 in your second case.
SELECT bioshop_id
FROM bioshop_have_product
WHERE product_id IN (1,2)
GROUP
BY bioshop_id
HAVING COUNT(*) = 2;