A normalized version of you data could be:
create table items (
id int,
name varchar(50),
primary key (id),
index (name)
);
create table categories (
id int,
name varchar(50),
primary key (id),
index (name)
);
create table items_categories (
item_id int,
category_id int,
primary key (item_id, category_id),
index (category_id, item_id),
foreign key (item_id) references items(id),
foreign key (category_id) references categories(id)
);
insert into items (id, name) values
(1, 'Lorem1'),
(2, 'Lorem2'),
(3, 'Lorem3'),
(4, 'Lorem4');
insert into categories (id, name) values
(1, 'A'),
(2, 'B'),
(3, 'C'),
(4, 'D');
insert into items_categories (item_id, category_id) values
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 2),
(3, 1),
(3, 3),
(4, 2);
Now - When you search for items in categories 'A' and 'B', the SELECT query would be:
select i.*, count(*) as matches
from items i
join items_categories ic on ic.item_id = i.id
join categories c on c.id = ic.category_id
where c.name in ('A', 'B')
group by i.id
order by matches desc, i.name;
Result:
| id | name | matches |
| --- | ------ | ------- |
| 1 | Lorem1 | 2 |
| 2 | Lorem2 | 2 |
| 3 | Lorem3 | 1 |
| 4 | Lorem4 | 1 |
If you want to search in categories 'A' and 'C', change the WHERE clause to
where c.name in ('A', 'C')
The result would be:
| id | name | matches |
| --- | ------ | ------- |
| 1 | Lorem1 | 2 |
| 3 | Lorem3 | 2 |
| 2 | Lorem2 | 1 |
View on DB Fiddle
You can even "emulate" your original schema with
select i.*, group_concat(c.name separator ', ') as categories
from items i
join items_categories ic on ic.item_id = i.id
join categories c on c.id = ic.category_id
group by i.id
Result:
| id | name | categories |
| --- | ------ | ---------- |
| 1 | Lorem1 | A, B, C |
| 2 | Lorem2 | A, B |
| 3 | Lorem3 | A, C |
| 4 | Lorem4 | B |
It would be much harder to do it the other way round. That is (for me) a major reason to use a normalized schema.
A good read: Is storing a delimited list in a database column really that bad?