从MySQL表中选择,按匹配的类别数排序

时间:2019-04-02 19:09:47

标签: php mysql sql

我有一张桌子,像这样:

id:int | name:String | categories:String

示例行:

1 | "Lorem1" | "A, B, C" 
2 | "Lorem2" | "A, B" 
3 | "Lorem3" | "A, C" 
4 | "Lorem4" | "B" 

我还有一个表格,您可以在其中检查u所涉及的类别。这应该是选择顺序的指南。

首先,您获得具有所有选定类别的行,然后获得具有较少匹配项的行。 (如果该行没有任何类别,则不会显示)

例如有人检查:

  • A和B,它们应按以下顺序返回行:Lorem1,Lorem2,Lorem3,Lorem 4

  • A和C,它们应按以下顺序返回行:Lorem1,Lorem3,Lorem2

这就是我要做的。我对编程很陌生,并且出现了这个问题。

我也知道,也许我应该为类别和对象之间的连接创建一个新表。

3 个答案:

答案 0 :(得分:0)

您可以使用find_in_set()检查以逗号分隔的列表中的字符串。但是您必须先replace()个空格。对用户选择的每个类别都这样做。然后检查find_in_set()的结果是否大于0,因为0表示未找到任何内容,否则返回列表中大于0的位置。将这些比较的结果相加。由于布尔运算的正确性在数字上下文中为1,否则为0,因此可以按该总和递减顺序进行排序。即行匹配的内容越多,则将其放在结果中的时间越早。

类别'A''C'的示例:

SELECT *
       FROM elbat
       ORDER BY (find_in_set('A', replace(categories, ' ', '')) > 0)
                +
                (find_in_set('C', replace(categories, ' ', '')) > 0)
                DESC;

您还可以使用它来排除没有匹配项的行。则总和为0。

SELECT *
       FROM elbat
       WHERE (find_in_set('A', replace(categories, ' ', '')) > 0)
             +
             (find_in_set('C', replace(categories, ' ', '')) > 0)
             > 0
       ORDER BY (find_in_set('A', replace(categories, ' ', '')) > 0)
                +
                (find_in_set('C', replace(categories, ' ', '')) > 0)
                DESC;

但是用逗号分隔的列表很麻烦。您应该考虑修改架构,并使用另一个表将项目链接到类别。

答案 1 :(得分:0)

您应该在用户表中定义一个ManyToMany字段,而不是将类别存储为字符串。因此,用户可以是一个或多个类别的一部分,反之亦然。类别表可以存储不同的类别及其各自的ID。

答案 2 :(得分:0)

您的数据的规范化版本可能是:

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);

现在-当您搜索类别“ A”和“ B”中的项目时,SELECT查询将为:

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;

结果:

| id  | name   | matches |
| --- | ------ | ------- |
| 1   | Lorem1 | 2       |
| 2   | Lorem2 | 2       |
| 3   | Lorem3 | 1       |
| 4   | Lorem4 | 1       |

如果要搜索类别“ A”和“ C”,请将WHERE子句更改为

where c.name in ('A', 'C')

结果将是:

| id  | name   | matches |
| --- | ------ | ------- |
| 1   | Lorem1 | 2       |
| 3   | Lorem3 | 2       |
| 2   | Lorem2 | 1       |

View on DB Fiddle

您甚至可以使用以下方式“模仿”您的原始架构

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

结果:

| id  | name   | categories |
| --- | ------ | ---------- |
| 1   | Lorem1 | A, B, C    |
| 2   | Lorem2 | A, B       |
| 3   | Lorem3 | A, C       |
| 4   | Lorem4 | B          |

反之则很难。 (对我而言)这是使用规范化架构的主要原因。

好读的书:Is storing a delimited list in a database column really that bad?