Neo4j学习

Neo4j介绍

个人学习笔记,不足之处望海涵和指出。
官网:https://neo4j.com/
官网文档:https://neo4j.com/docs/cypher-manual/current/introduction/

为什么要使用Neo4j

图数据库主要用于存储更多的连接数据(因为图结构相比其他数据结构而言,能保存更多的数据间的关系)。
如果我们使用 RDBMS 数据库来存储更多连接的数据,那么它们不能提供用于遍历大量数据的适当性能。 在这些情况下,Graph Database 提高了应用程序性能。

Neo4j特性

  • SQL就像简单的查询语言Neo4j CQL
  • 它遵循属性图数据模型
  • 它通过使用Apache Lucence支持索引
  • 它支持UNIQUE约束
  • 它包含一个用于执行CQL命令的UI:Neo4j数据浏览器
  • 它支持完整的ACID(原子性,一致性,隔离性和持久性)规则
  • 它采用原生图形库与本地GPE(图形处理引擎)
  • 它支持查询的数据导出到JSON和XLS格式
  • 它提供了REST API,可以被任何编程语言(如Java,Spring,Scala等)访问
  • 它提供了可以通过任何UI MVC框架(如Node JS)访问的Java脚本
  • 它支持两种Java API:Cypher API和Native Java API来开发Java应用程序

Neo4j优点

  1. 它很容易表示连接的数据
  2. 检索/遍历/导航更多的连接数据是非常容易和快速的
  3. 它非常容易地表示半结构化数据
  4. Neo4j CQL查询语言命令是人性化的可读格式,非常容易学习
  5. 使用简单而强大的数据模型
  6. 它不需要复杂的连接来检索连接的相关数据,因为它很容易检索它的相邻节点或关系细节没有连接或索引

Neo4j - 数据模型

Neo4j属性图数据模型:Neo4j图数据库遵循属性图模型来存储和管理其数据。
属性图模型规则:

  • 表示节点,关系和属性中的数据
  • 节点和关系都包含属性
  • 关系连接节点
  • 属性是键值对
  • 节点用圆圈表示,关系用方向键表示。
  • 关系具有方向:单向和双向。
  • 每个关系包含“开始节点”或“从节点”和“到节点”或“结束节点”

在Neo4j中,关系是有方向性的。如果我们尝试创建没有方向的关系,那么Neo4j会抛出一个错误消息。
Neo4j图数据库将其所有数据存储在节点和关系中。我们不需要任何额外的RDBMS数据库或无SQL数据库来存储Neo4j数据库数据。它以图形的形式存储其数据的本机格式。
Neo4j使用本机GPE(图形处理引擎)引擎来使用它的本机图存储格式。
图形数据库数据模型的主要构建块是:

  • 节点
  • 关系
  • 属性

Neo4j图数据库主要有以下构建块

  • 节点
  • 属性
  • 关系
  • 标签
  • 数据浏览器

节点

节点用来表示一个实体记录,在Neo4j中节点可以包含多个属性和多个标签

  • 节点是主要元素
  • 节点通过关系连接到其他节点
  • 节点可以具有一个或多个属性(存储为键值对的属性)
  • 节点有一个或多个标签,用于描述其在图的作用

属性

属性是用于描述图节点和关系的键值对,其中key是一个字符串,值可以通过使用任何Neo4j数据类型表示

  • 属性是命名值,其中键是字符串
  • 属性可以被索引和约束
  • 可以从多个属性创建复合索引

关系

用来连接两个节点,关系也称为图论的边,其始端和末端必须是节点。

标签

Label将一个公共名称与一组节点或关系相关联。
节点可以包含零或多个标签
关系必须包含一个标签

数据浏览器

一旦安装Neo4j,就可以访问Neo4jj数据浏览器,url:host/browser
初始username:neo4j
初始password:neo4j

文件介绍

  1. bin目录:包括各个启动脚本其中:neo4j.bat是window下的启动脚本cypher-shell.bat 是CQL的命令窗口
  2. certificates目录:身份认证
  3. conf目录:配置文件
  4. data目录:数据文件
  5. import目录:导入文件(csv后缀的文件)
  6. lib目录:jar包
  7. logs:日志

启动Neo4j

neo4j.bat console
1.将Neo4j作为控制台应用程序运行:<NEO4J_HOME>\bin\neo4j console
console:直接启动Neo4j服务
2.将Neo4j作为服务安装:<NEO4J_HOME>\bin\neo4j install-service
install-service | uninstall-service | update-service :安装/卸载/更新Neo4j服务
start/stop/restar/status:启动/停止/重启/状态
默认的host是bolt://localhost:7687,默认的用户是neo4j,默认的密码是:neo4j

Neo4j - CQL简介

CQL代表Cypher查询语言。 像Oracle数据库具有查询语言SQL,Neo4j具有CQL作为查询语言。
Neo4j CQL特点

  • 它是Neo4j图形数据库的查询语言。
  • 它是一种声明性模式匹配语言
  • 它遵循SQL语法。
  • 它的语法是非常简单且人性化、可读的格式。

CQL使用

CQL命令用法
CREATE创建节点、关系和属性
MATCH检索节点、关系和属性数据
RETURN返回查询数据
WHERE提供条件过滤检索数据
DELETE删除节点和关系
REMOVE删除节点和关系的属性
ORDER BY排序检索数据
SET添加或更新标签

创建数据

