Neo4j入门点滴(二):模式与模式匹配

12350阅读 0评论2017-05-22 五岳之巅
分类:NOSQL

  Announcement: All data comes from the book "Building Web Applications with Python and Neo4j", just for study & not for commerce.

 模式及模式匹配(Pattern and Pattern matching)此乃Cypher的核心,描述了我们想要查找、创建或更新的数据的形状。不理解模式和模式匹配,就写不出既有效果又有效率的查询。

一、数据准备
 首先,输入如下命令清空当前数据库:
  1. match (n)-[r]-(n1) delete n,r,n1;
  2. match (n) delete n
  第一条命令删除相互联系的所有节点及其联系,第二句则删除所有独立的节点。
 然后,创建一堆男人和女人:
  1. CREATE (bradley:MALE:TEACHER {name:'Bradley', surname:'Green',age:24, country:'US'})
    CREATE (matthew:MALE:STUDENT {name:'Matthew', surname:'Cooper',age:36, country:'US'})
    CREATE (lisa:FEMALE {name:'Lisa', surname:'Adams', age:15,country:'Canada'})
    CREATE (john:MALE {name:'John', surname:'Goodman', age:24,country:'Mexico'})
    CREATE (annie:FEMALE {name:'Annie', surname:'Behr', age:25,country:'Canada'})
    CREATE (ripley:MALE {name:'Ripley', surname:'Aniston',country:'US'})
  此时有节点但没有联系,结果如下图:
 然后我一堆输入下列联系语句,总报错。但逐条输入就没问题了:
  1. MATCH (bradley:MALE{name:"Bradley"}),(matthew:MALE{name:"Matthew"})WITH bradley, matthew CREATE (bradley)-[:FRIEND]->(matthew) , (bradley)-[:TEACHES]->(matthew);
  2. MATCH (bradley:MALE{name:"Bradley"}),(matthew:MALE{name:"Matthew"})WITH bradley,matthew CREATE (matthew)-[:FRIEND]->(bradley);
  3. MATCH (bradley:MALE{name:"Bradley"}),(lisa:FEMALE{name:"Lisa"})WITH bradley,lisa CREATE (bradley)-[:FRIEND]->(lisa);
  4. MATCH (lisa:FEMALE{name:"Lisa"}),(john:MALE{name:"John"})WITH lisa,john CREATE (lisa)-[:FRIEND]->(john);
  5. MATCH (annie:FEMALE{name:"Annie"}),(ripley:MALE{name:"Ripley"})WITH annie,ripley CREATE (annie)-[:FRIEND]->(ripley);
  6. MATCH (ripley:MALE{name:"Ripley"}),(lisa:FEMALE{name:"Lisa"})WITH ripley,lisa CREATE (ripley)-[:FRIEND]->(lisa);
  现在可以看到基本数据集合的样貌:

、模式简介

(1)Pattern for Nodes
  匹配节点是最基本也是最简单的一种,使用括号进行描述。但是要注意,如果不额外使用属性或标签,那么括号可以省略:
  1. MATCH (a) return a
  2. 等价于:
  3. MATCH a return a;
  结果有四个节点都是男性,如图:

(2)Pattern for Labels

  就是增加“:标签”进行限定,需要说明的是,可以同时使用多标签,起到交集的作用,如下第二句就使用了多标签:
  1. MATCH (n:MALE) return n;
  2. MATCH (n:MALE:TEACHER) return n;
  结果返回的只有一个节点:

(3)Pattern for Relationships
  联系就是两个给定节点之间的连接,既可以是单向的,也可以是双向的,由[]和命名组成。
 看一个单向的例子:
  1. match (a:TEACHER)-[b:TEACHES]->(c:STUDENT) return a,b,c
  在这个例子中,系统首先搜寻TEACHER和STUDENT标签的节点,然后在这些节点中再找寻符合TEACHES联系的。
 双向就是不需要箭头标识了,如下:
  1. match (a:MALE)-[b:FRIEND]-(c:FEMALE) return a,b,c

