Java 如何对postgresql表中与输入值匹配或与任何其他匹配行中的值匹配的行进行集群?
在我的postgresql数据库中有一个类似的表 如果群集中的每个联系人与群集中的另一个联系人共享contact_id_a或contact_id_b值,或者两者都共享,我如何带回联系人群 在上面截图中的示例中,第1-6行将在同一个集群中,第8行不属于任何集群 如何使用SQL查询或SQL查询结合Java代码来实现这一点 对于上下文,此表列出了联系人列表中所有可能重复的联系人。我们希望向列表所有者显示所有可能重复的联系人,以便用户可以手动管理这些重复的联系人 这是我的起始代码:Java 如何对postgresql表中与输入值匹配或与任何其他匹配行中的值匹配的行进行集群?,java,sql,postgresql,cluster-analysis,Java,Sql,Postgresql,Cluster Analysis,在我的postgresql数据库中有一个类似的表 如果群集中的每个联系人与群集中的另一个联系人共享contact_id_a或contact_id_b值,或者两者都共享,我如何带回联系人群 在上面截图中的示例中,第1-6行将在同一个集群中,第8行不属于任何集群 如何使用SQL查询或SQL查询结合Java代码来实现这一点 对于上下文,此表列出了联系人列表中所有可能重复的联系人。我们希望向列表所有者显示所有可能重复的联系人,以便用户可以手动管理这些重复的联系人 这是我的起始代码: DuplicateC
DuplicateCandidate firstDuplicate = db.sql("select * from duplicates where list_id = "+list_id+ " and ignore_duplicate is not true").first(DuplicateCandidate);
String sql = "select * from duplicates where list_id = "+list_id+ "and ignore_duplicate is not true "
+ "and (contact_id_a = ? or contact_id_b = ? or contact_id_a = ? or contact_id_b = ?";
List<DuplicateCandidate> groupOfDuplicates = db.sql(sql, firstDuplicate.contact_id_a,firstDuplicate.contact_id_a, firstDuplicate.contact_id_b, firstDuplicate.contact_id_b).results(DuplicateCandidate.class);
这将返回第一行和包含16247096或16247097的任何其他行,但不返回与第二个查询结果中的联系人ID匹配的其他基本行
干杯。您可以使用递归CTE。这将遍历图形,然后为每一行分配图形中的最小标识符。请注意,您的数据没有每行的唯一标识符,因此首先生成一个:
with recursive d as (
select row_number() over (order by contact_id_a, contact_id_b) as id, d.*
from duplicates d
),
cte (id, contact_id_a, contact_id_b, min_id, ids, lev) as (
select id, contact_id_a, contact_id_b, id as min_id, array[id] as ids, 1 as lev
from d
union all
select d.id, d.contact_id_a, d.contact_id_b, least(d.id, cte.min_id), ids || d.id, lev + 1
from cte join
d
on cte.contact_id_a = d.contact_id_a or cte.contact_id_b = d.contact_id_b
where d.id <> ALL (cte.ids)
)
select distinct on (id) cte.*
from cte
order by id, min_id;
列min_id包含所需的分组
是一个演示代码的数据小提琴。像这样的集群是一个迭代过程,步骤未知。我从未找到可以在递归查询中完成的解决方案 我已经六年多没有从事CRM工作了,但是下面的功能与我们用来生成匹配组的功能类似。对于我们的工作负载来说,逐行执行此操作的性能不够好,通过主机语言使用Java HashMap和HashSet以及反向索引来完成此操作会创建非常混乱的代码 假设此模式:
\d contact_info
Table "public.contact_info"
Column | Type | Collation | Nullable | Default
------------------+---------+-----------+----------+---------
contact_id_a | bigint | | |
contact_id_b | bigint | | |
ignore_duplicate | boolean | | | false
list_id | integer | | | 496
select * from contact_info ;
contact_id_a | contact_id_b | ignore_duplicate | list_id
--------------+--------------+------------------+---------
16247096 | 16247097 | f | 496
16247096 | 16247098 | f | 496
16247096 | 16247099 | f | 496
16247097 | 16247098 | f | 496
16247097 | 16247099 | f | 496
16247098 | 16247099 | f | 496
16247094 | 16247095 | f | 496
(7 rows)
此函数创建两个临时表来保存中间集群,然后在不再可能进行集群时返回结果
create or replace function cluster_contact()
returns table (clust_id bigint, contact_id bigint)
language plpgsql as $$
declare
last_count bigint := 1;
this_count bigint := 0;
begin
create temp table contact_match (clust_id bigint, contact_id bigint) on commit drop;
create index cm_1 on contact_match (contact_id, clust_id);
create index cm_2 on contact_match using hash (clust_id);
create temp table contact_hold (clust_id bigint, contact_id bigint) on commit drop;
with dedup as (
select distinct least(ci.contact_id_a) as clust_id,
greatest(ci.contact_id_b) as contact_id
from contact_info ci
where not ci.ignore_duplicate
)
insert into contact_match
select d.clust_id, d.clust_id from dedup d
union
select d.clust_id, d.contact_id from dedup d;
while last_count > this_count loop
if this_count = 0 then
select count(distinct cm.clust_id) into last_count from contact_match cm;
else
last_count := this_count;
end if;
with new_cid as (
select cm.contact_id as clust_id_old,
min(cm.clust_id) as clust_id_new
from contact_match cm
group by cm.contact_id
)
update contact_match
set clust_id = nc.clust_id_new
from new_cid nc
where contact_match.clust_id = nc.clust_id_old;
truncate table contact_hold;
insert into contact_hold
select distinct * from contact_match;
truncate table contact_match;
insert into contact_match
select * from contact_hold;
select count(distinct cm.clust_id) into this_count from contact_match cm;
end loop;
return query select * from contact_match order by clust_id, contact_id;
end $$;
我所见过的开发人员面临的最大心理障碍之一是忽略了联系人id与自身的关系。这导致了不相交的处理和不必要地由左侧和右侧复杂化的心智模型
select * from cluster_contact();
clust_id | contact_id
----------+------------
16247094 | 16247094
16247094 | 16247095
16247096 | 16247096
16247096 | 16247097
16247096 | 16247098
16247096 | 16247099
(6 rows)
如果您需要澄清此解决方案中的任何步骤,或者此解决方案不适用于您,请发表意见
另外,要知道Levenshtein在中可用,而且它工作得很好
如果希望顺序clust_id从1开始,请将函数中的返回查询更改为:
return query
select dense_rank() over (order by cm.clust_id) as clust_id,
cm.contact_id
from contact_match cm
order by clust_id, contact_id;
它将产生:
select * from cluster_contact();
clust_id | contact_id
----------+------------
1 | 16247094
1 | 16247095
2 | 16247096
2 | 16247097
2 | 16247098
2 | 16247099
(6 rows)
如果识别重复项是用例,为什么不通过d>1的a、b、c从表组中选择a、b、c、COUNT1作为d;其中a、b和c是您想要识别重复项的列?因为我们不仅仅是在搜索精确匹配项。我们正在使用计分方案。我们为每列指定了唯一的权重。我们在Java中检查两个联系人之间的相似程度。如果使用每个列中的LevsHeTin距离乘以相应的列权重的相似度的总和低于截止阈值,那么我们考虑这两个联系人重复并将它们添加到表中。这太复杂了,无法在SQL语句中完成。@GNG。db/SQL FIDLE将非常有用,或者至少是非图像格式的数据。错误:cte第5行或附近的语法错误:递归cte联系人id A、联系人id b、最小id、id…^SQL状态:42601字符:141@GNG . . . 我修复了一些语法错误并添加了一个dbfiddle。这看起来确实有效。我们如何改变这个结果,使cluster_id只是一个增量整数?或者将集群id作为集群中的最小联系人id是否有好处?@GNG我使用了最小联系人id,因为它就在那里。您可以在末尾使用密集排列对其重新编号。我将把这一点补充到我的回答中。