neo4j的查询语法Cypher+python演示

全栈工程师开发手册 (作者:栾鹏)
架构系列文章


MATCH

查询节点
根据label,属性的值查询单个(多个)节点。其中label,attr,value可以省略

MATCH (node1_var :node1_label{attr1:value1,attr2:value2}),(node2_var :node2_label)
RETURN node1_var.attr3,node2_var

查询邻接点
查询节点的相关节点(不区分方向),使用--,其中两个节点变量的label,attr,value都可以省略

MATCH (node_var: node_label{attr1:value1,attr2:value2})--(node1_var: node_label1{attr3:value3,attr4:value4})
RETURN node_var1.attr5

若要区分方向,使用-->或者<--

MATCH (node_var: node_label{attr1:value1,attr2:value2})-->(node1_var: node_label1{attr3:value3,attr4:value4})
RETURN node_var1.attr5

查询边

可以把关系设置为变量,并返回。其中relation_var表示关系变量的合集, 三个变量的label、attr、value都可以省略。

MATCH (node_var: node_label{attr1:value1})<-[relation_var: relation_label{attr3:value3}]-(node1_var: node_label1{attr2:value2})
RETURN relation_var.attr4

查询多条边

MATCH (node1_var :node1_label {attr1:value1})<-[r1]-(node1_var: node1_label{attr2:value2})-[r2]->(node3)
RETURN r2.peer_count

查询多跳关系
乘以3表示查询至多3跳关系的。

MATCH (node1_var :node1_label {attr1:value1})<-[r1:relation_label*3]-(node1_var: node1_label{attr2:value2})-[r2]->(node3)
RETURN r2.peer_count

读取多跳关系

MATCH p =(node1_var { attr1:value1 })-[:relation_label*2]-(node3)
RETURN relationships(p)

注意:match使用方法中label,使用:labeld1 | :label2 方式,属性使用{attr1:value1,attr2:value2}的方式来实现多条件匹配。
relation的label被称为type

where

读取节点

MATCH (n) 
WHERE id(n)= 0
RETURN n

读取边

MATCH ()-[r]->() 
WHERE id(r)= 0
RETURN r

批量读取

MATCH (n)
WHERE id(n) IN [0, 3, 5]
RETURN n

布尔运算符AND,OR,XOR和NOT。可以使用label、属性等过滤。

MATCH (n)
WHERE n:mylabel AND n.name = 'Peter' XOR (n.age < 30 AND n.name = 'Timothy') OR NOT (n.name = 'Timothy' OR n.name = 'Peter')
RETURN n.name, n.age

has包含属性

MATCH (n)
WHERE has(n.belt)
RETURN n.name, n.belt

新版本似乎可以使用

MATCH (n)
WHERE n.belt IS NOT NULL 
RETURN n.name, n.belt

字符串STARTS WITH

MATCH (n)
WHERE n.name STARTS WITH 'Pet'             开头
WHERE n.name ENDS WITH 'ter'                 结尾
WHERE n.name CONTAINS 'ete'                   包含
WHERE n.name =~ 'Tim.*'                           正则
RETURN n.name, n.age

查询孤立节点

match (n) where not (n)–[]-() return n;

with 管道,变量作用域

理解为定义变量。

聚合结果必须通过一个WITH子句才能过滤。

MATCH (david { name: 'David' })--(otherPerson)-->()
WITH otherPerson, count(*) AS foaf
WHERE foaf > 1
RETURN otherPerson.name

管道也可以有处理函数。比如ORDER,limit,去重

您可以在将结果传递给collect之前对结果进行排序,从而对结果列表进行排序。

MATCH (n)
WITH n
WITH DISTINCT n
ORDER BY n.name DESC LIMIT 3
RETURN collect(n.name)

使用UNWIND将列表转换为行,实现批量的操作

理解成for语句,多层UNWIND,理解成多层for语句

WITH [[1, 2],[3, 4], 5] AS nested
UNWIND nested AS x
UNWIND x AS y
RETURN y

批量创建的示例
参数

{
  "events" : [ {
    "year" : 2014,
    "id" : 1
  }, {
    "year" : 2014,
    "id" : 2
  } ]
}

原始列表的每个值都被展开并传递MERGE以查找或创建节点和关系。

UNWIND $events AS event
MERGE (y:Year { year: event.year })
MERGE (y)<-[:IN]-(e:Event { id: event.id })
RETURN e.id AS x
ORDER BY x

order by + skip(offset) + limit

ORDER BY n.age, n.name
ORDER BY n.name DESC

