Neo4j / Cypher语言学习

文章目录


本文全部内容来自: https://www.jianshu.com/p/8b9b49e9e3cf

1. 普通的Match: 查找John和John的朋友

?:match后面可以有复杂的结构

match (a:User{name:"John"})-[:Friend]->()-[:Friend]->(b:User)
return john.name, friend.name

2. 添加过滤条件的查询

?: ①用in表示集合②=~是正则表达式

match (a:User)-[:friend]->(follower:User) 
where a.name in ['Joe', 'John', 'Sara'] and follower.name =~ 'S.*'
return a.name, follwer.name

3. 基础知识

模式

节点 (a)

关联节点 (a)-->(b) (a)-->()<--(c) 不指定箭头就是任意方向的联系

标签 (a:User:People) 可以有任意多个标签

关系

// 不关心方向的关系
(a)--(b)
// 若需要引用关系,起个变量
(a)-[r]-()
// 给关系增加类型约束
(a)-[r:rela]-(b)

? 关系只能有一个标签. 如果要查询多个可以用或| match (a)-[r:real1|:real2]->(b) return r

节点语法
Cypher采用一对()来表示节点.
():匿名节点,匹配所有的节点,如果想要操作匹配到的节点,需要加变量(matrix)
(matrix):赋有变量的节点,matrix将包含匹配到的所有节点,通过matrix变量可以对它们进行操作
(matrix:Movie): 指定了标签的节点,只会匹配标签为Moive的节点
(matrix:Movie {title:"Haha"}):指定了标签和属性的节点,只有节点标签是Movie,标题为Haha的节点会被匹配
关系语法

?: 关系语法赋予变量

-- :表示无方向关系
-->:有方向关系
-[rale]->:给关系赋予一个变量,方便对其操作
-[rale:friend]->:匹配关系类型为friend类型的关系,并赋予rale变量接收
-[rale:friend {long_time:2}]->:给关系加条件,匹配friend类型的关系且属性long_time为2

同时如果想要关系语法不想重复写, 想要多个语句写的时候减少重复写,可以将关系语法赋予一个变量

acted_in = (people:Person)-[:acted_in]->(movie:Movie)
这样 acted_in 就可以写到多个查询语句中,减少重复编写
更新语句

对于读取, Cypher采用惰性加载, 直到需要返回结果的时候才会实际的去匹配数据

?: WITH用来连接这两个部分. 使用WITH语句进行聚合过滤查询, 示例:

match (n:User {name:"John"})-[:friend]-(friend)
with n, count(friend) as friendsCount
where friendsCount > 3
return n, friendsCount

使用WITH语句进行聚合更新

match (n {name:'John'})-[:friend]-(friend)
with n, count(friend) as c
set n.friendsCount = c
return n.friendsCount

?刚刚自己做了一个测试

match (a:User)-[:REPLY]->(b:User) with a, b, count(b) as countb where countb>3 return *

这条语句返回的同一个a连接的b, b的入度大于3, 或者说a连接了3次b

match ()-[:REPLY]->(b:User) with b, count(b) as countb where countb>3 return *

这条语句返回的是b的入度大于3即可

h (b:User)<-[:REPLY]-(a) with a,b, count(b) as countb where countb>3 return *

3是1的反向, 反向后与1结果相同

返回语句

任何查询都可以返回数据.

// 返回匹配到的节点
match(n:Person {name:"Nick"}) return n

// 返回所有节点,关系和路径
match (n) rerurn *

// 也可以制造数据返回
return "haha"

RETURN语句有三个子句,分别为SKIP, LIMIT和ORDER BY

SKIP: 跳过多少条数据
LIMIT: 限制返回多少条数据
ORDER BY 用于对输出进行排序, 紧跟再RETURN和WITH后面
注意: 空值进行排序的时候. 对于升序null是排在最后面的, 对于降序null是排在最前面的

match(n)
return n.name
skip 1
limit 1
// 根据属性进行排序,默认升序
match (n) return n order by n.name

// 根据多个属性进行排序  对于年龄相等的采用名字来排序
MATCH(n) RETURN n ORDER BY n.age, n.name

// 降序排序
MATCH(n) RETURN n ORDER BY n.age DESC

4. 唯一性

