java—如何在postgresql表中对与输入值匹配的行或与任何其他匹配行中的值匹配的行进行聚类?

ybzsozfc  于 2021-07-24  发布在  Java
关注(0)|答案(2)|浏览(403)

在我的postgresql数据库中有一个这样的表

如果群集中的每个联系人都与群集中的另一个联系人共享contact\u id\u a或contact\u id\u b值(或两者都共享),如何恢复联系人群?
在上面截图中的示例中,第1-6行将位于同一个集群中,第8行不属于任何集群。
如何使用sql查询或sql查询结合java代码来实现这一点?
对于上下文,此表列出了联系人列表中所有可能的重复联系人。我们希望向列表所有者显示所有可能重复的联系人,以便用户可以手动管理这些重复。
这是我的起始代码:

  1. DuplicateCandidate firstDuplicate = db.sql("select * from duplicates where list_id = "+list_id+ " and ignore_duplicate is not true").first(DuplicateCandidate);
  2. String sql = "select * from duplicates where list_id = "+list_id+ "and ignore_duplicate is not true "
  3. + "and (contact_id_a = ? or contact_id_b = ? or contact_id_a = ? or contact_id_b = ?";
  4. 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的任何其他行,但不会从第二个查询的结果中带回与联系人标识匹配的其他基本行。
干杯。

t3psigkw

t3psigkw1#

像这样的聚类是一个迭代过程,步骤数未知。我从来没有找到一个可以在递归查询中完成的解决方案。
我已经六年没有从事crm工作了,但是下面的功能与我们以前生成匹配组的方式类似。对于我们的工作负载来说,一行一行地完成这项工作并没有表现得足够好,而使用java等宿主语言来完成这项工作 HashMap() 以及 HashSet() 反向索引会产生非常混乱的代码。
假设此架构:

  1. \d contact_info
  2. Table "public.contact_info"
  3. Column | Type | Collation | Nullable | Default
  4. ------------------+---------+-----------+----------+---------
  5. contact_id_a | bigint | | |
  6. contact_id_b | bigint | | |
  7. ignore_duplicate | boolean | | | false
  8. list_id | integer | | | 496
  9. select * from contact_info ;
  10. contact_id_a | contact_id_b | ignore_duplicate | list_id
  11. --------------+--------------+------------------+---------
  12. 16247096 | 16247097 | f | 496
  13. 16247096 | 16247098 | f | 496
  14. 16247096 | 16247099 | f | 496
  15. 16247097 | 16247098 | f | 496
  16. 16247097 | 16247099 | f | 496
  17. 16247098 | 16247099 | f | 496
  18. 16247094 | 16247095 | f | 496
  19. (7 rows)

此函数创建两个临时表来保存中间集群,然后在不再可能进行集群时返回结果。

  1. create or replace function cluster_contact()
  2. returns table (clust_id bigint, contact_id bigint)
  3. language plpgsql as $$
  4. declare
  5. last_count bigint := 1;
  6. this_count bigint := 0;
  7. begin
  8. create temp table contact_match (clust_id bigint, contact_id bigint) on commit drop;
  9. create index cm_1 on contact_match (contact_id, clust_id);
  10. create index cm_2 on contact_match using hash (clust_id);
  11. create temp table contact_hold (clust_id bigint, contact_id bigint) on commit drop;
  12. with dedup as (
  13. select distinct least(ci.contact_id_a) as clust_id,
  14. greatest(ci.contact_id_b) as contact_id
  15. from contact_info ci
  16. where not ci.ignore_duplicate
  17. )
  18. insert into contact_match
  19. select d.clust_id, d.clust_id from dedup d
  20. union
  21. select d.clust_id, d.contact_id from dedup d;
  22. while last_count > this_count loop
  23. if this_count = 0 then
  24. select count(distinct cm.clust_id) into last_count from contact_match cm;
  25. else
  26. last_count := this_count;
  27. end if;
  28. with new_cid as (
  29. select cm.contact_id as clust_id_old,
  30. min(cm.clust_id) as clust_id_new
  31. from contact_match cm
  32. group by cm.contact_id
  33. )
  34. update contact_match
  35. set clust_id = nc.clust_id_new
  36. from new_cid nc
  37. where contact_match.clust_id = nc.clust_id_old;
  38. truncate table contact_hold;
  39. insert into contact_hold
  40. select distinct * from contact_match;
  41. truncate table contact_match;
  42. insert into contact_match
  43. select * from contact_hold;
  44. select count(distinct cm.clust_id) into this_count from contact_match cm;
  45. end loop;
  46. return query select * from contact_match order by clust_id, contact_id;
  47. end $$;

我见过的开发人员面临的最大的心理障碍之一就是忽略了 contact_id 对自己。这导致了不相交的处理和不必要的复杂的心理模型的左侧和右侧。

  1. select * from cluster_contact();
  2. clust_id | contact_id
  3. ----------+------------
  4. 16247094 | 16247094
  5. 16247094 | 16247095
  6. 16247096 | 16247096
  7. 16247096 | 16247097
  8. 16247096 | 16247098
  9. 16247096 | 16247099
  10. (6 rows)

如果您需要澄清此解决方案中的任何步骤,或者它不适合您,请发表意见。
另外,要知道levenshtein在 fuzzystrmatch ,效果很好。
如果你愿意 clust_id 开始于 1 ,更改您的 return query 在此函数中:

  1. return query
  2. select dense_rank() over (order by cm.clust_id) as clust_id,
  3. cm.contact_id
  4. from contact_match cm
  5. order by clust_id, contact_id;

它将产生:

  1. select * from cluster_contact();
  2. clust_id | contact_id
  3. ----------+------------
  4. 1 | 16247094
  5. 1 | 16247095
  6. 2 | 16247096
  7. 2 | 16247097
  8. 2 | 16247098
  9. 2 | 16247099
  10. (6 rows)
展开查看全部
dy1byipe

dy1byipe2#

可以使用递归cte。这将遍历图形,然后为每一行分配图形中的最小标识符。请注意,您的数据没有每行的唯一标识符,因此首先生成一个:

  1. with recursive d as (
  2. select row_number() over (order by contact_id_a, contact_id_b) as id, d.*
  3. from duplicates d
  4. ),
  5. cte (id, contact_id_a, contact_id_b, min_id, ids, lev) as (
  6. select id, contact_id_a, contact_id_b, id as min_id, array[id] as ids, 1 as lev
  7. from d
  8. union all
  9. select d.id, d.contact_id_a, d.contact_id_b, least(d.id, cte.min_id), ids || d.id, lev + 1
  10. from cte join
  11. d
  12. on cte.contact_id_a = d.contact_id_a or cte.contact_id_b = d.contact_id_b
  13. where d.id <> ALL (cte.ids)
  14. )
  15. select distinct on (id) cte.*
  16. from cte
  17. order by id, min_id;

min_id 包含所需的分组。
下面是一个db<>小提琴来演示代码。

展开查看全部

相关问题