对结果集进行排序时,null将始终在结果集的末尾进行升序排序,并且首先进行降序排序。

MATCH (n)
RETURN n.name
ORDER BY n.name
SKIP 1
LIMIT 2

DELETE

DELETE删除变量
DETACH DELETE 删除节点和绑定边

MATCH (n { name: 'Andy' })-[r:KNOWS]->()
DELETE r,n
DETACH DELETE n

SET设置变量(设置为NULL表示删除属性)

查询设置

设置属性
MATCH (n { name: 'Andy' })
SET n.surname = 'Taylor'
RETURN n.name, n.surname

设置标签
MATCH (n { name: 'Stefan' })
SET n:German        # 设置标签
SET n:Swedish:Bossman   # 设置多标签
RETURN n.name, labels(n) AS labels

过滤设置

MATCH (n { name: 'Andy' })
SET (
CASE
WHEN n.age = 36
THEN n END ).worksIn = 'Malmo'
RETURN n.name, n.worksIn

节点或关系对象赋值

MATCH (at { name: 'Andy' }),(pn { name: 'Peter' })
SET at = pn          #会删除原有属性
SET p = { name: 'Peter Smith', position: 'Entrepreneur' }   #会删除原有属性
SET p = { }     #会删除原有属性
SET n = $props   #会删除原有属性
SET p += { age: 38, hungry: TRUE , position: 'Entrepreneur' }   #不会删除原有属性
RETURN at.name, at.age, at.hungry, pn.name, pn.age

MERGE创建或者更新

默认是创建新的。

可以分别处理如果是创建,或者如果是更新

MATCH (person:Person)          先查询,可以使用以后变量创建新变量
MERGE (city:City { name: person.bornIn })           # 合并的变量,变量可能会已经存在
ON CREATE SET keanu.created = timestamp()   # 如果是创建了变量          
ON MATCH SET keanu.lastSeen = timestamp()   # 如果是更新了操作
RETURN person.name, person.bornIn, city

设置唯一限制

CREATE CONSTRAINT ON (n:mylabel) ASSERT n.name IS UNIQUE

参考官方文档:https://neo4j.com/docs/cypher-manual/3.5/

python演示

# -*- coding:utf-8 -*-
import os
import time,datetime
from neo4j.v1 import GraphDatabase



