我搜索了很多,但什么都没找到。
我的情况是:
我有两个表table_item
和table_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
当我将D
与G
关联起来
获取G
时,我可以成功获取D
,反之亦然。但是问题出在我
将H
与G
链接
所以我必须在获取D
的同时获取H
G
。
({D
H
G
已通过各种方式链接,并且在获取其中一种后,必须附加并获取其余两个)
这就像一个多重关系(多对多关系)。
伙计们,我知道必须有专业的方法来做到这一点。我希望有任何指导。我什至可以更改数据库结构。
PS:
请不要建议添加#tag
,因为一个项目与另一个链接完全相似。
答案 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)
在您的应用程序中,您将删除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_cluster
(item_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 ;
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
但是:在处理冗余数据时几乎总是如此-与源数据保持同步可能会变得非常复杂。添加和合并群集很简单-当需要删除/删除项目或链接时,可能需要拆分群集,这可能需要编写递归或迭代代码来确定哪些项目属于同一群集。尽管一种简单(愚蠢的)算法只是删除并重新插入所有受影响的项目和链接,然后让插入触发器完成其工作。
最后但并非最不重要:您可以编写一个存储过程,该过程将通过链接进行迭代:
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
以伪代码进行解释:
call get_linked_items(1);
和@ids
@ids_next
中查找直接链接到@ids_next
中任何ID的所有项目ID @ids
中(覆盖)@ids_next
的ID附加到@ids_next
(将这两个集合合并到@ids
中)@ids
不为空:请转到步骤2。@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的所有“孩子”。