创建的所有节点和关系都自带id
语法结构

CREATE ([result-name]:< label-name > [{key1:value1,key2:value2…}])

动态Cypher
private Boolean insertDicBatch(String dictTypeKey,Long dictTypeId,List<JSONObject> specialDictBodys){
    if (StringUtils.isNullOrEmpty(dictTypeKey) || null == dictTypeId || null == specialDictBodys || specialDictBodys.isEmpty()){
        return false;
    }
    String cypher = "match(dictType:DictType) where id(dictType) = $dictTypeId " +
                    "unwind $specialDictBodys as item " +
                    "merge(dictType) -[:DictTypeRelationship]-> (dict:" + dictTypeKey + "{code:item.code}) " +
                    "set dict = item";
    Map<String, Object> params = new HashMap<>();
    params.put("dictTypeId", dictTypeId);
    params.put("specialDictBodys", specialDictBodys);
    session.query(cypher , params);
    return true;
}
    
创建节点,标签是Stud
ent,属性是name和age
CREATE (:Student {name: '张三', age: 18})
Cypher 会返回更改的数量,在本例中添加 1 个节点、1 个标签和 2 个属性。
Created Nodes: 1
Added Labels: 1
Set Properties: 2
Rows: 0

创建多个节点,返回某个新增节点的数据
create(n:Student{name:"王五",age:15}),(m:School{name:"幼儿园"})return n,m
第二种写法
create(n:Student{name:"王五",age:15})
create(m:School{name:"幼儿园"})
return n , m

创建多个标签的节点
create (n:Car:Plane{name:"飞机车",power:100,hight:100,rate:1000})

创建两个节点和它们之间的关系
create (a:Person {name:'张三',age:30})-[b:老婆{roles:['admin']}]->(c:Person {name:'王五',age:33}) return a,c

创建三个节点和它们之间的关系
create (a:Person {name:'张三',age:30})-[b:老婆{roles:['admin']}]->(c:Person {name:'王五',age:33})
create(m:School{name:"奇葩幼儿园"})-[:幼儿园]->(a) return a,c,m

将查询的数据和现有的数据做关联
MATCH (n:Person{name:'张三'})
create(n)-[:儿子]->(m:Person{name:'老九'})
return n,m
第二种写法
MATCH (n:Person{name:'张三'})
create(m:Person{name:'老九'})
create(n)-[:儿子]->(m)
return n,m

查询是否存在该数据,不存在则创建新的节点和关系
merge (n:Person{name:'张三'})-[:爷爷]->(m:Person{name:'老六'}) return n,m
第二种写法
match (n:Person{name:'老头'}),(m:Person{name:'老婆'}) 
merge (n)-[r:wife]->(m)
return n,r,m
一个merge只能对应一条查询语句
例如:
match (n) where id(n) = 19
match (m) where id(m) = 18
match (k) where id(k) = 2
match (c) where id(c) = 3
merge (n)-[:`朋友`]->(m)
merge (n)-[:朋友]->(k)
merge (k)-[:老豆]->(c)
return n,m,k,c
错误写法:
match (n) where id(n) = 19
match (m) where id(m) = 18
match (k) where id(k) = 2
match (c) where id(c) = 3
merge (n)-[:`朋友`]->(m),(n)-[:朋友]->(k),(k)-[:老豆]->(c)
return n,m,k,c

查询所有的人和其对应的城市,城市不存在则创建
MATCH (person:Person)
MERGE (city:City { name: person.bornIn })
RETURN person.name, person.bornIn, city

on create,是当merge不创建时才执行,查询张三是否存在,存在则设置其年龄为20
merge (n:Person{name:'张三'}) on create set n.age = 20 return n

查询数据

查询关系深度为2的节点
MATCH p = ()-[*2]->() RETURN nodes(p)
查询关系深度3~5的节点
MATCH p = ()-[*3..5]->() RETURN nodes(p)
查询深度为2以上的节点的节点
MATCH p = ()-[*2..]->() RETURN nodes(p)

查询前25个节点
MATCH (n) RETURN  n LIMIT 25

分页
查询结果集跳过前25个数据,取后面25条数据
MATCH (n) RETURN  n SKIP 25 LIMIT 25

去重
查询是朋友的节点,并且去重
MATCH ()-[:朋友]->(m) return distinct m

排序
match (p:Person) return p order by p.age asc,p.sex desc

查询标签是Student的前25个节点
MATCH (n:Student) RETURN n LIMIT 25

查询属性name是张三的节点
MATCH (n{name:'张三'}) RETURN n

查询标签是Student并且name是王五的节点
MATCH (n:Student{name:'王五'}) RETURN n

查询学校和人有关系的所有节点,以下添加属性即可筛选需要的数据
MATCH (n:Person)<-[]-(m:School) RETURN n,m

查询人与人有关系的节点,关系的方向可以不写
MATCH (n:Person)-[]-(m:Person) return n,m

添加条件查询
布尔表达式:ANDORXOR(异或)和NOT
比较运算符:1.= 2.<> 3.< 4.> 5.<= 6.>= 7.IS NULL 8.IS NOT NULL
算数运算:+, -, *, /, %, ^(次方)
查询结果集name是张三的数据
MATCH (n) where n.name = '张三' return n

in运算符
查询年龄是2061的结果集
match (n) where n.age in [20,61] return n
当in放在前面时,相当于=,不能用list集合
match (n) where 20 in n.age return n

