存在一组用户。一个人可以有多个用户,但是ref1
和ref2
可能是相似的,因此可以将用户链接在一起。ref1
和ref2
不重叠,则ref1
中的一个值不存在于ref2
中。
一个用户可以拥有多个资产。我想“合并”拥有一个或多个相同参考的用户,然后计算他们拥有的资产数量。user表中可能缺少条目,在这种情况下,我只想将所有者传播到ref 2中,并设置asset_count和asset_ids。
下面是一个示例模式:
示例资产
SELECT * FROM assets;
| id|姓名|业主|
| - -----|- -----|- -----|
| 1|第一名|a|
| 2|第二名|B|
| 3|三号|c型|
| 4|四号|a|
| 5个|第五名|c型|
| 六|六号|d|
| 七个|#7| e的|
| 八|八号|d|
| 九个|九号|a|
| 十个|第十名|a|
| 十一|十一号|z|
示例用户
SELECT * FROM users;
| id|用户名|参考1|参考文献2|
| - -----|- -----|- -----|- -----|
| 1|波波|a| d|
| 2|托托|B| e的|
| 3|莫莫|c型|d|
| 4|洛洛|a|关闭|
| 5个|波波|c型|关闭|
我最终想要的
SELECT * FROM results;
| IDS|用户名|参考文献1|参考文献2|资产ID|资产计数|
| - -----|- -----|- -----|- -----|- -----|- -----|
| 一二三四五|波波,莫莫,洛洛,波波|a、c| d、f|一二三四五六八九十|八|
| 2|托托|B| e的|二、七|2|
| | | | z|十一|1|
我尝试过不同的方法,但这是我目前所拥有的:
我最亲近的人
SELECT
ARRAY_AGG(DISTINCT u.id) AS ids,
ARRAY_AGG(DISTINCT u.username) AS usernames,
ARRAY_AGG(DISTINCT u.ref1) AS refs1,
ARRAY_AGG(DISTINCT u.ref2) AS refs2,
COUNT(DISTINCT a.id) AS asset_count
FROM assets a
JOIN users u ON a.owner = u.ref1 OR a.owner = u.ref2
GROUP BY a.owner
ORDER BY MIN(a.id);
| IDS|用户名|参考文献1|参考文献2|资产计数|
| - -----|- -----|- -----|- -----|- -----|
| 一、四|波波河|a| d、f| 4|
| 2|托托|B| e的|1|
| 三点五|莫莫,popo| c型|d、f| 2|
| 一、三|波波,莫莫|a、c| d| 2|
| 2|托托|B| e的|1|
如果我在id上合并上面的表,我几乎得到了我想要的结果(没有用户表中丢失的条目)。合并可以很容易地在代码中完成,但我不能分页等。如果可能的话,我想在DB层中对此进行处理。
我想要一个问题的解决方案,或者一个很好的解释为什么不可能做到(用例子)。
请查看我的DB Fiddle。
4条答案
按热度按时间7lrncoxx1#
这个问题有两个不同的部分:
第一部分:图遍历问题
基于公共引用识别用户集群看起来像是一个图行走问题。这在SQL中是一个复杂的任务,需要递归查询。该模式是取消透视用户的引用以生成节点,然后标识边(具有公共引用的节点),最后遍历图(无循环)以生成组。
在Postgres中,数组可以方便地聚合节点:
| id| IDS|
| - -----|- -----|
| 1| {1,3,4,5}|
| 2| {2}|
| 3| {1,3,4,5}|
| 4| {1,3,4,5}|
| 5个|{1,3,4,5}|
第二部分:
left join
和聚合既然我们已经确定了组,我们可以检查资产。由于您希望结果中包含所有资产,因此我们从
assets
表开始,然后使用left join
s引入用户和组。我们仍然可以group by
用户组,这将所有孤立资产放在同一个组中(组为null
)-这正是我们想要的。最后一步是数组聚合;孤儿资产的所有者到
ref2
的“传播”可以用case
表达式来处理。| IDS|用户名|参考文献1|参考文献2|资产ID|资产计数|
| - -----|- -----|- -----|- -----|- -----|- -----|
| {1,3,4,5}|{bobo,lolo,momo,popo}|{a,c}|{d,f}|(一)(二)(三)(一)(二)(三)(四)(四)(五)(五)(六)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(八)(|八|
| {2} |{toto}|{b}|{e}|[2,7]| 2|
| 联系我们|联系我们|联系我们|{z}|{11}| 1|
插件:图行走性能
在有很多边的网络上,性能会受到影响,特别是如果图中只有几个集群,每个集群包含许多用户。我们可以尝试优化这种情况下的查询;想法是通过在每次迭代 * 聚合每个用户 * 的所有路径来尝试和限制需要行走的路径的数量。
这个查询**passes your test case**有200个用户,都属于同一个集群(而第一个查询耗尽了DB Fiddle资源):
r7s23pms2#
这很难(通常非常慢)在SQL中仅使用基于集合的操作来解决。所以我在PL/pgSQL函数中循环:
fiddle
电话:
见内联注解和解释。
一般的想法是避免一步一步地遍历可能很长的图,并尽可能多地进行基于集合的工作。最佳解决方案在很大程度上取决于实际的数据分布。
根据
ref1
和ref2
不重叠的信息,我创建了两个临时表:一个用于ref1
,一个用于ref2
。初始查询聚合同一资源上的所有用户。这可以立即处理同一资源(同一个人)上的许多用户。你说你有很多这样的。在循环的每次迭代中,我合并 other 临时表中的所有行,这些行被 this 临时表中的同一行链接在一起,反之亦然。这两个查询是完全对称的。(从一个或另一个开始可能更有效。)只要一个查询没有找到任何要合并的行,我们就完成了。
然后返回包含所有详细信息的最终集合,并像您在问题中添加的那样附加“用户表中缺少的条目”。
我已经简化了工作流程,但是
DELETE
和INSERT
是相对昂贵的操作。确保您允许足够的临时缓冲区,以便两个临时表可以在RAM中独占操作。我在顶部添加了一点巧妙的自动设置。但如果您的temp_buffer
设置足够高,您可能不需要它。在同一会话中访问任何临时表后,不能使用自动设置。参见:这两个临时表在 transaction 结束时自动删除,因为是用
ON COMMIT DROP
创建的。使用临时表上的索引和更多的微调,性能可能会得到进一步优化。在这种情况下,在临时表上运行
ANALYZE
(一次或多次)也会有所帮助。参见:关于我使用的
ctid
:mzillmmw3#
您所显示的已经是一个有效的图表。使用
pgRouting
,您可以直接查询它:ascii()
和chr()
可以将1个字符的ref
转换为扩展所期望的integer
,然后转换回来:ascii('a')=97
、chr('97')='a'
。在数百万用户组成的不同数量和长度的链接组合上进行了测试,它需要几秒钟的时间,并且在单个CPU上占用的内存可以忽略不计。在引擎盖下,它是一个Boost C++ DFS。db<>fiddle,db-fiddle和sqlfiddle不提供
pgrouting
,这就是为什么我不能附加一个在线演示,但here的测试数据生成器,我折磨这个。一旦SQL:2023的SQL/PGQ进入PostgreSQL发行版,您可以期待一个内置的机制。
h43kikqp4#
请看下一个解决方案:
https://sqlize.online/sql/psql11/88ab227ab4a34c532fc711cff533f272/
它返回几乎期望的结果。您只需要unique assets_id数组