进行模式匹配时,默认不会多次匹配同一个图关系.比如匹配John的<朋友>的<朋友>,不会将John自己也返回给John

MATCH (john:Person {name:"John"})-[:friend]-(friend1)-[:friend]-(friend2)
RETURN john.name,friend2.name

用上面这种查询, friend2不能使John。

如果想要更改默认的返回方式, 比如想要返回John自己本身. 这个时候可以这样做:
将上面的一个查询给拆分成两个(因为同一个查询不会查询同一个关系, 所以拆成两个查询)

MATCH (john:Person{name:"john"})-[:friend]-(friend1)
MATCH (friend1)-[:friend]-(friend2)
RETURN friend2.name

? 什么情况下需要聚合? 有函数的时候才有必要? With是如何传递的?

反正经过一轮With的时候, 不在with中声明的变量后面都不能用了, 比如

match (b:User)<-[:REPLY]-(a) with a, count(b) as countb where countb>3 return a,b

会有运行错误: 变量b未定义

5. 设置Cypher查询的版本

Neo4j数据库的Cypher也是会进行版本变化的.
可以在neo4j.conf配置中cypher.default_language_version 参数来设置Neo4j数据库使用哪个版本的Cypher语言

如果只是想在一个查询中指定版本,可以在查询语句的开头地方写上版本. 比如:

Cypher 2.3
match(n) return(n)

目前的版本有3.1 3.0 2.3

6. 基本元素

Cypher的基本类型

数据类型: 数值 字符串 布尔值
节点
关系
路径
映射 map
列表 list

转义字符

\t 制表符
\b 退格
\n 换行
\r 回车
\f 换页
\ 反斜杠
\uxxxx Unicode UTF-16 编码
\uxxxxxxxx Unicode UTF-32 编码

表达式

十进制
十六进制 : 0x开头
八进制:0开头
字符串
布尔 True true False false
变量 n
属性 n.prop
动态属性 n[“prop”] map[coll[0]] rel[n.city + n.zip]
参数 0 ​ 0 ​ 0param
表达式列表 []
函数调用 length(p) nodes(p)
聚合函数 avg(x.prop) count(*)
正则表达式 a.name =~ 'Tob.*'
路径 (a) -()->(b)
计算式 1+2 and 3>4
断言表达式 a.prop = 4 length(a)>10
大小写敏感的字符串匹配表达式 a.prop STARTS WITH ‘Hello’ , a.prop1 ENDS WITH ‘World’,a.name CONTAINS ‘N’

? CASE表达式
MATCH(n)
RETURN  
CASE n.eyes
    WHEN 'blue' THEN 1
    WHEN 'brown' THEN 2
    ELSE 3 
END
AS result

7. 参数

参数在编程语言中使用: https://hugh-wangp.iteye.com/blog/1848862

MATCH(n)
WHERE n.name = $name
RETURN n

对大小写敏感的字符串模式匹配

match(n)
where n.name starts with "N"
return n

8. 运算符

数学运算符 +, -, *, /, %, ^
比较运算符 = ,<>, <, >, <=, >= , is null , is not null
布尔运算符 and or nor xor(异或)
字符串运算符 连接运算符+ 正则匹配运算 =~
列表运算符 in

match(n)
where n.id in [1,2,3,4]
return n

9. 匹配路径长度

指定了关系数量为23个节点的匹配, 等价于(a)-[]->()-[]->(b)(a)-->()-->(b)

(a)-[*2]->(b)

指定可变长度的关系

(a)-[*2..4]->(b)  // 匹配路径长度为 2 到4 之间 的路径
(a)-[*2..]->(b) // 匹配路径长度大于2的路径
(a)-[*..2]->(b) // 匹配路径长度小于2的路径
(a)-[*]->(b) // 匹配任意长度的路径

示例:

match (me)-[:know*1..2]-(remote_friend)
where me.name = "Nick"
return remote_friend.name
深度匹配 (路径长度匹配)
match(n) -[r:TYPE*minHops..maxHops]->(m) return r

注意变长匹配返回字典组成的列表,定长匹配返回字典

minHops不写默认1 , maxHops不写默认关系的最大深度
最小边界写0,意味着自己本身,所以自身也会被返回

