Mysql 将一个项目链接到同一表中的其他项目

Mysql 将一个项目链接到同一表中的其他项目,mysql,codeigniter,database-design,Mysql,Codeigniter,Database Design,我找了很多,但什么也没找到 我的设想是: 我的数据库有两个表table\u item和table\u item\u linked表_项有许多项。用户将来添加项目。稍后,其他用户通过带有两个下拉列表的表单将一个项目与其他项目链接起来 到目前为止,我所做的是: 表项的结构: +-------------------+ | table_item | +-------------------+ | item_id (Primary) | | others | | ..

我找了很多,但什么也没找到

我的设想是:

我的数据库有两个表
table\u item
table\u item\u linked
<代码>表_项有许多项。用户将来添加项目。稍后,其他用户通过带有两个
下拉列表的表单将一个项目与其他项目链接起来

到目前为止,我所做的是:

表项的结构

+-------------------+
| table_item        |
+-------------------+
| item_id (Primary) |
| others            |
| ....              |
| ....              |
| ....              |
+-------------------+
+---------------------+
| table_item_linked   |
+---------------------+
| linked_id           | (Primary)
| item_id             | (Foreign key referencing -> item_id of table_item) 
| linked_items        | (here I need to store ids of linked items)    
| linked_by           | (referencing to user_id of user_table)           
| linked_timestamp    | (timestamp) 
+---------------------+
链接的
表项的结构

+-------------------+
| table_item        |
+-------------------+
| item_id (Primary) |
| others            |
| ....              |
| ....              |
| ....              |
+-------------------+
+---------------------+
| table_item_linked   |
+---------------------+
| linked_id           | (Primary)
| item_id             | (Foreign key referencing -> item_id of table_item) 
| linked_items        | (here I need to store ids of linked items)    
| linked_by           | (referencing to user_id of user_table)           
| linked_timestamp    | (timestamp) 
+---------------------+
如果我在
表中有项目\u项目
如:
A
B
C
D
E
F
G
H

当我将
D
链接到
G

我可以在获取
D
时成功获取
G
,反之亦然。但问题来了,当我

链接
H
G

因此,我必须在获取
G
的同时获取
D
H

D
H
G
以各种方式链接,在获取一个后,必须附加并获取其余两个)

它就像一个多重关系(多对多关系)

伙计们,我知道一定有专业的方法。我希望得到任何指导。我甚至可以更改我的数据库结构

PS: 请不要建议添加
#标签
,因为一个项目与另一个链接项目完全相似

更新 前端看起来像这样。如果我打算链接两条记录,我将有两个
下拉列表,如图所示:

如果我检查记录
A

如果我检查记录
B

如果我检查记录
C

显而易见的解决方案是在
表\u item\u linked
中为每个链接存储一行

然后,您的桌子将变为

