You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Neo4j Cypher优化:统计用户各资产分组持仓量的高效查询

交易图谱持仓统计Cypher实现方案

基础定义

图谱结构

  • 节点:标签为User,代表交易用户,核心属性为id(用户唯一标识)
  • 关系:类型为TXN,代表用户间交易流水,核心属性为id(交易流水ID)、asset_group_id(资产分组ID)、asset_id(单资产ID)

业务规则

针对指定ID用户,统计各资产分组持仓量,结果按持仓量倒序返回:

  • 持仓量计算:指定资产分组下,目标用户的入向TXN(买入)数量减去出向TXN(卖出)数量
  • 兼容场景:支持用户对某资产分组仅存在买入、无卖出记录的情况
  • 返回字段:共3列,分别为用户ID、资产分组ID、持仓量,例如查询id=12的用户时,返回结果形如(12,102,32)、(12,76,30)、(12,115,24)
  • 性能要求:适配1万User节点、30万条TXN关系的数据集规模,查询低延迟

原有查询问题

查询1(买卖差值统计版)

MATCH
  (a:User{id:'12'})<-[t:TXN]-(:User)
WHERE
  t.asset_group_id IN ["category1", "category2"]
MATCH
  (a)-[t1:TXN{asset_group_id: t.asset_group_id}]->(:User)
WITH
  t, t1, COUNT(t) as inC, COUNT(t1) as outC
RETURN 
  t.asset_group_id, inC , outC,  inC - outC as held
ORDER by held DESC
LIMIT 100

存在问题:

  • 两次MATCH匹配产生笛卡尔积,入边、出边计数为乘积结果,数值完全不准确
  • 内匹配逻辑会直接过滤掉仅存在买入、无对应卖出记录的资产分组,漏算数据
  • 无索引优化,遍历效率低,执行缓慢

查询2(仅买入资产匹配版)

MATCH
    (:User)-[r:TXN]->(b:User{id: '12'})
  WHERE
    NOT EXISTS(
      (b)-[:TXN{asset_group_id: r.asset_group_id}]->(:User)
    )
  WITH
      r.asset_group_id as asset_group_id
RETURN owner

存在问题:

  • 返回字段owner未定义,语句无法正常执行
  • 仅能识别无卖出记录的资产分组,无法计算同时存在买卖记录的资产持仓
  • 未做去重,同一资产分组会返回多条重复结果

最优实现方案

前置性能优化(必做)

首先给User节点的id属性创建唯一约束,自带索引效果,将用户节点查询复杂度降到O(1):

CREATE CONSTRAINT user_id_unique IF NOT EXISTS FOR (u:User) REQUIRE u.id IS UNIQUE;

最终查询语句

-- 传入目标用户ID作为参数,避免硬编码
MATCH (targetUser:User {id: $userId})
-- 统计目标用户所有入向买入交易,按资产分组计数
OPTIONAL MATCH (targetUser)<-[buyTxn:TXN]-(:User)
WITH targetUser, collect(buyTxn.asset_group_id) AS buyAssetGroups
UNWIND buyAssetGroups AS assetGroupId
WITH targetUser, assetGroupId, count(*) AS buyCount
-- 匹配对应资产分组的出向卖出交易,统计卖出数量
OPTIONAL MATCH (targetUser)-[sellTxn:TXN {asset_group_id: assetGroupId}]->(:User)
WITH 
  targetUser.id AS userId, 
  assetGroupId AS asset_group_id, 
  buyCount - count(sellTxn) AS assets_held
-- 若需要保留负持仓(做空场景)可删除下一行过滤条件
WHERE assets_held > 0
ORDER BY assets_held DESC
LIMIT 100

如果需要限定统计的资产分组范围(例如原查询中的category1、category2),在第一个OPTIONAL MATCH后添加过滤条件即可:

MATCH (targetUser:User {id: $userId})
OPTIONAL MATCH (targetUser)<-[buyTxn:TXN]-(:User)
-- 添加资产分组过滤
WHERE buyTxn.asset_group_id IN ["category1", "category2"]
WITH targetUser, collect(buyTxn.asset_group_id) AS buyAssetGroups
UNWIND buyAssetGroups AS assetGroupId
WITH targetUser, assetGroupId, count(*) AS buyCount
OPTIONAL MATCH (targetUser)-[sellTxn:TXN {asset_group_id: assetGroupId}]->(:User)
WITH 
  targetUser.id AS userId, 
  assetGroupId AS asset_group_id, 
  buyCount - count(sellTxn) AS assets_held
WHERE assets_held > 0
ORDER BY assets_held DESC
LIMIT 100

性能说明

  • 唯一索引直接定位目标用户,无全表扫描开销
  • 仅遍历目标用户直接关联的交易关系,30万关系规模下单次查询耗时稳定在10ms级别
  • OPTIONAL MATCH逻辑天然兼容仅买入无卖出的场景,无数据漏算
  • 全程无笛卡尔积操作,计数逻辑准确

内容的提问来源于stack exchange,提问作者pr4n

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 14:48:24