最短路径匹配 shortestPath() 函数 ?
match (martin:Person {name:"name1"}),(oliver:Perspn{name:"name2"}),p = shortestPath((martin)-[*..15]-(oliver)) 
return p
找到所有最短路径 allShortestPaths()
match (martin:Person {name:"name1"}),(oliver:Perspn{name:"name2"}),p= allShortestPaths((martin)-[*]-(oliver)) 
return p

10. 列表

创建列表

return [0,1,2,3,4] as list
>>  [0,1,2,3,4] 
return range(0,4)   // 注意range左边和右边都是开区间
>> [0,1,2,3,4] 

列表的索引

return range(0,4)[1]
>>1
return range(0,4)[-1]
>>4
return range(0,4)[0..3]		// 注意索引是左开又闭区间
>>[0,1,2]

return range(0,4)[5]		// 单个索引越界返回null
>>null

return range(0,4)[3..7]		// 多个索引越界从越界的地方截断
>>[3,4]

列表推导式

return [x in range(0,4) where x % 2 =0 | x^2 ] as result
>> [0,4,16]

模式推导式

模式推导式也可以理解为去匹配结果,将结果作为列表展示
比如: 匹配与nick有关的所有电影的发行年限

match(nick:People {name:"nick"})
return [(nick)-->(m)  where m:Movie | m.year] as years

11. Map字典投射

return {key:"value",listkey:[{inner:"map1"},{inner:"map2"}]}

找到nick和他参演过的所有电影

match(actor:Person {name:"nick"})-[:acted_in]->(m:Movie)
return actor {.name, .realname, movies:collect(movie {.title, .year})}
>> {name->"nick",realname->"hello nick",movies->[{title->"taitannike",year->"long_ago"},...]}

Map用来修改结果的格式. 找到nick,并返回他所有的属性,nick没有age属性,所以age对应的值是null

match(p:Person {name:"nick"})
return p {.*, .age}  
>> {name->"nick",gender->"boy",age-><null>}

12. 空值 NULL

空值null意味着一个未找到的未知值.

两个未知的值并不意味着它们是同一个值. 因此 null=null 返回null(不知道null是否等于null) 而不是true.

null 只能用 is null 和 is not null 判断.

空值进行逻辑运算

当成一个未知值去处理,比如:
null and true 值是null
null and false 值是false
null or flase 值是null

空值与in

2 in [1,null,3] => null

2 in [2, null] => true

下面这些情况将返回null
  • 从列表中获取不存在的元素 [][0]
  • 试图访问节点或者关系的不存在 x.prop1
  • 与null 做比较 1 < null
  • 包含null 的算术运算 1 + null
  • 包含null参数的函数调用 : count(null)

13. Match

简单匹配
match(n) return n

匹配标签
match(n:lable) return n
match(n:lable1:lable2) return n

匹配关系

match(n)--(m) return n,m
match(n)-->(m) return n,m
match(n)-[]->(m) return n,m
match(n)-[r]->(m) return n,m,r
match(n)-[r:rela]->(m) return n,m,r
// type 是返回关系是哪个类型, | 代表或的意思
match(n)-[r:rela1 | :rela2 | :rela3]->(m) return n,m,r,type(r)
// 用` 包括含有特殊字符的关系名 或者标签名
match(n) -[r:`r e l a 1`]->(m) return n,m,r,type(r)
optional match

optional match 类似于match, 不同之处在于 如果没有匹配到, optional match将用null作为未匹配到部分的值. 可以理解为mysql中的inner join如果没有就用null.

// 这里a节点没有外向关系,所以x会返回null; 因为x不存在,对应的属性name也会返回null
match(a:Movie {title:"haha"}) optional match (a)-->(x) return x,x.name
where的作用

如果where是和match, optinal match结合, 是添加约束的作用
如果where和with, start 结合, 则是过滤结果的作用
添加约束和过滤结果是不一样的, 一个是查询的过程中起作用, 一个是查询完之后起作用

14. 其它

动态节点属性过滤

以方括号语法形式使用动态计算的值来过滤属性

match (n) where n[toLower("Name")] = "nick" return n

经测试, n[‘name’]和n.name的运行效果没区别,

toLower(n.name)和n[toLower(“Name”)]运行效果也没区别.

toLower(‘Name’)又不可能时把Name变成name, 意思肯定是toLower(n.name), 那为什么方括号中的写法这么奇怪?

