如何在数据库中找到最长的关系链?

时间:2012-07-03 01:05:28

标签: database algorithm postgresql relationship

我有一个很大的postgresql数据库,包括艺术家,歌曲和歌曲之间的封面关系。我想在数据库中找到最长的封面关系链,类似于http://www.coversproject.com/artist/longest_chain

最后我想要这样的事情:

  • 艺术家一首由艺术家B
  • 创作的封面歌曲
  • 艺术家B原创歌曲2由艺术家C
  • 创作
  • 艺术家C封面歌曲3原作艺术家D
  • ...

在我的用例中,任何艺术家只能在列表中出现一次,这使得这更加棘手。我在这里也简化了我的数据库结构,使问题不那么具体,但这不应成为问题。

在我看来,没有神奇的查询会给我一个明确的答案。我想我需要某种算法,用不同的起始条目一遍又一遍地查询数据库,同时存储每个查询运行的结果。过了一会儿,我只选择那段时间内找到的最长的链条,这可能不是现有的最长的链条,但对我来说足够好了。

有关如何实现这一目标的任何指示? (本地使用postgres或编写查询数据库的脚本)

3 个答案:

答案 0 :(得分:1)

在“The Stanford GraphBase”一节中,FOOTBALL Knuth考虑了在“A节拍B乘5,B节拍C乘9,C节拍D乘43 ......”形式的足球队之间寻找长链游戏的问题。提供一个论证,即A对Z的预期获胜幅度很大。他表示这是一个NP完全问题,并要求提出建议。他实际编程的是他称之为分层贪婪的东西,看起来很像http://en.wikipedia.org/wiki/Beam_search

前段时间我花了一些时间玩Beam Search寻求乐趣,但最后开始想知道有限差异搜索是否更好 - 它往往要求你花更少的时间来节省部分答案的状态,因为它是非常接近回溯,当您做出更多假设或撤回似乎不起作用的假设时,您通常会对答案做出微小的更改。

答案 1 :(得分:1)

嗯,我想我以前做过类似的事情。那时我有一个等级,问题是"找到节点X"的所有子孙。在关系数据库中这不是很容易做到的 - 所以我制作了一个帮助表和一些脚本来填充它。让我们看看我是否能记住它...... 注意:这是在我的记忆之后自由而没有经过测试,没有任何保证,我做对了。我的问题也与你的问题有点不同,所以我不确定解决方案是否适用。

create table chain_helper (
    head int,
    tail int,
    chain_length int
)
create index chain_helper_by_head(head);
create index chain_helper_by_tail(tail);

这个表的想法是包含所有可能的链接,其中head和tail是外键。我的情况稍微容易一点,因为我有一个严格的层次结构,不需要循环控制。源表具有id和parent_id字段。以下是我填充表格的方式:

使用简单链接初始化表格:

insert into chain_helper (head, tail, chain_length) 
    select id, parent_id, 1 from source_table;

我继续用长度为2的所有链子填充表格:

insert into chain_helper (head, tail, chain_length)
    select parent.head, child.tail, min(parent.chain_length + 1)
    from chain_helper parent 
    join source_table child on source_table.parent_id=parent.id
    where not exists 
       (select * from chain_helper where head=parent.head and tail=child.tail)
    group by parent.head, child.tail;

(因为我有一个严格的层次结构,我不需要聚合 - 在我的情况下不会有重复)。

重复将插入长度为3的所有链等,并且可以重复所有语句,直到无需插入任何内容。然后找到最大链长是微不足道的:

select max(chain_length) from chain_helper;

这个解决方案并不容易显示链条 - 但在我的情况下这并不是一个要求。我主要在连接中使用chain_helper来捕获层次结构中特定节点的所有子节点 - 即"此子树的总收入":

select sum(source_table.revenue) 
from source_table join chain_helper on chain_helper.tail = source_table.id
where chain_helper.head = parent_of_subtree;

答案 2 :(得分:0)

我不太确定我能得到你想要的东西,a。但是,我会做类似的事情:

WITH RECURSIVE chain (artist_id, path) (
    SELECT id, id::text from artist
    UNION
    SELECT a.id, path || ',' || a.id 
      FROM artist a
      JOIN covers co ON (co.covered_by = a.id)
      JOIN chain ch ON (co.originally_by = ch.artist_id)
)
SELECT * 
  FROM artist a
  JOIN chain c ON c.artist_id = a.id
ORDER BY array_upper(string_to_array(c.path, ',')::int[], 1)
LIMIT 1;

请注意,对于很多艺术家而言,表现并不是那么好,但如果您可以缩小搜索条件范围,那就可以提供帮助。