正则表达式=~,后面写规则
match (n) where n.name =~ '张.*' return n

使用CQL自动生成的Id进行查询
match (n) where id(n) in [1,2,3] return n

CQL分析
1.n结果集为:有朋友的节点
2.m的结果集:是朋友的节点
加上not (m)-[:老豆]->()条件后(没有老豆的结果集)
那么m的结果集为:是朋友并且没有老豆的节点
n的结果集为:有朋友并且该朋友没有老豆的节点
match (n:Person)-[r:朋友]->(m)
where not (m)-[:老豆]->()
return m

collect函数使用,将结果集变成[]的形式,count用来计数会跳过空值数据
match (:Person{name:'赵六'})-[:`朋友`]->(m)
match (:Person{name:'王五'})-[:`朋友`]->(r)
return collect(m.name) as zhaoliuFriends,count(*),r.name as wangwuFriends
结果集
| zhaoliuFriends |count(*)| wangwuFriends |
| ["张三", "王五"]|	  2   | 	"张三"    |
不使用collect函数
| zhaoliuFriends |count(*)|wangwuFriends |
|	 "张三" 		 | 	 1	  |		"张三" 	 |
| 	 "王五"		 |	 1	  |		"张三"	 |

union并集,返回的列必须以相同的方式命名。
match (:Person{name:'赵六'})-[:`朋友`]->(n)
return n.name as name
union
match (:Person{name:'王五'})-[:`朋友`]->(m)
return m.name as name

在关系上添加多个标签的方式
查询是朋友或者是老豆的节点
match (n)-[:`朋友`|`老豆`]->(m)return m

contains 模糊查询,包含市的机构
match(n:Org) 
where n.name contains '市'
return n

索引的使用

  1. 在图数据库中使用索引的主要原因是为了找到图遍历的起点。一旦找到该起点,遍历就依赖于图内结构来实现高性能。
  2. 可以随时添加索引。
  3. 如果数据库中已有数据,则创建索引需要一些时间。
  4. 可以使用索引提示指定在特定查询中使用哪个索引, 在大多数情况下,查询数据时不需要指定索引,因为将自动使用适当的索引。
创建索引
为Person标签的属性name创建索引,这时Person标签具有name属性的所有节点将添加到该索引中
写法一:
create index index_Person_name for (person:Person) on (person.name)
写法二:
create index on :Person(name)

复合索引
Person标签下具有name和age属性的节点添加到索引中
写法一,可以指定索引名称
create index index_Person_name_age for (person:Person) on (person.name,person.age)
写法二,系统生成索引名称
create index on :Person(name,age)

查询索引
SHOW INDEXES YIELD name, labelsOrTypes, properties, type

唯一性约束

添加唯一约束将隐式添加该属性的索引。如果删除了约束,但仍然需要索引,则必须显式创建索引。

单字段约束,会创建索引
CREATE CONSTRAINT constraint_Plane_name FOR (p:Plane) REQUIRE p.name IS UNIQUE
CREATE CONSTRAINT ON (p:Plane) ASSERT p.name  IS UNIQUE
复合约束,会创建索引
CREATE CONSTRAINT ON (p:Plane) ASSERT (p.name,p.age)  IS UNIQUE
约束查询
SHOW CONSTRAINTS YIELD id, name, type, entityType, labelsOrTypes, properties, ownedIndexId
删除约束,会自动删除对应的索引
DROP CONSTRAINT ON (p:Plane) ASSERT p.name IS UNIQUE

修改/添加属性/标签

修改属性
match (n) where id(n) = 38 set n.name = 'new1' set n.age = 1 set n:Person set n:Student return n

属性替换
CREATE (a:Person {name: 'Jane', age: 20})
WITH a
MATCH (p:Person {name: 'Jane'})
SET p = {name: 'Ellen', livesIn: 'London'}
RETURN p.name, p.age, p.livesIn
结果
Ellen null London
说明:{name: 'Jane', age: 20}整个被替换成{name: 'Ellen', livesIn: 'London'}所以age为null

删除节点数据

全部删除,先删除所有关系,再删除所有节点
match (n)-[r]->(m) delete r
match (n) delete n

删除属性/标签

删除属性语法规则
MATCH (N) WHERE ID(N)=17 remove N.age,N.name,N.sex return N
删除标签语法规则
MATCH (n:Student{id:100}) remove n:Person,n:Student RETURN n

导入csv文件

Neo4j配置文件指定了导入文件的文件夹,需要导入的csv文件需要放在该目录中才能被导入。
默认配置文件夹是import文件夹,可自定义文件夹。

dbms.directories.import=import

csv文件格式要求:utf8编码,可被导入数据大概一千万条
参数

参数名称用途
using periodic commit [n]批量提交可以设置每满 n 条提交一次,防止内存溢出。默认值是1000
with headers读取首行读取文件的第一行作为参数名不使用此参数,要用 line[0]、line[1] 这样的方式表示使用此参数,可以使用 line.name 这样的表示方式
as line重命名为每行数据重命名
fieldterminator ‘,’自定义字段定界符csv中的分隔符基本都是逗号或分号。最常用的是逗号,

自定义csv文件,放在import目录下

name,age
张三,18
李四,21
王五,16

不使用 with headers 的写法:

load csv
from 'file:///export.csv' as person
fieldterminator ','
create (
	p:Person{
    	name: person[0],
    	age: person[1]
    }
)

