将一项链接到同一表格中的另一项

时间:2019-06-24 10:49:08

标签: mysql codeigniter database-design

我搜索了很多,但什么都没找到。

我的情况是:

我有两个表table_itemtable_item_linked的数据库。 table_item有很多项目。用户将来添加项目。后来,其他用户来了,并通过带有两个link的表单将dropdown的一个项目与其他项目进行了复制。

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

table_item的结构:

+-------------------+
| table_item        |
+-------------------+
| item_id (Primary) |
| others            |
| ....              |
| ....              |
| ....              |
+-------------------+

table_item_linked的结构:

+---------------------+
| 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中有以下项目: A B C D E F G H

当我将DG关联起来

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

HG链接

所以我必须在获取D的同时获取H G

({D H G已通过各种方式链接,并且在获取其中一种后,必须附加并获取其余两个)

这就像一个多重关系(多对多关系)。

伙计们,我知道必须有专业的方法来做到这一点。我希望有任何指导。我什至可以更改数据库结构。

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

2 个答案:

答案 0 :(得分:1)

假设您的table_item如下所示:

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

table_item_linked可能是

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

这基本上是相同类型项目之间的多对多关系

请注意,这里通常不需要AUTO_INCREMENT列。您可以删除它,并将(item1_id, item2_id)定义为PRIMARY KEY。并且linked_by应该是引用users表的FOREGN KEY。

如果用户(标识为123)希望将“记录A”(item_id = 1)与“记录B”(item_id = 2)和“记录B”({{1 }})与“记录C”(item_id = 2)一起使用,您的INSERT语句将为:

item_id = 3

现在-当用户选择“记录A”(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); )时,您可以通过递归查询获得所有相关项(至少需要MySQL 8.0或MariaDB 10.2):

item_id = 1

结果将是:

set @input_item_id = 1;

with recursive input as (
  select @input_item_id as item_id
), rcte as (
  select item_id from input

  union distinct

  select t.item2_id as item_id
  from rcte r
  join table_item_linked t on t.item1_id = r.item_id

  union distinct

  select t.item1_id as item_id
  from rcte r
  join table_item_linked t on t.item2_id = r.item_id
)
  select i.*
  from rcte r
  join table_item i on i.item_id = r.item_id
  where r.item_id <> (select item_id from input)

db-fiddle

在您的应用程序中,您将删除item_id record ——————————————————— 2 Record B 3 Record C 并使用占位符将set @input_item_id = 1;更改为select @input_item_id as item_id。然后准备该语句并将select ? as item_id绑定为参数。

更新

如果服务器不支持递归CTE ,则应考虑将冗余数据存储在单独的表中,该表易于查询。可以使用关闭表,但这不是必需的,它可能会占用过多的存储空间。我会将将(直接和间接)连接在一起的项目分组集群

鉴于与上述相同的架构,我们定义了一个新表item_id

table_item_cluster

此表将项目(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_id)。由于一项只能属于一个集群,因此我们可以将cluster_id定义为主键。这也是引用item_id的外键。

创建新项目时,该项目未连接到任何其他项目,而是建立了自己的集群。因此,当我们插入新项目时,我们还需要在table_item中插入新行。为简单起见,我们用table_item_clusteritem_id)标识集群。这可以在应用程序代码中完成,也可以通过以下触发器完成:

item_id = cluster_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_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 ;

db-fiddle

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 = ? (“记录A”)的结果为:

c1.item_id = 1

但是:在处理冗余数据时几乎总是如此-与源数据保持同步可能会变得非常复杂。添加和合并群集很简单-当需要删除/删除项目或链接时,可能需要拆分群集,这可能需要编写递归或迭代代码来确定哪些项目属于同一群集。尽管一种简单(愚蠢的)算法只是删除并重新插入所有受影响的项目和链接,然后让插入触发器完成其工作。

更新2

最后但并非最不重要:您可以编写一个存储过程,该过程将通过链接进行迭代:

item_id    record
———————————————————
      2    Record B
      3    Record C

要获取“记录A”(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 ; )的所有链接项目,请使用

item_id = 1

db-fiddle

以伪代码进行解释:

  1. 使用输入参数初始化call get_linked_items(1); @ids
  2. @ids_next中查找直接链接到@ids_next中任何ID的所有项目ID
  3. 将结果存储到@ids中(覆盖)
  4. @ids_next的ID附加到@ids_next(将这两个集合合并到@ids中)
  5. 如果@ids不为空:请转到步骤2。
  6. 返回ID为@ids_next的所有项目

答案 1 :(得分:0)

一种明显的解决方案是在table_item_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'

然后,您需要写一个hierarchical query来检索G的所有“孩子”。

相关问题