(4)Pattern for property
  属性的匹配使用的是花括号和键值对,其间使用都好分隔,如下:
  1. match (a:MALE{ name:"John", age:24} return a

三、使用Where从句
(1)Where
  如果仅仅使用Pattern并不能充分地满足要求,别懵逼,还有Where在。可以使用Where进一步过滤数据,但是要注意Where条件句本身并不能单独使用,只能用在match、optionalmatch、start或with的后面。比如:
  1. match (x)
  2. where x.age < 30 and x.country = "US"
  3. return x
  此时,只有一个人符合要求:


(2)Where从句中使用Pattern
  对于一个集合而言,如果是空集,那么就代表false,非空则表示true。可以使用in这个关键词来进一步限定:
  1. match (x)
  2. where x.name in ["John", "Andrew"] and x.age is not NULL
  3. return x
  当然,也可以使用not和正则表达式进行过滤:
  1. match (x)
  2. where x.name =~ "J.*"
  3. return x
  在这种情况下,不是= ~"xxx",而是 =~ "xxx",也就是说=~这是一个符号,千万别写错了。
 好了,以下要介绍一些此书上没有技巧
  [1] 使用别名:
  1. with 'AGE' as haha
  2. match (x)
  3. where x[toLower(haha)] < 30
  4. return x
  注意,Neo4j中属性名是大小写敏感的,如果写成x.AGE,则系统会提示并没有该属性:

  但是,“.”并不等同于“[]”,比如如下写法就是错误的:

  Why?我终于发现,并非“.”不等同于“[]”,而是二者确实相等,但用法有讲究。对于[]而言,其间必须是常量,所以当我把x[age]写成x['age']后,就顺利通过了,而且返回结果与x.age一样:


  [2] 使用exists()函数进行属性检验
  1. match (x)
  2. where exists(x.age)
  3. return x.name
  以前使用过has(),但现在被exists()代替而移除了

  [3] 字符匹配:
 这绝对是一把利器,使用starts with、ends with或contains,匹配字符串以何种模式开始,以何种模式结束或者其中包含什么。非常便利!比如:
  1. match (x)
  2. where x.name starts with "B"
  3. return x.name
  或者:
  1. match (x)
  2. where x.name contains "a"
  3. return x.name

(3)其他从句
  [1] order来排序(默认是升序,支持混排)
  [2] limit来限定返回数,skip则表示忽略最前面的。从而使用limit和skip的组配,可以取到中间的值:
  1. match (x)
  2. return x
  3. order by x.age skip 3 limit 2
  返回的就是“不要前3个,只要第4和第5”。确实很灵活!

(4)with从句
  with也是非常有用的一种从句,在介绍with之前,需要先研究一下“,”。比如在如下语句中,逗号是作为并列出现的,结果返回x和y两个人的信息,包括return语句中的x,y之间的逗号也都是这种用法。
  1. match (x{name:"John"}),(y{name:'Annie'})
  2. return x,y
  好的,继续,对于如下的初始情况:

  执行以下的语句会有什么结果?
  1. match (x{name:'Lisa'})<--(y)--()
  2. return count(y)
  我觉得应该是2,但我错了,结果是4。如下图:

  为什么会是这样?也就意味着把两个间接的联系也算上了?好吧,自己再试试,这次用双向试试。结果大跌眼镜,依旧是4:
  1. match (x{name:'Lisa'})--(y)--()
  2. return count(y)
  用Brandley的结果竟然是8,使用有方向的话是3。我又重新核对了一遍预设的所有联系,确实不应该是3。难道是BUG?(需要换个版本试试
  OK,话说回来,让逗号出现在with中,书中的例子如下:
  1. match (x{name:'Bradley'})--(y)-->()
  2. with y, count(*) AS cnt
  3. where cnt > 1
  4. return y
  返回值是Matthew,这没问题。因为符合模式的所有记录中,只有Matthew超过两条联系。问题来了,必须写with y才能限定吗?如果我去掉y呢?
  1. match (x{name:'Bradley'})--(y)-->()
  2. with count(*) AS cnt
  3. where cnt > 1
  4. return cnt
  cnt的值就变成了3,好的,我再加上y,看看cnt值:
  1. match (x{name:'Bradley'})--(y)-->()
  2. with y, count(*) AS cnt
  3. where cnt > 1
  4. return cnt
  这时,返回的cnt就成了2了。上述的尝试充分说明了with y, xxx这个模式中,逗号前的y起到限定的作用,如果不加y,那么:
  1. match (x{name:'Bradley'})--(y)-->()
  2. return count(*)
  记录数就是3,表示命中的自Bradley发出的3条联系及其节点,如果推论正确,那么添加一个z表示目标节点,那么count(z)就应该是2个:
  1. match (x{name:'Bradley'})--(y)-->(z)
  2. return count(z)
  结果是3个,但是如果我加上distinct,则变成了2个:
  1. match (x{name:'Bradley'})--(y)-->(z)
  2. return count(distinct z)
  明白了,有些东东重复计算了,现在再试一下那些不解的例子:




  也就是说,有向联系的结果加入distinct是正确的,没有问题,不加就是4和2。无向联系就见了鬼了:

  更加奇怪的是,一下结果并不返回John:

  天啊,神啊。我终于发现是怎么回事了,都是我的错!怪我并没有理解的很深。
 细细讲一下,我一直把(x)--(y)--()当成了一组联系,实际上这是错误的,因为联系并不是用()表示,而是用[]表示。看看,不论有方向还是没有方向,这下全对了:


  好吧,现在主要来看()--()--()三个括号的连用。那么我先试试-而不是--,如:

  很显然,结果是错误的。也就是说match (x{name:'Lisa'})-(y)-(z),这种写法就是不对的。当然,换成--就没有语法错误了:match (x{name:'Lisa'})--(y)--(z) return count(*),但结果是4。现在我明白,因为(a)--(b)--(c)表示节点之间的连接关系,就是说要满足a是Lisa,而且b和a是连接的,并且c还要和b相连。通俗地讲,就是以a为中心,向外扩2层。如果我理解的是对的,那么match (x{name:'Lisa'})--(y)--(z) return z,结果就应该是最外层的两个节点了。而且match (x{name:'Lisa'})--(y) return count(*),就应该与Lisa直接相连的3个节点罗。试试,检验一下:

  恭喜自己,虽然费了番周折,但终于搞定!反过来说,对于联系也可以这么玩:[a]-(b)-[c],我没试,但我相信是可以的。Cypher真的很灵活!
 接来下,使用with x增加限定条件,进行创建。但我发现不论有没有这个with x,都是一样的,创建了三个kai:



(5)union和union all从句
  union的用法与SQL一样,用于连接两个Match,返回结果中剔除了重复记录。但union all功能一样,但不剔除重复记录。
  1. match (x:MALE)-[:FRIEND]->() return x.name, labels(x)
  2. union
  3. match (x:FEMALE)-[:FRIEND]->() return x.name, labels(x)
  labels()返回的是全部的标签,有几个返回几个,如下图所示:

  但是,要注意:没有label()这个返回一个标签的函数。

 这一篇博文更长,终于告一段落了。接下来会开启第三篇。

                                               五岳之巅
                                   2017年5月23日(孟菲斯时间)
                                                  20:37
                                             终稿于Dorsey

上一篇:Neo4j入门点滴(一):Cypher
下一篇:Neo4j入门点滴(三):用Cypher完善图