+---------------------+
| table_item_linked   |
+---------------------+
| linked_id           | (Primary
| from_item_id        | (The item linked _from_ -> item_id of table_item) 
| to_item_id          | the item linked _to_  
| linked_by           | (referencing to user_id of user_table)           
| linked_timestamp    | (timestamp) 
+---------------------+
在您的示例中,数据将是:

linked_id     from_item_id    to_item_id   linked_by   linked_timestamp
------------------------------------------------------------------------
1                        D            H            sd      '1 jan 2020'
2                        H            G            sa      '2 Jan 2020'
insert into table_item_linked (item1_id, item2_id, linked_by) values (1, 2, 123);
insert into table_item_linked (item1_id, item2_id, linked_by) values (2, 3, 123);
item_id    record
———————————————————
      2    Record B
      3    Record C

然后,您需要编写一个函数来检索G的所有“子项”。

显而易见的解决方案是在
表\u item\u linked
中为每个链接存储一行

然后,您的桌子将变为

+---------------------+
| table_item_linked   |
+---------------------+
| linked_id           | (Primary
| from_item_id        | (The item linked _from_ -> item_id of table_item) 
| to_item_id          | the item linked _to_  
| linked_by           | (referencing to user_id of user_table)           
| linked_timestamp    | (timestamp) 
+---------------------+
在您的示例中,数据将是:

linked_id     from_item_id    to_item_id   linked_by   linked_timestamp
------------------------------------------------------------------------
1                        D            H            sd      '1 jan 2020'
2                        H            G            sa      '2 Jan 2020'
insert into table_item_linked (item1_id, item2_id, linked_by) values (1, 2, 123);
insert into table_item_linked (item1_id, item2_id, linked_by) values (2, 3, 123);
item_id    record
———————————————————
      2    Record B
      3    Record C

然后,您需要编写一个函数来检索G的所有“子项”。

假设您的
表\u项
如下所示:

create table table_item (
  item_id int unsigned auto_increment not null,
  record  varchar(50),
  primary key (item_id)
);

insert into table_item (record) values
  ('Record A'),
  ('Record B'),
  ('Record C'),
  ('Record D'),
  ('Record E'),
  ('Record F'),
  ('Record G'),
  ('Record H');
表项目链接

create table table_item_linked (
  linked_id int unsigned auto_increment not null,
  item1_id  int unsigned not null,
  item2_id  int unsigned not null,
  linked_by int unsigned not null,
  linked_timestamp timestamp not null default now(),
  primary key (linked_id),
  unique key  (item1_id, item2_id),
  index       (item2_id, item1_id),
  foreign key (item1_id) references table_item(item_id),
  foreign key (item2_id) references table_item(item_id)
);
这基本上是相同类型的项目之间的多对多关系

请注意,这里通常不需要自动增量列。您可以删除它,并将
(item1\u id,item2\u id)
定义为主键。而
链接的
应该是引用
用户
表的外键

如果用户(ID为
123
)希望将“记录a”(
item_ID=1
)与“记录B”(
item_ID=2
)链接,并将“记录B”(
item_ID=2
)与“记录C”(
item_ID=3
)链接,则插入语句如下:

linked_id     from_item_id    to_item_id   linked_by   linked_timestamp
------------------------------------------------------------------------
1                        D            H            sd      '1 jan 2020'
2                        H            G            sa      '2 Jan 2020'
insert into table_item_linked (item1_id, item2_id, linked_by) values (1, 2, 123);
insert into table_item_linked (item1_id, item2_id, linked_by) values (2, 3, 123);
item_id    record
———————————————————
      2    Record B
      3    Record C
现在-当用户选择“记录A”(
item\u id=1
)时,您可以通过递归查询获得所有相关项(至少需要MySQL 8.0或MariaDB 10.2):

在应用程序中,您将删除
set@input\u item\u id=1并更改
选择@input\u item\u id作为item\u id
使用占位符选择?作为项目标识
。然后准备语句并将
item\u id
绑定为参数

更新

如果服务器不支持递归CTEs,则应该考虑将冗余数据存储在单独的表中,这是简单的查询。闭包表是一个选项,但在这里它不是必需的,并且可能会消耗太多的存储空间。我会将连接在一起(直接或间接)的项目分组到集群中

给定与上述相同的模式,我们定义了一个新的表
表\u项\u集群

create table table_item_cluster (
  item_id    int unsigned not null,
  cluster_id int unsigned not null,
  primary key (item_id),
  index       (cluster_id, item_id),
  foreign key (item_id) references table_item(item_id)
);
此表将项目(
item\u id
)链接到集群(
cluster\u id
)。由于一个项目只能属于一个集群,我们可以将
item\u id
定义为主键。它也是一个外键,引用
表\u项

创建新项时,它不会连接到任何其他项,而是构建自己的集群。因此,当我们插入一个新项目时,我们还需要在
表\u item\u集群中插入一个新行。为了简单起见,我们通过
item\u id
item\u id=cluster\u id
)来识别集群。这可以在应用程序代码中完成,也可以使用以下触发器完成:

delimiter //
create trigger table_item_after_insert 
  after insert on table_item
  for each row begin
    -- create a new cluster for the new item
    insert into table_item_cluster (item_id, cluster_id)
      values (new.item_id, new.item_id);
  end//
delimiter ;
当我们链接两个项目时,我们只是合并它们的集群。两个合并集群中所有项目的
cluster\u id
现在需要相同。在这里,我只需要两个中的至少一个。同样,我们可以在应用程序代码中或使用触发器执行此操作:

delimiter //
create trigger table_item_linked_after_insert 
  after insert on table_item_linked
  for each row begin
    declare cluster1_id, cluster2_id int unsigned;

    set cluster1_id = (
      select c.cluster_id
      from table_item_cluster c
      where c.item_id = new.item1_id
    );

    set cluster2_id = (
      select c.cluster_id
      from table_item_cluster c
      where c.item_id = new.item2_id
    );

    -- merge the linked clusters
    update table_item_cluster c
    set c.cluster_id = least(cluster1_id, cluster2_id)
    where c.item_id in (cluster1_id, cluster2_id);
  end//
delimiter ;
现在-当我们有一个项目并希望获得所有(直接和间接)链接的项目时,我们只需从同一集群中选择所有项目(给定项目除外):

select i.*
from table_item i
join table_item_cluster c on c.item_id = i.item_id
join table_item_cluster c1
  on  c1.cluster_id = c.cluster_id
  and c1.item_id <> c.item_id -- exclude the given item
where c1.item_id = ?
但是:在处理冗余数据时,几乎总是这样-保持数据与源数据同步可能会变得相当复杂。虽然添加和合并集群很简单,但当您需要移除/删除项目或链接时,可能需要拆分集群,这可能需要编写递归或迭代代码来确定哪些项目属于同一集群。尽管一个简单(而且“愚蠢”)的算法是删除并重新插入所有受影响的项目和链接,并让插入触发器工作

更新2 最后但并非最不重要的一点:您可以编写一个存储过程,该过程将遍历以下链接:

delimiter //
create procedure get_linked_items(in in_item_id int unsigned)
begin
  set @ids := concat(in_item_id);
  set @ids_next := @ids;
  set @sql_tpl := "
    select group_concat(distinct id order by id) into @ids_next
    from (
      select item2_id as id
      from table_item_linked
      where item1_id in ({params_in})
        and item2_id not in ({params_not_in})
      union all
      select item1_id
      from table_item_linked
      where item2_id in ({params_in})
        and item1_id not in ({params_not_in})
    ) x
  ";

  while (@ids_next is not null) do
    set @sql := @sql_tpl;
    set @sql := replace(@sql, '{params_in}', @ids_next);
    set @sql := replace(@sql, '{params_not_in}', @ids);
    prepare stmt from @sql;
    execute stmt;
    set @ids := concat_ws(',', @ids, @ids_next);
  end while;

  set @sql := "
    select *
    from table_item
    where item_id in ({params})
      and item_id <> {in_item_id}
  ";
  set @sql := replace(@sql, '{params}', @ids);
  set @sql := replace(@sql, '{in_item_id}', in_item_id);

  prepare stmt from @sql;
  execute stmt;
end//
delimiter ;

要用伪代码解释它,请执行以下操作:

  • 使用输入参数初始化
    @ids
    @ids\u next
  • 查找直接链接到
    @IDs\u next
    中任何ID的所有项目ID,除了那些已经在
    @IDs
  • 将结果存储到
    @ids\u next
    (覆盖它)
  • @IDs\u next
    中的ID追加到
    @IDs<