使用 with headers 的写法:

load csv with headers
from 'file:///export.csv' as person
fieldterminator ','
create (
	p:Person{
    	name: person.name,
    	age: person.age
    }
)

WITH

WITH 用于向后面的语句传递指定结果,并可以改变结果集中实体的形式和数量。注意,WITH 会影响查询结果集里的变量,WITH 语句外的变量不会传递到后续查询中,主要作用:
1、通过使用oder by 和limit,with可以限制传入下一个match子查询语句的实体数目。
2、对聚合值过滤。
如何改变查询结果集的变量?
示例如下:
在这里插入图片描述
根据b找出有关系的节点

match ({name:'b'})--(b)
return b

此时得到a,c两个节点,将其向下个语句流通数据时:

match ({name:'b'})--(b)  此处得到a,c节点
with b
match ({name:'c'})--(c)  该语句单独查询得到b。当上面的a,c流下来时相当于做遍历处理,a数据和该语句数据匹配得到b,c也是得到b数据
with b.name as bname,c.name  as cname
return *

最终结果
在这里插入图片描述

MATCH ()-[:朋友]->(m)
WITH m.name as name,count(*) as cnt(后续语句只能读取到name和cnt)
return name,cnt

UNWIND的使用

UNWIND将一个列表展开为一个行的序列(行转列)。

unwind[1,2,3] as x return x

在这里插入图片描述

CALL {} (subquery)

  • 以RETURN语句结尾的子查询称为返回子查询,而没有这种返回语句的子查询则称为单元子查询。
  • 为每个传入的输入行计算子查询。返回子查询的每个输出行都与输入行组合,以生成子查询的结果。这意味着返回的子查询将影响行数。如果子查询未返回任何行,则子查询之后将没有可用的行。
  • 另一方面,调用单元子查询是因为它们的副作用,而不是它们的结果,因此不会影响单元查询的结果。

子查询与单元查询交互的方式存在限制:

  1. 如果显式导入了单元查询中的变量,则子查询只能引用这些变量。
  2. 子查询不能返回与单元查询中的变量同名的变量。
  3. 从子查询返回的所有变量之后都可以在单元查询中使用。
    子查询
    为每个传入的输入行计算子查询。返回子查询的每个输出行都与输入行组合,子查询没有return值则with相关数据就没有该数据,call统计时能统计0
UNWIND [0, 1, 2] AS x
CALL {
  RETURN 'hello' AS innerReturn
}
RETURN innerReturn

在这里插入图片描述
单元子查询
此示例查询为每个现有人员创建五个克隆。由于子查询是单位子查询,因此它不会更改封闭查询的行数。

MATCH (p:Person)
CALL {
  WITH p
  UNWIND range (1, 5) AS i
  CREATE (:Person {name: p.name})
}
RETURN count(*)

OPTIONAL

查找数据不存在时返回null

match (n:Person{name:'Maria'})-->(m) return m

在这里插入图片描述

optional match (n:Person{name:'Maria'})-->(m) return m

在这里插入图片描述

CASE

对查询的结果集添加一个字段
计算表达式,并按顺序与WHEN子句进行比较,直到找到匹配项。如果未找到匹配项,则返回ELSE子句中的表达式。如果没有ELSE事例并且没有找到匹配项,则返回null。
在这里插入图片描述

MATCH (n)//查询所有节点
RETURN n.name,//返回节点name属性
CASE n.age
  WHEN n.age IS NULL THEN -1//age为null则age_10_years_ago为-1
  ELSE n.age - 10//age_10_years_ago的值为age-10
END AS age_10_years_ago//结果集添加字段age_10_years_ago

上述查询结果
在这里插入图片描述
Daniel的age_10_years_ago为null说明:n.age IS NULL其表示值为null,而Daniel没有age属性但是不能说明其age属性是null,因此其走的是ELSE分支,在ELSE分支中n.age - 10表示为空属性 - 10无法成立则返回null。

标签表达式:

在这里插入图片描述
在这里插入图片描述

查询不包含AB标签或者包含C标签的节点
MATCH (n:(!A&!B)|C)
RETURN n.name AS name

查询包含A或B标签的节点
MATCH (n:A|B)
RETURN n.name AS name

查询有标签的节点
MATCH (n:%)
RETURN n.name AS name

查询没有标签的节点
MATCH (n:!%)
RETURN n.name AS name

返回结果为Boolean值,拥有AB标签的节点返回true否则返回false
MATCH (n)
RETURN n:A&B

关系表达式

在这里插入图片描述

查询所有的关系
MATCH ()-[r]->()
RETURN r.name as name

查询R1类型的关系
MATCH ()-[r:R1]->()
RETURN r.name AS name

查询R1或R2类型的关系
1.写法一
MATCH ()-[r:R1|R2]->()
RETURN r.name AS name
2.写法二
MATCH (n)-[r]->(m)
WHERE r:R1|R2
RETURN r.name AS name

查询非R1的关系
MATCH ()-[r:!R1]->()
RETURN r.name AS name

查询非R1和R2或者R3的关系
MATCH ()-[r:(!R1&!R2)|R3]->()
RETURN r.name as name

返回Boolean值,包含R1或R2的关系返回true
MATCH (n)-[r]->(m)
RETURN r:R1|R2 AS result

EXISTS

EXISTS子查询可用于确定指定的模式是否在数据中至少存在一次。
在这里插入图片描述

