反叛军财务系统
引言
在这里,我将通过一个故事来介绍一个日常问题的解决方案。有多少次,你与其他反叛军一起进行超空间旅行,或者与几位绝地武士执行任务,或者只是与其他人一起做某事时,需要分摊费用?
在这些情况下,经常会出现某人欠钱给另一人,而后者又欠钱给前者的情况。
在这里,我将讲述共和国如何使用Neo4j优雅地解决了这个问题的故事。
故事
很久很久以前,在一个遥远的星系里,有四个人一起旅行,为了拯救共和国。
他们很勇敢,但超空间的通行费和燃料可不是免费的;他们不富裕,也没有一个中心化的财务系统,而是由他们的积蓄构成的分布式系统。
幸运的是,除了原力之外,他们还有一个Neo4j数据库来处理这个分布式财务问题。
所以有勇敢的绝地女孩蕾伊,悔改的芬恩,老派的汉和毛茸茸的外星人丘巴卡。
他们有一个任务:从Neo4j星球获取管理共和国财务的技术,并找到正确使用它的方法。
第一部分很容易,他们带着一个U盘到达了那个星球,获得了数据库,但他们必须找到正确使用它的方法来解决奥加纳的问题。他们决定用他们的返程旅途进行一个小实验。
出发前,他们将自己作为节点添加到数据库中,名字作为属性,并标记为Friend
s。
CREATE (f1:Friend {name:'Rey'})
CREATE (f2:Friend {name:'Finn'})
CREATE (f3:Friend {name:'Han'})
CREATE (f4:Friend {name:'Chewbacca'})
RETURN f1, f2, f3, f4
然后他们需要加满燃料,于是汉为千年隼号加了1000共和国信用点(从现在起我们称之为£)的燃料,这样其他人每人欠他250£(1000 / 4 = 250)。
他们决定将250£表示为Expense
节点,船员与汉之间的关系是一个Credit
节点,其中包含欠债权人的总金额——在本例中是250£。每个Credit
通过:CREDIT
关系链接到债权人,并通过:DEBIT
关系链接到债务人。在Cypher中,以汉为债权人,蕾伊为债务人的表示方式是
MATCH (creditor:Friend {name:'Han'}), (debitor:Friend {name:'Rey'})
MERGE (creditor)-[:CREDIT]->(c:Credit {Creditor: creditor.name, Debitor: debitor.name})-[:DEBIT]->(debitor)
MERGE (c)<-[:EXPENSE]-(exp:Expense {Creditor: creditor.name, Debitor: debitor.name, Amount: 250, Description: 'Fuel Millenium Falcon'})
WITH c
MATCH (c)<-[:EXPENSE]-(exp)
WITH c, SUM(exp.Amount) AS total
SET c.Amount = total;
MATCH (creditor:Friend {name:'Han'}), (debitor:Friend)
WHERE debitor.name IN ['Finn', 'Chewbacca']
MERGE (creditor)-[:CREDIT]->(c:Credit {Creditor: creditor.name, Debitor: debitor.name})-[:DEBIT]->(debitor)
MERGE (c)<-[:EXPENSE]-(exp:Expense {Creditor: creditor.name, Debitor: debitor.name, Amount: 250, Description: 'Fuel Millenium Falcon'})
WITH c
MATCH (c)<-[:EXPENSE]-(exp)
WITH c, SUM(exp.Amount) AS total
SET c.Amount = total;
对于整个团队,则变成
MATCH (n)
RETURN n
加完油后,他们开始了旅程,过了一会儿到了午餐时间,于是他们在服务区停下,丘巴卡在那里买了些夸瑞恩食物,花了80£。
同样地,他们将这笔费用添加到他们的Neo4j数据库中,这样他们每人欠丘巴卡20£
这时有一个有趣的发现:丘巴卡欠汉250£,而汉欠丘巴卡20£。这意味着他们的信用是相互关联的。
MATCH p=(c:Friend {name:'Chewbacca'})-[:CREDIT|DEBIT*2]-(h:Friend {name:'Han'})
RETURN p
他们想描述这种关联,所以他们将同一个人两个信用之间的关系定义为:CHAIN
,这表示他的进账和出账信用之间存在资金流。
在这种情况下,汉的借记和信用之间存在一个:CHAIN
关系,丘巴卡也是一样
MATCH (credit:Credit)<-[:CREDIT]-(creditor:Friend {name:'Han'})<-[:DEBIT]-(debit :Credit)
MERGE (credit)<-[chain:CHAIN]-(debit);
MATCH (credit:Credit)<-[:CREDIT]-(creditor:Friend {name:'Chewbacca'})<-[:DEBIT]-(debit :Credit)
MERGE (credit)<-[chain:CHAIN]-(debit);
(这里是模型清晰图片的链接,遗憾的是我无法链接Dropbox图片 :( )
神奇之处就在这里,因为他们发现可以请求Neo4j查找信用之间是否存在抵消并进行结算
MATCH (creditor:Friend {name:'Chewbacca'})-[:CREDIT]->(credit:Credit),
p = (credit)-[:CHAIN*1..]->(credit)
WITH TAIL(NODES(p)) AS ns, credit
WITH ns,
SIZE(ns) AS Chain_length,
[x IN ns | x.Creditor] AS People_involved,
REDUCE(comp = credit.Amount, n in ns | CASE WHEN comp < n.Amount THEN comp ELSE n.Amount END) AS Compensation
WHERE Compensation > 0
FOREACH (credit in ns| SET credit.Amount = credit.Amount - Compensation)
RETURN Chain_length, People_involved, Compensation;
在这种情况下,丘巴卡和汉之间沿着长度为2的链条发生了20£的抵消。查询MATCH
从丘巴卡开始,因为他最近的信用更新可能产生了一条链条。
到目前为止都很简单,但让我们继续故事,看看这个工具的强大之处。
他们恢复旅程后,遭遇了一场流星雨,护盾受损了。
于是他们去取了一个机械零件来替换损坏的零件,蕾伊支付了1200£(每人300£)。
这使得网络更加复杂,因为他们得到了更多的抵消
MATCH (creditor:Friend)-[:CREDIT]->(credit:Credit),
p = (credit)-[:CHAIN*1..]->(credit)
WITH TAIL(NODES(p)) AS ns, credit
WITH ns,
SIZE(ns) AS Chain_length,
[x IN ns | x.Creditor] AS People_involved,
REDUCE(comp = credit.Amount, n in ns | CASE WHEN comp < n.Amount THEN comp ELSE n.Amount END) AS Compensation
WHERE Compensation > 0 AND
ALL(n in People_involved where
1=size([m in People_involved where m=n]))
RETURN Chain_length, People_involved, Compensation
ORDER BY Compensation DESC, Chain_length DESC
现在棘手的部分来了:我们有不同的链条,补偿金额不同,但有一些共同的信用节点。这使得我们无法像处理丘巴卡和汉那样一次性抵消所有事项。我们必须谨慎地逐条链进行补偿。最好的解决方案是补偿金额最多且链条最长的那一条(当补偿金额相同时,最好优先满足人数最多的链条)。
MATCH (creditor:Friend)-[:CREDIT]->(credit:Credit),
p = (credit)-[:CHAIN*1..]->(credit)
WITH TAIL(NODES(p)) AS ns, credit
WITH ns,
SIZE(ns) AS Chain_length,
[x IN ns | x.Creditor] AS People_involved,
REDUCE(comp = credit.Amount, n in ns | CASE WHEN comp < n.Amount THEN comp ELSE n.Amount END) AS Compensation
WHERE Compensation > 0 AND
ALL(n in People_involved where
1=size([m in People_involved where m=n]))
WITH Chain_length, People_involved, Compensation, ns
ORDER BY Compensation DESC, Chain_length DESC
LIMIT 1
FOREACH (credit in ns| SET credit.Amount = credit.Amount - Compensation)
RETURN Chain_length, People_involved, Compensation
继续故事,他们拿到护盾零件后,去了机械师那里,芬恩支付了修理费600£(每人150£),他们将这笔费用添加到系统中。
与此同时,丘巴卡和汉决定去酒馆喝一杯班萨奶酒和一杯绝地啤酒,丘巴卡支付了50£(每人25£)。
数据库再次提供了帮助,找到了一些抵消来平账。
MATCH (creditor:Friend)-[:CREDIT]->(credit:Credit),
p = (credit)-[:CHAIN*1..]->(credit)
WITH TAIL(NODES(p)) AS ns, credit
WITH ns,
SIZE(ns) AS Chain_length,
[x IN ns | x.Creditor] AS People_involved,
REDUCE(comp = credit.Amount, n in ns | CASE WHEN comp < n.Amount THEN comp ELSE n.Amount END) AS Compensation
WHERE Compensation > 0 AND
ALL(n in People_involved where
1=size([m in People_involved where m=n]))
WITH Chain_length, People_involved, Compensation, ns
ORDER BY Compensation DESC, Chain_length DESC
LIMIT 1
FOREACH (credit in ns| SET credit.Amount = credit.Amount - Compensation)
RETURN Chain_length, People_involved, Compensation
并且多次运行查询后,他们达到了所有款项都被抵消的地步。
MATCH (creditor:Friend)-[:CREDIT]->(credit:Credit),
p = (credit)-[:CHAIN*1..]->(credit)
WITH TAIL(NODES(p)) AS ns, credit
WITH ns,
SIZE(ns) AS Chain_length,
[x IN ns | x.Creditor] AS People_involved,
REDUCE(comp = credit.Amount, n in ns | CASE WHEN comp < n.Amount THEN comp ELSE n.Amount END) AS Compensation
WHERE Compensation > 0 AND
ALL(n in People_involved where
1=size[m in People_involved where m=n]))
WITH Chain_length, People_involved, Compensation, ns
ORDER BY Compensation DESC, Chain_length DESC
LIMIT 1
FOREACH (credit in ns| SET credit.Amount = credit.Amount - Compensation)
RETURN Chain_length, People_involved, Compensation
MATCH (creditor:Friend)-[:CREDIT]->(credit:Credit),
p = (credit)-[:CHAIN*1..]->(credit)
WITH TAIL(NODES(p)) AS ns, credit
WITH ns,
SIZE(ns) AS Chain_length,
[x IN ns | x.Creditor] AS People_involved,
REDUCE(comp = credit.Amount, n in ns | CASE WHEN comp < n.Amount THEN comp ELSE n.Amount END) AS Compensation
WHERE Compensation > 0 AND
ALL(n in People_involved where
1=size([m in People_involved where m=n]))
WITH Chain_length, People_involved, Compensation, ns
ORDER BY Compensation DESC, Chain_length DESC
LIMIT 1
FOREACH (credit in ns| SET credit.Amount = credit.Amount - Compensation)
RETURN Chain_length, People_involved, Compensation
他们知道这项技术是新革命的开端。这确实可以极大地简化反叛军的整个财务系统,并节省大量金钱和时间。
他们飞回自己的星球,向奥加纳将军展示了他们的发现。
故事的其余部分广为人知,但直到此刻,没有人解释过在这个如此分散的共和国里,贫穷的反叛军是如何管理他们的资金的。
未来发展
在我的工作中,我介绍了一种抵消链条的方法,这种方法有点机械化,但我相信有更好的方法。首先,描述一种查询来抵消所有不相连的子网络将非常有趣,此外,如果能找到一种一次性抵消所有事项的方法就太好了。
结论
这是一个有趣的故事,用来解释图数据库真正可以发挥最大作用的一个用例。它可以描述这样一个内在相互连接的网络,并对数据执行非常有描述性的查询。我的工作只是解决寻找共享信用子网络问题的一种方法,并且可以作为开发分布式经济系统的一个想法。我真的要感谢Neo4j,因为它描述连接数据的方法开启了新的思维方式,并使人们能够更好地洞察信息的真实价值。
此页面有帮助吗?