属性检查(exist()函数)
match(n) where exists(n.name)  return n, n.name
反引号 `

特殊字符可以用反引号括起来

别名 as

可以用AS起别名

15. 字符串

私以为正则表达式就够用了.

以…开始、结尾,包含… starts with, ends with, contains
match(n) where n.name starts with "N" return n
match(n) where n.name ends with "k" return n
match(n) where n.name contains "ick" return n
match(n) where not n.name starts with "N" return n  
// 注意 not 是对整个结果取反,不能写成n.name not starts with
正则表达式匹配
match(n) where n.name =~ 'Ni.*'  return n

16. 在约束中添加关系约束

(?原来添加否定的东西在这里!!)

// 把 one -- other 写到where条件里面
match (one:Person {name:"nick"}) ,(other) 
where other.name in ["zhudi"] and (one) -- (other) 
return other

match (one:Person {name:"nick"}), (two) 
where not (one) --> (two) return two	// 返回nick没有连接到的节点

match (n)
where (n) -- ({name:"nick"}) return n	// 

下面是一些普通的and/or

match (n)-[r]-> (m) 
where n.name = "nick" and type(r) =~ "lik."

match (n) 
where n.name = "nick" or n.name is null return n

match (n) 
where n.name = "nick" and n.age is null return n

match (n) where n.age > 20 and n.age < 25 return n

match (n) where 20 < n.age < 25 return n

17. start 通过索引搜索节点或关系

start n = node:nodes("name:A")  return n	// 索引名为nodes,条件是name:A
start n = node:nodes(name="A") return n		// 跟上面一样

start r = relationship:rels(name="address") return r

18. 聚合

统计次数 count
// 统计朋友的朋友的数量
match (me:User)-->(f:User)-->(ff:User)
where me.name = 'nick'
return count(DISTINCT ff), count(ff) // me可能从不同路径到达ff
              
// 计算节点的数量
match (n {name:"nick"})-->(x) return n, count(*)

// 根据关系类型分组统计每个类型出现的次数
match (n {name:"nick"})-[r]->()
return type(r), count(*)	// 返回结果会根据其中最细的项分类
            
// 计算非空值的数量
match (n:User) return count(n.name) // 返回name属性不为空的总数
其它函数

sum() 求和, avg() 平均

percentileDisc(n.age, 0.5) 计算给定值在一个组中的百分比

stdev() 计算标准差(部分样本?) stdevp() 计算标准差(整个样本)

max() 查找数列中的最大值 min() 最小值

collect() 将所有值收集起来放入一个列表中, NULL值将被忽略(感觉这是展示数据的好方法)

distinct 所有聚合函数都可以用的聚合修饰符, 去重

19. 添加和删除属性

设置属性
match (n:Person {name:"nick"}) set n.age = 23 return n
设置标签
match (n {name:"nick"}) set n:Person return n,labels(n)
删除属性
match(n:Person {name:"nick"}) set n.age = null return n
match(n:Person {name:"nick"}) remove n.age return n
删除标签
match(n:Person {name:"h1"})
remove n:Person
return n

match(n:Person:People {name:"h1"})
remove n:Person:People
return n
复制属性
match(a {name:"h1"}),(b {name:"h2"})
set a = b
return a,b

这样会把b的所有属性全部设置到a上面,a上面的所有属性都会被删除

以map的方式添加属性
match (a:Person {name:"nick"})
set a+={age:23,gender:"boy"}
return a
以覆盖的方式设置属性
match(n:Person {name:"h1"})
set n = {name:"h2",gender:"boy"}
return n

20. 添加和删除节点

DELETE
match (n:Person) delete n	// 删除所有Person

match(n) detach delete n    
// 上面detach 是删除节点的同时并删除其所有的关系
MERGE

merge匹配已存在的节点, 如果节点不存在就创建新的并绑定它.

注意MERGE的语句无论多长, 只要语句中不存在的都会被创建.

?merge on 不存在时候触发 on 后面的语句

// 假设没有nick这个节点,下面的语句会创建一个出来
merge (n:Person {name:"nick"}) return n,labels(n)

// 重复的只会创建一次,因为创建之后就存在了,可以匹配到,就不会再创建
match(people:Person) merge(city:City {name:people.bornIn}) return city

// MERGE 和 CREATE 搭配 ,即MERGE匹配不存在的节点去创建的时候设置属性
merge(nick:Person {name:"Nick"})
on create set nick.like = "Judy", nick.created = timestamp()
return nick

// MERGE 和 MATCH 搭配,即 MERGE 匹配已存在的节点匹配的时候设置属性
merge(nick:Person {name:"Nick"})
on match set nick.age = 23
return nick
CREATE UNIQUE

CREATE UNIQUE类似于MERGE,尽可能的匹配,然后创建未匹配到的
再保证唯一性这方面 CREATE UNIQUE 比MERGE更强.
CREATE UNIQUE 尽可能的减少对图的改变,充分利用已有的图
还有很重要的一点是 CREATE UNIQUE 假设模式是唯一性的,如果有多个匹配的子图可以找到,会报错

match (nick {name:"Nick"})
create unique (nikc)->[:love]-(judy {name:"judy"})
return judy

match (nick {name:"Nick"}), (judy {name:"Judy"})
create unique (nick)-[l:love]->(judy)
return l

21. FOR EACH

给所有节点设置一个属性值, 注意数据的传递要用到管道符

MATCH (n:Person)
FOREACH (p in nodes(n)) | SET p.age = 20

将列表中的人全部添加为A的朋友

MATCH (A {name:"A"})
FOREACH (name IN ["Mike","B","C"]) | CREATE (A)-[:FRIEND]->({name: name})

22. WITH ?

WITH语句将分段查询的部分链接在一起, 将查询结果从一部分以管道形式传递给另外一部分作为开始点

使用WITH可以在将结果传递到后续查询之前对结果进行操作. 操作可以改变结果的形式或者数量, 常见的用法是限制传递给其它MATCH语句的结果数

WITH聚合之后通过WHERE进行过滤

返回nick的f(朋友)和ff(朋友的朋友)关系中, f出现次数两次以上的返回

MATCH  (nick {name:"Nick"}) -- (f) -- (ff)
WITH f, count(*) as other_count	// 按逗号两边粒度最细的拆分, 是一个f对count的表格
WHERE other_count > 2
RETURN others
WITH配合排序, 进行数据限制
MATCH(n)
WITH n
ORDER BY n.age ASC LIMIT 3
RETURN collect(n.name)
限制路径搜索的分支
MATCH (n {name:"Nick"}) -- (m)
WITH m
ORDER BY m.name ASC LIMIT 1
MATCH (m) -- (0)
RETURN m.name

23. UNWIND

将列表展开为一个行的序列
UNWIND [1,2,3] as x
RETURN x

结果:
x
1
2
3

将一个重复值列表转为一个集合

UNWIND [1,1,1,2,2,3] as x
WITH DISTINCT x
RETURN collect(x) as set

结果
set
[1,2,3]

24. UNION

UNION和UNION ALL都是将多个查询结果组合起来。
不同的是UNION会移除重复的行。UNION ALL不会移除重复的行。
注意:无论是使用UNION还是UNION ALL都要保证查询到的 列的名称列的数量 要完全一致。

MATCH(n:Actor)
RETURN n.name AS name
UNION ALL
MATCH (m:Movie)
RETURN m.title AS name

结果:
A
B
C
C

MATCH(n:Actor)
RETURN n.name AS name
UNION 
MATCH (m:Movie)
RETURN m.title AS name

结果:
A
B
C

25. 函数

函数名功能类型
all()如果断言满足列表中的所有元素, 返回真
any()如果断言至少满足列表中的一个元素, 返回真
none()如果断言不适用于列表中的任何一个元素, 返回真
single()如果断言只满足列表中的一个元素, 返回真
exists()如果数据库中存在该模式或节点中存在该属性, 返回真
size()返回列表元素的个数. RETURN size((a)–>()–>()) as num
head()返回列表中第一个元素
last()返回列表中最后一个元素
tail()返回列表中除了首元素之外的所有元素
length()返回路径长度 / 字符串长度.
type()返回关系的类型(标签)
labels()返回节点的标签
id()返回关系或节点的ID
properties()返回关系或节点的属性(key, value) 以map的形式
keys()返回关系或节点的所有属性的键(key) 以字符串列表的形式
startNode()返回关系的开始节点
endNode()返回关系的结束节点
coalesce()返回表达式列表中第一个非空的值
timestamp()返回时间戳
toInt()将参数转换为整数. 字符串解析失败返回NULL
toFloat()将参数转换为浮点数
nodes(path)返回模式中的所有节点(可能含关系)List
relationships()返回模式中的所有关系(不含节点)List

?Path和RelationShip是什么关系?

x=§–§ 可以用 relationships() 转换成关系List. 所以Path是带节点的, 关系是不带节点的. 模式和路径可能是一个意思?

extract

遍历一个列表, 每个值都执行一个表达式, 将结果返回组成List

match p = (a)-->(b)
return extract(n IN nodes(p) |  n.age)  as age_list
filter

对列表中的元素进行过滤

返回a节点的array属性中的字符串长度大于3的元素列表

match(a)
where a.name = "nick"
return filter(str in keys(a) where length(str) > 3) 
range

生成列表 语法 range(start,end[,step])

reduce

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算

语法:
reduce(accumulator=initial,variable IN list | expression)
参数:
accumulator:用于累加每次迭代的部分结果
initial:累加结果的初始值
variable :变量名
list:列表
expression:表达式

函数名功能
abs()绝对值
ceil()上取整
floor()下取整
round()四舍五入
sign()返回正负(-1,0,1)
rand()返回[0, 1]之间的随机数
log()自然对数(以 e e e为底)
log10()以10为底的对数
exp(n)返回 e e e的n次方
sqrt()平方根
sin()正弦
cos()余弦
tan()正切
cot()余切
asin()反正弦
acos()反余弦
atan()反正切
pi() π \pi π
replace 字符串替换

语法: replace(original,search,replace)

original:源字符串
search:期望被替换的字符串
replace:用于替换的字符串

substring 子字符串

语法: substring(original,start[,length])

original:原字符串
start:子串的开始位置,索引从0开始
length:子串的长度,长度不写默认从开始位置到最后

函数名功能
left(original,length)返回原字符串左边指定长度的子串
right(original,length)返回源字符串右边指定长度的子串
ltrim(str)移除字符串左侧的空白字符串返回,不改变原字符串
rtrim(str)移除字符串右侧的空白字符
trim(str)移除两侧的空白字符
lower(str)返回小写的原字符串
upper(str)返回大写的原字符串
split(original,splitPattern)以指定的字符分隔字符串返回列表
reverse()返回原字符串的倒序字符串

26. 索引和约束

创建索引

create index on :Person(name)
在拥有Person标签的所有节点的name属性上创建了索引

删除索引

DROP INDEX ON :Person(name)
删除在拥有Person标签的所有节点的name属性索引

创建唯一性约束

使用IS UNIQUE语法创建约束,它能确保数据库中拥有特定标签和属性值的节点是唯一的

CREATE CONSTRAINT ON (book:Book) ASSERT book.isbn IS UNIQUE

需要注意: 在创建唯一性约束之前,如果数据库中已经存在了不唯一的数据,将会报错,创建唯一性约束将失败.在创建唯一性约束之后,如果再次添加已经存在的数据,那么会报错,添加失败

删除唯一性约束
DROP CONSTRAINT ON {book:Book} ASSERT book.isbn IS UNIQUE
创建存在性约束

唯一性是保证属性值唯一.存在性是保证属性key是唯一且必须存在的,即标签下的所有节点都应该含有这个属性

CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.isbn)
删除存在性约束
CREATE CONSTRAINT ON (book:Book) ASSERT exists(book.isbn)

注意:使用REMOVE 不能删除有存在性制约的属性.删除会报错,同样在创建存在性约束之前已经存在不存在该属性的节点创建存在性约束会失败.

创建关系属性存在性约束

使用 ASSERT exists() 创建关系属性存在性约束,可确保特定类型的所有关系都有一个特定的属性

CREATE CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.day)
删除关系存在性约束
DROP CONSTRAINT ON ()-[like:LIKED]-() ASSERT exists(like.day)
查询指定索引

指定索引的时候会更改默认的开始查询点

MATCH(a:Person{name:"nick"})-[r:like]->(b:Person {name:"judy"})
USING INDEX 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值