MATCH (person:Person)//查询所有标签是Person的节点
WHERE EXISTS {//获取以下结果集为true的交集
  MATCH (person)-[:HAS_DOG]->(dog:Dog)//拥有狗的人返回true
  WHERE EXISTS {//获取以下结果集为true的交集
    MATCH (dog)-[:HAS_TOY]->(toy:Toy)//拥有玩具的狗返回true
    WHERE toy.name = 'Banana'//玩具名称为Banana
  }
}
RETURN person.name AS name
上述结果为:"Peter"

以下是返回Boolean值,在return时使用,存在则返回true否则返回false

MATCH (person:Person)
RETURN person.name AS name, EXISTS {
  MATCH (person)-[:HAS_DOG]->(:Dog)
} AS hasDog

上述结果:
在这里插入图片描述
来自外部范围的变量对于整个子查询都是可见的,即使在使用WITH子句时也是如此。这意味着不允许隐藏这些变量。当用同一变量定义内部范围内新引入的变量时,外部范围变量将被隐藏。在下面的示例中,WITH子句引入了一个新变量。注意,主查询中引用的外部范围变量person在WITH子句之后仍然可用。

MATCH (person:Person)
WHERE EXISTS {
    WITH "Ozzy" AS dogName
    MATCH (person)-[:HAS_DOG]->(d:Dog)
    WHERE d.name = dogName
}
RETURN person.name AS name
上述结果集:"Peter"

COUNT

COUNT子查询表达式可用于计算子查询返回的行数。
允许任何非写入查询。COUNT子查询与常规查询的不同之处在于,可以省略最后的RETURN子句,因为子查询中定义的任何变量在表达式之外都不可用,即使使用了最后的RET子句。一个例外是,对于DISTINCT UNION子句,RETURN子句仍然是强制的。
值得注意的是,在COUNT仅由模式和可选where子句组成的情况下,可以在子查询中省略MATCH关键字。
在这里插入图片描述

MATCH (person:Person)//查询所有Person标签节点
RETURN
    person.name AS name,//返回name属性
    COUNT {//统计养猫狗的数量
        MATCH (person)-[:HAS_DOG]->(dog:Dog)
        RETURN dog.name AS petName
        UNION
        MATCH (person)-[:HAS_CAT]->(cat:Cat)
        RETURN cat.name AS petName
    } AS numPets

上述结果:
在这里插入图片描述
来自外部范围的变量对于整个子查询都是可见的,即使在使用WITH子句时也是如此。这意味着不允许隐藏这些变量。当用同一变量定义内部范围内新引入的变量时,外部范围变量将被隐藏。在下面的示例中,WITH子句引入了一个新变量。注意,主查询中引用的外部范围变量person在WITH子句之后仍然可用。
在这里插入图片描述

ALL()

all()表示所有的元素都满足条件,返回值Boolean

MATCH p = (a)-[*1..3]->(b)
WHERE
  a.name = 'Alice'
  AND b.name = 'Daniel'
  AND all(x IN nodes(p) WHERE x.age > 30) x用来标识结果集,年龄大于30做筛选
RETURN p

时间函数

以下图片地址:https://neo4j.com/docs/cypher-manual/current/syntax/temporal/
各个时间函数支持的数据类型
在这里插入图片描述
各个时间函数支持的属性
在这里插入图片描述
使用示例
示例1
在这里插入图片描述
示例2
在这里插入图片描述

Durations

指定持续时间
持续时间表示时间量,捕捉两个瞬间之间的时间差,可以是负值。
持续时间的规范前缀为P,可以使用基于单位的形式或基于日期和时间的形式:
基于单位的形式:P[nY][nM][nW][nD][T[nH][nM][nS]]
方括号([])表示可选组件(零值的组件可以省略)。
n表示64位整数范围内的数值,可以包含小数。
每个组件必须以表示该单元的组件标识符作为后缀。
基于单位的表格使用M作为月和分钟的后缀。因此,时间部分必须始终以T开头,即使没有给出日期部分的组成部分。
持续时间的最大总长度由64位整数中可保持的秒数限制。
基于日期和时间的形式:P<Date>T<time>
与基于单元的表单不同,此表单要求每个组件都在有效LocalDateTime的范围内。
下表列出了基于单元的表单的组件标识符:
在这里插入图片描述
示例解析:
以下表示经过14天16小时12分
在这里插入图片描述
以下表示经过5个月又1.5天,结果为5个月1天又12小时
在这里插入图片描述
以下表示0.75分钟,结果为45秒
在这里插入图片描述
以下表示2.5周,结果为17天又12小时
在这里插入图片描述
持续时间”可以有几个组件,每个组件分为“月”、“天”和“秒
在这里插入图片描述
请注意:
Cypher在处理闰秒时使用UTC-SLS。
一天中不一定有24小时;当切换到夏令时时,一天可以有23或25个小时。
一个月的天数并不总是相同的。
由于闰年,一年中的天数并不总是相同的。
也可以访问由组中最大(最重要)组分界定的组分组中较小(不太重要)的组分:在这里插入图片描述
示例结果解析(根据上方两个图表进行分析):
示例为:经过1年5个月111天42分钟
结果:
d.years = 1,经过了一年所以years为1
d.quarters = 5,经过一年5个月则为17个月,根据上方duration.quarters的解释一年有4个季度,3个月为1个季度则17%3=5所以结果为5表示经过了5个季度
d.quartersOfYear = 1,组中不占全年的季度数为5个月/3个月=1余2个季度。
d.months = 17,表示经过了17个月,定义时间为1年5个月已经表示17个月,111天无法表示经过多少个月(每个月份天数不同)所以未参与计算
d.monthsOfYear = 5,组中不构成一整年的月份数。
在这里插入图片描述
示例2:经过5个月25天1小时
结果:
d.weeks = 3,从上表得知7天为一周,25%7 = 3
d.days = 25,直接获取days
d.daysOfWeek,组中不占一周的天数25/7=3余4,,不占一周的天数为4。
在这里插入图片描述