class Neo4j_client():

    def __init__(self,neoj4_host = 'bolt://192.168.11.127:30687',username='neo4j',password='admin'):
        self.driver = GraphDatabase.driver(neoj4_host,auth=(username, password))

    def close(self):
        self.driver.close()

    # 创建name唯一性,name是profile_id
    def create_name_unique(self,tx,project):
        command = 'CREATE CONSTRAINT ON (n:%s) ASSERT n.name IS UNIQUE'%project
        tx.run(command)
        return None

    # 删除一个项目的全部节点和边
    def clear_all(self,tx,project):
        command = 'MATCH (n:%s) DETACH DELETE n' % project
        tx.run(command)
        return None

    # 建节点,原节点没有就会建,属性和属性的值完全一样才会保留原数据
    def create_node(self,tx,project,profile_id,update_time):
        tx.run("MERGE (node: %s{name: $profile_id, project: $project,update_time: $update_time}) "%project,profile_id=profile_id,project=project,update_time=update_time)
        return None

    # 删除节点和关联的边
    def delete_node_from_profile_id(self,tx,project,profile_id):
        tx.run("MATCH (node: %s{name: $profile_id, project: $project}) DETACH DELETE node "%project,profile_id=profile_id,project=project)
        return None

    # 删除节点和关联的边
    def delete_node_from_update_time(self,tx,project,update_time):
        tx.run("MATCH (node: %s{project: $project}) WHERE node.update_time<$update_time DETACH DELETE node "%project,update_time=update_time,project=project)
        return None


    # 建立节点之间的连边,这个是单向的,里面的count是边的属性,边可以有多个属性。
    def create_relationship(self,tx, project, profile_id, neighbor_id, peer_count,update_time):
        tx.run("MATCH (from: %s{name: $profile_id}),(to: %s{name: $neighbor_id}) "%(project,project)+
               "MERGE (from)-[:peer{peer_count:$peer_count,update_time:$update_time}]->(to) ",
               profile_id=profile_id,neighbor_id=neighbor_id,peer_count=peer_count,update_time=update_time)
        return None

    # 删除指定时间范围之前的关系
    def delete_relations_from_update_time(self,tx,project,update_time):
        tx.run("MATCH (node1:%s) -[r]-(node2:%s) WHERE r.update_time<$update_time DELETE r" % (project,project),update_time=update_time, project=project)
        return None


    # 根据档案号读取node
    def get_node_from_profile_id(self,tx,project, profile_id):
        nodes = tx.run("MATCH (node:%s {name: $name}) RETURN node"%project, name=profile_id)
        profile = nodes.single()[0]
        result = {'id': profile.id, 'profile_id': profile.get('name'), 'project': profile.get('project'),
                  'labels': list(profile.labels)}
        return result

    # 读取邻接点和边,关系方向为当前节点到邻接点
    def get_neighbors_relations(self,tx,project,name):
        results = tx.run("MATCH (src_node:%s {name: $name})-[relation:peer]->(neighbor) RETURN relation,neighbor"%project, name=name)
        results = results.data()
        neighbors={}  # {id:{"node":{},"relation":{}}}
        for result in results:
            # print(result)
            relation = result['relation']
            neighbor=result['neighbor']
            neighbors[neighbor.id]={
                'node':{'id':neighbor.id,'name':neighbor.get('name'),'project':neighbor.get('project'),'labels':list(neighbor.labels)},
                'relation':{'id':relation.id,'type':relation.type,'peer_count':relation.get('peer_count'),'update_time':relation.get('update_time')}
            }
            # print(relation)
            # print(neighbor)
        # print(neighbors)
        return neighbors



    # 同时创建或更新所有邻节点和关系。neighbors是列表形式[{profile_id:profile_id,peer_count:peer_count}]
    def update_neighbors(self,tx, project, src_profile_id, neighbors,update_time):
        print(src_profile_id,neighbors)
        command = '''
        MERGE (src_node:%s{name:$src_profile_id,project: $project })
        WITH src_node
        UNWIND $peer_profiles AS peer_profile
        MERGE (peer_node:%s { name: peer_profile.profile_id,project: $project  })
        MERGE (src_node)<-[r:peer]-(peer_node)
        ON CREATE SET r.update_time=peer_profile.update_time,r.peer_count=peer_profile.peer_count
        ON MATCH SET r.update_time=peer_profile.update_time,r.peer_count=peer_profile.peer_count+r.peer_count
        RETURN src_node.id AS src_node_id'''%(project,project)
        print(command)
        tx.run(command,project=project,peer_profiles=neighbors,src_profile_id=src_profile_id,update_time=update_time)
        return None


    # 将一个节点的全部关系转移到另一个节点上,并删除原节点
    def move_node(self,tx,project,src_profile_id,des_profile_id):
        print(src_profile_id,des_profile_id)
        # 匹配不定方向,会把双边方向都匹配下来, 由由于原邻接点需要与目标节点建立双边关系,所以如果原关系取双边的话,会重复计算一遍
        command = '''
                MATCH (src_node:%s { name: $src_profile_id })-[src_r:peer]->(peer_node),(des_node:%s { name: $des_profile_id }) WHERE NOT id(peer_node)=id(des_node)
                MERGE (des_node)-[des_r:peer]-(peer_node)
                ON CREATE SET des_r.update_time=src_r.update_time,des_r.peer_count=src_r.peer_count
                ON MATCH SET des_r.peer_count=src_r.peer_count+des_r.peer_count
                DETACH DELETE src_node
                RETURN des_node.name AS des_node_name,des_r.peer_count''' % (project, project)
        print(command)
        result = tx.run(command,src_profile_id=src_profile_id,des_profile_id=des_profile_id)
        print(result.data())
        return None





neo4j_client = Neo4j_client()
neo4j_client.driver.session().write_transaction(neo4j_client.clear_all, 'yinli')   # 删除yinli的全部节点和边
neo4j_client.driver.session().write_transaction(neo4j_client.create_name_unique, 'yinli')   # 创建name唯一性。
peer={
    'lp1':{
        'lp2':3,
        'lp3':4
    },
    'lp2':{
        'lp1':3,
        'lp3':1
    },
    'lp3':{
        'lp1':4,
        'lp2':1
    }
}

# 创建或者更新节点和关系
for src_profile_id in peer:
    neighbors = peer[src_profile_id]
    new_neighbors=[]
    for neighborid in neighbors:
        new_neighbors.append({'profile_id':neighborid,'peer_count':neighbors[neighborid]})
    print(new_neighbors)
    neo4j_client.driver.session().write_transaction(neo4j_client.update_neighbors, 'yinli', src_profile_id, new_neighbors, datetime.datetime.now().strftime('%Y%m%d'))


# 读取节点
profile = neo4j_client.driver.session().write_transaction(neo4j_client.get_node_from_profile_id, 'yinli','lp1')
print(profile)