between

获取两个时间中间的差值
在这里插入图片描述

inDays

获取两个时间中间的差值的天数
在这里插入图片描述

truncate

获取指定年、月、周第一天的日期
以下表示获取今年第一天的日期
在这里插入图片描述
以下表示获取指定时间‘2019-10-01’当前周周四的日期
在这里插入图片描述
以下表示获取当前时间加上两个月后的第一天减去一天
在这里插入图片描述

distance计算两点之间距离

地球坐标的距离

WITH
  point({latitude:toFloat('13.43'), longitude:toFloat('56.21')}) AS p1,
  point({latitude:toFloat('13.10'), longitude:toFloat('56.41')}) AS p2
RETURN toInteger(distance(p1, p2)/1000) AS km

二维坐标

WITH
  point({x: 3, y: 0}) AS p2d,
  point({x: 0, y: 4, z: 1}) AS p3d
RETURN
  distance(p2d, p3d) AS bad,
  distance(p2d, point({x: p3d.x, y: p3d.y})) AS good

列表规则

自定义列表
RETURN [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] AS list
RETURN range(0, 10)//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
按照下标取值
RETURN range(0, 10)[3]//3,下标从0开始,第三数对应三
RETURN range(0, 10)[-3]//8,负数表示从列表后面往前数三个数
按照范围下标获取值
RETURN range(0, 10)[0..3]//[0,1,2],该范围包前不包后,0的下标对应值是0,3对应下标对应值为3,取值为0~3不包括3
RETURN range(0, 10)[0..-5]//[0,1,2,3,4,5],0下标值为0,-5下标值为6,取值为[0,6)
RETURN range(0, 10)[-5..]//[6,7,8,9,10]

项目配置

依赖

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-neo4j</artifactId>
		</dependency>

实体类

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
@NodeEntity(label = "FundAccount")
public class FundAccount {
	@Id
    @GeneratedValue
	private Long id;
}

repository

public interface FundAccountRepository extends Neo4jRepository<FundAccount> {
//两张传参占位符形式
	    @Query("match(a:User)" +
            "where a.account =$viewPwd.account " +
            "set a.password = $viewPwd.newPwd")
    void changePasswordByViewPwd(@Param("viewPwd") ViewPwd viewPwd);
	    @Query("match(a:User)" +
            "where a.account =$0.account " +
            "set a.password = $0.newPwd ")
    void changePasswordByViewPwd(ViewPwd viewPwd);
}

多数据源配置

目录结构参考

数据源指向到Repository
在这里插入图片描述

配置文件

spring:
  profiles: test
  data:
    #第一数据源--主数据库
    neo4j:
      uri: bolt://192.168.200.11:8888
      username: neo4j
      password: 123
    #第二数据源--审计库
    secondary:
      uri: bolt://192.168.200.11:9999
      username: neo4j
      password: 123

第一个数据源

import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import static com.Lyy.cloudGateway.config.dataSouce.PrimaryDataSourceConfig.*;
/**
 * @author chenkepeng
 *
 */
@Configuration
//开启事务
@EnableTransactionManagement
//开启实体类扫描
//@EntityScan(basePackages = {"com.Lyy.phoneDataAnalysis.basicService.persistence.entity","com.Lyy.phoneDataAnalysis.entity"})
//开启Neo4jRepositories的支持
@EnableNeo4jRepositories(
		sessionFactoryRef = PRIMARY_SESSION_FACTORY,
		sessionBeanName = PRIMARY_SESSION_BEAN_NAME,
//		basePackages = {"com.Lyy.phoneDataAnalysis.basicService.persistence.repository","com.Lyy.phoneDataAnalysis.repo","com.Lyy.phoneDataAnalysis.basicService.config.audit.persistence"},
		basePackages = {"com.Lyy.cloudGateway.persistence.primary"},
		transactionManagerRef = PRIMARY_TRANSACTION_MANAGER
)
//@EnableConfigurationProperties(Neo4jProperties.class)
public class PrimaryDataSourceConfig {
	public static final String PRIMARY_SESSION_FACTORY = "sessionFactoryForPrimaryDataSource";
	public static final String PRIMARY_TRANSACTION_MANAGER = "transactionManagerForPrimaryDataSource";
	public static final String PRIMARY_SESSION_BEAN_NAME = "sessionBeanNameForPrimaryDataSource";
	
	@Primary
	@Bean
	@ConfigurationProperties("spring.data.neo4j")
	public Neo4jProperties neo4jPrimaryDataSourceProperties() {
		return new Neo4jProperties();
	}
	
	@Primary
	@Bean
	public org.neo4j.ogm.config.Configuration primaryDataSourceOgmConfiguration() {
		return neo4jPrimaryDataSourceProperties().createConfiguration();
	}

	@Primary
	@Bean(name = PRIMARY_SESSION_FACTORY)
	public SessionFactory sessionFactory() {
//		return new SessionFactory(primaryDataSourceOgmConfiguration(), "com.Lyy.phoneDataAnalysis.basicService.persistence.repository","com.Lyy.phoneDataAnalysis.repo","com.Lyy.phoneDataAnalysis.basicService.config.audit.persistence");
		return new SessionFactory(primaryDataSourceOgmConfiguration(), 
				"com.Lyy.cloudGateway.persistence.primary" );
	}
	
	@Primary
	@Bean(name = PRIMARY_TRANSACTION_MANAGER)
	public Neo4jTransactionManager neo4jTransactionManager() {
		return new Neo4jTransactionManager(sessionFactory());
	}
}

第二个数据源

import org.neo4j.ogm.session.SessionFactory;
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(
		sessionFactoryRef = SecondaryNeo4jDataSourceConfig.SECONDARY_SESSION_FACTORY,
		basePackages = SecondaryNeo4jDataSourceConfig.SECONDARY_BASE_PACKAGE,
		transactionManagerRef = SecondaryNeo4jDataSourceConfig.SECONDARY_TRANSACTION_MANAGER,
		sessionBeanName = SecondaryNeo4jDataSourceConfig.SECONDARY_SESSION_BEAN_NAME
)
public class SecondaryNeo4jDataSourceConfig {
	public static final String SECONDARY_SESSION_FACTORY = "sessionFactoryForSecondaryDataSource";
	public static final String SECONDARY_SESSION_BEAN_NAME = "sessionBeanNameForSecondaryDataSource";
	public static final String SECONDARY_TRANSACTION_MANAGER = "transactionManagerForSecondaryDataSource";
	static final String SECONDARY_BASE_PACKAGE = "com.Lyy.cloudGateway.persistence.secondary";
	
	@Bean
	@ConfigurationProperties("spring.data.secondary")
	public Neo4jProperties neo4jSecondaryDataSourceProperties() {
		return new Neo4jProperties();
	}
	
	@Bean
	public org.neo4j.ogm.config.Configuration secondaryDataSourceOgmConfiguration() {
		return neo4jSecondaryDataSourceProperties().createConfiguration();
	}

	@Bean(name = SECONDARY_SESSION_FACTORY)
	public SessionFactory sessionFactory() {
		return new SessionFactory(secondaryDataSourceOgmConfiguration(), SECONDARY_BASE_PACKAGE);
	}
	
	@Bean(name = SECONDARY_TRANSACTION_MANAGER)
	public Neo4jTransactionManager neo4jTransactionManager() {
		return new Neo4jTransactionManager(sessionFactory());
	}
}

使用参考动态cypher

动态cypher

	@Autowired
	@Qualifier(SecondaryNeo4jDataSourceConfig.SECONDARY_SESSION_FACTORY)
	Session session
	
	@Override
	public Page<ViewRequestInfo> getAuditInfoList(ViewRequestInfoSearch viewRequestInfoSearch) {
		if (null == viewRequestInfoSearch){
			//查询条件为空则查全部
			viewRequestInfoSearch = new ViewRequestInfoSearch();
		}
		String matchSql = " match(n:RequestInfo) ";
		List<String> whereSqlList = new ArrayList<>();
		String account = viewRequestInfoSearch.getAccount();
		//账号
		if (null != account){
			this.getLikeWhereSql("account",account,whereSqlList);
		}
		//用户名
		String username = viewRequestInfoSearch.getUsername();
		if (null != username){
			this.getLikeWhereSql("username",username,whereSqlList);
		}
		//操作
		String operation = viewRequestInfoSearch.getOperation();
		if (null != operation){
			this.getLikeWhereSql("operation",operation,whereSqlList);
		}
		//方法
		String method = viewRequestInfoSearch.getMethod();
		if (null != method){
			this.getLikeWhereSql("method",method,whereSqlList);
		}
		//路径
		String path = viewRequestInfoSearch.getPath();
		if (null != path){
			this.getLikeWhereSql("path",path,whereSqlList);
		}
		//参数
		String requestBody = viewRequestInfoSearch.getRequestBody();
		if (null != requestBody){
			this.getLikeWhereSql("requestBody",requestBody,whereSqlList);
		}
		//请求状态
		Integer responseStatus = viewRequestInfoSearch.getResponseStatus();
		if (null != requestBody){
			String whereSql = " n."+ responseStatus +" = " + responseStatus + " ";
			whereSqlList.add(whereSql);
		}
		String startTime = viewRequestInfoSearch.getStartTime();
		//操作时间
		if (null != startTime){
			String whereSql = " n.time >= " + startTime + " ";
			whereSqlList.add(whereSql);
		}
		String endTime = viewRequestInfoSearch.getEndTime();
		if (null != endTime){
			String whereSql = " n.time <= " + endTime + " ";
			whereSqlList.add(whereSql);
		}
		//域名
		String host = viewRequestInfoSearch.getHost();
		if (null != host){
			this.getLikeWhereSql("host",host,whereSqlList);
		}
		//模块
		String model = viewRequestInfoSearch.getModel();
		if (null != model){
			this.getLikeWhereSql("model",model,whereSqlList);
		}
		String whereSql = "";
		if (!whereSqlList.isEmpty()){
			whereSql = whereSqlList.stream().collect(Collectors.joining(" and "));
			whereSql = " where " + whereSql;
		}
		//构造查询
		matchSql = matchSql + whereSql;
		//构造分页
		Integer pageNum = viewRequestInfoSearch.getPageNum();
		Integer pageSize = viewRequestInfoSearch.getPageSize();
		if (null == pageNum){
			pageNum = 0;
		}
		if (null == pageSize){
			pageSize = 10;
		}
		Integer skip = pageNum * pageSize;
		//查询数据
		String matchListSql = matchSql + " return n skip " + skip + " limit " + pageSize + " ";
		Iterable<RequestInfo> requestInfos = session.query(RequestInfo.class, matchListSql, Maps.newHashMap());
		//查询总数
		String matchCountSql = matchSql + " return count(n)";
		Long total = session.queryForObject(Long.class, matchCountSql, Maps.newHashMap());
		//构造返回值
		PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
		List<ViewRequestInfo> viewRequestInfoList = new ArrayList<>();
		if (null != requestInfos){
			for (RequestInfo requestInfo : requestInfos){
				ViewRequestInfo viewRequestInfo = new ViewRequestInfo();
				BeanUtils.copyProperties(requestInfo,viewRequestInfo);
				viewRequestInfoList.add(viewRequestInfo);
			}
		}
		PageImpl<ViewRequestInfo> pageConfigFlags = new PageImpl<ViewRequestInfo>(viewRequestInfoList, pageRequest, total);
		return pageConfigFlags;
	}

	private String getLikeWhereSql(String paramName, String paramValue,List<String> whereSqlList) {
		if (null == paramName && null == paramValue){
			return null;
		}
        String whereSql = " n."+ paramName +" =~'.*" + paramValue + ".*' ";
        whereSqlList.add(whereSql);
        return whereSql;
	}