# 读取节点的邻接点和边
neighbors = neo4j_client.driver.session().write_transaction(neo4j_client.get_neighbors_relations,'yinli', 'lp1')
print(neighbors)


# 转移节点
neo4j_client.driver.session().write_transaction(neo4j_client.move_node,'yinli', 'lp1','lp2')


# 删除节点和关联的边关系
neo4j_client.driver.session().write_transaction(neo4j_client.delete_node_from_profile_id, 'yinli','lp2')

neo4j性能调优

  • 1:增加索引
  • 2:优化neo4j配置文件
  • 3:增加服务器内存
  • 4:增加ssd固态硬盘

一、增加索引

经查阅相关资料可知,neo4j数据库的索引一般分为三类。

① 手动索引:Neo4j数据库若采用手动方式创建索引,则索引并不会随着数据的改变而自动更新。虽然该种方法可以手动创建和维护索引,但由于较为麻烦,所以一般不采用。

② 自动索引:自动索引是一种通过修改配置文件来创建索引的方法,但是在目前的neo4j 3.x版本中已经摒弃了用该方法来创建索引,并建议使用模式索引代替之。

③ 模式索引:模式索引和关系数据库中的索引很相似, 每一个索引会对应一个标签和一组属性,无论是更新还是删除节点,索引都会自动更新或者删除,因此该种创建索引的方式更适用。

很显然采用模式索引会更简单方便,而建立模式索引,需要使用Cypher语句:CREATE INDEX ON: 标签(待查字段)。一般在浏览器http://172.18.34.25:7474/browser/网页上,可分别为待查字段建立模式索引。然而实验结果表明,建立索引后的查询时间虽有减少但不足以满足实际需求。另外有一点非常重要,索引建立后只是Populating状态,一定要一定要一定要重启数据库并关闭http://172.18.34.25:7474/browser/网页让索引ONLINE生效,否则刚刚建的索引是无效的,望大家切记。若不知道待查字段是否已有索引,可用“:schema”指令查看当前数据库中已建好的所有索引和索引是否ONLINE。

二、优化neo4j配置文件

① 先明确neo4j的安装路径,然后执行“cd /home/public/Software/neo4j-community-3.3.7/conf/”指令进入指定目录下。由于要对neo4j配置文件进行修改,为了保险起见建议在对neo4j.conf文件进行修改之前,先备份一份neo4j.conf文件。

② 用“vim neo4j.conf”指令打开neo4j.conf文件并进行相应修改。经过查阅一些资料得知,通过添加jvm虚拟环境可以提高数据库的查询速度,即取消neo4j配置文件中关于dbms.memory.heap.initial_size=512m;dbms.memory.heap.max_size=512m两行的注释,并做合适的修改(最大堆内存越大越好,但是要小于机器的物理内存)。

三、增加服务器内存(未实施)

四、增加ssd固态硬盘(未实施)

由于“增加索引”和“优化neo4j配置文件”已经可以让neo4j数据库的查询时间得到了较大的缩减,并能满足一般的商业需求,所以暂时还未进行“增加服务器内存”和“增加ssd固态硬盘”的优化操作。

心得体会:

①:在测试前一定要为待查字段分别建立模式索引,建与不建的查询速度是非常显著的哈;

②:索引创建后一定要ONLINE才会生效,这点把我坑的好惨啊!

③:测试查询语句时,一定要尽可能将在一类标签中(其实相当于一张表)靠后或靠中间的节点属性作为查询条件,这样才能遍历更多的节点,故所得的测试结果才会真实可信;

④:增加WHERE语句、配合使用AND、OR等加大查询复杂度,另外还可以通过使用错误的范围语句来进行测试,如 “2020-10”<= P1.paper_publish_date <= “2017-10”;

⑤:测试语句也要将不存在的节点的属性作为查询条件,看返回空的时间如何;

⑥:学会优化Cypher查询语句,如

MATCH (a:Author)-[:author_is_in_field]->(f:Field)

WHERE f.field_level = "L3"

RETURN a.auhtor_name,f.field_name,f.field_reference_count

LIMIT 10  

可以优化成

MATCH (a:Author)-[:author_is_in_field]->(f:Field{field_level:"L3"})

RETURN a.auhtor_name,f.field_name,f.field_reference_count

LIMIT 10

⑦:测试过程中,要想尽一切用户可能使用的场景来进行测试,切不可有意回避某些使用场景,否则不过是自欺欺人而已。

原文链接:https://blog.csdn.net/Vensmallzeng/article/details/89299687

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

腾讯AI架构师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值