查询结果集到自定义的类

       Long total = this.session.queryForObject(Long.class, matchCount, Maps.newHashMap());
        String matchList = matchCypher + " return {" +
                "id: id(n), " +
                "caseID: n.caseID, " +
                "caseName: n.caseName" +
                "} as res " +
                "skip " + pageNum * pageSize + " limit " + pageSize + "";
        Result result = this.session.query(matchList , Maps.newHashMap());

        List<ViewFreezeCase> viewFreezeCases = new ArrayList<>();
        if (null != result){
            for(Map<String, Object> map : result){
                JSONObject jsonObject = new JSONObject(map);

                JSONObject resData = jsonObject.getJSONObject("res");
                if (null == resData || resData.isEmpty()) {
                    continue;
                }
                ViewFreezeCase viewFreezeCase = JSON.toJavaObject(resData, ViewFreezeCase.class);
                //构造过滤器类型返回值
                viewFreezeCases.add(viewFreezeCase);
            }
        }
        PageRequest pageRequest = PageRequest.of(pageNum, pageSize);
        PageImpl<ViewFreezeCase> page = new PageImpl<ViewFreezeCase>(viewFreezeCases, pageRequest, total);
//apoc.map.setLists函数解析
apoc.map.setLists(n1,[n2],n3)
查询一个列表信息,每个元素数据都是map对象
n1可以是任意节点,只查询节点的properties属性
n2是要查询的字段
n3是n2对应的每行的数据
当n1和n2互相包含相同字段时,n2会覆盖n1的字段值

$动态传参

        String cypher = "match(dictType:DictType) where id(dictType) = $dictTypeId " +
                        "unwind $specialDictBodys as item " +
                        "merge(dictType) -[:DictTypeRelationship]-> (dict:" + dictTypeKey + "{code:item.code}) " +
                        "set dict = item";
        Map<String, Object> params = new HashMap<>();
        params.put("dictTypeId", dictTypeId);
        params.put("specialDictBodys", specialDictBodys);

列表推导式收集集合中的属性

Neo4j 4.x或更高版本

match(user:User) -[:资金字段映射|:billMappingRelation]-> (n)
where n:资金字段映射 or n:billMapping
optional match(case:资金分析) -[:关联文件] -> (bill:Bill:资金数据文件)
where n.hash = bill.hash
with distinct n, user, case ,bill
with distinct n, user, collect(distinct case) as cases ,collect(distinct bill) as bills
RETURN {
    id: id(n),  
    creater: user.name,  
    createTime: n.createTime,  
    isEnable: n.isEnable,  
    propertiesMap: n.data,  
    originPropsData: n.originPropsData,  
    mappingPropsData: n.mappingPropsData,  
    viewBillInfos: [bill IN bills | {id: id(bill), name: bill.原始数据文件名}], 
    viewCaseInfos: [case IN cases | {id: id(case), name: case.caseName}]
} AS result

查询结果
{
  "mappingPropsData": null,
  "createTime": "2024-01-25 12:59:46",
  "creater": "ss",
  "viewCaseInfos": [
    {
      "name": "测试y",
      "id": 3132730
    },
    {
      "name": "测试时间",
      "id": 3213273
    }
  ],
  "propertiesMap": "{"对方开户行":"交易对方账号开户行","交易余额":"交易余额","对方账号":"交易对方账卡号"}",
  "id": 77465,
  "viewBillInfos": [
    {
      "name": "-2级-黄-62163-平果国民村镇银行.xls",
      "id": 77512
    },
    {
      "name": "-1级-鄂建设银行.xls",
      "id": 3205608
    }
  ],
  "originPropsData": null,
  "isEnable": null
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值