图模型重构
机场数据集
本指南使用一个机场数据集,其中包含 2008 年 1 月美国机场之间的连接。数据以 CSV 文件形式呈现。下面您可以看到数据库的图模型
在导入任何数据之前,您应该在 Airport
标签和 code
属性上创建唯一约束,以确保您不会意外导入重复的机场。以下查询创建约束
CREATE CONSTRAINT airport_id
FOR (airport:Airport) REQUIRE airport.code IS UNIQUE
86 毫秒后可用 0 行,在另外 0 毫秒后消耗。添加了 1 个约束 |
以下查询使用 LOAD CSV
工具从 CSV 文件加载数据
LOAD CSV WITH HEADERS FROM "https://raw.githubusercontent.com/neo4j-contrib/training/master/modeling/data/flights_1k.csv" AS row
MERGE (origin:Airport {code: row.Origin})
MERGE (destination:Airport {code: row.Dest})
MERGE (origin)-[connection:CONNECTED_TO {
airline: row.UniqueCarrier,
flightNumber: row.FlightNum,
date: date({year: toInteger(row.Year), month: toInteger(row.Month), day: toInteger(row.DayofMonth)}),
cancelled: row.Cancelled,
diverted: row.Diverted}]->(destination)
ON CREATE SET connection.departure = localtime(apoc.text.lpad(row.CRSDepTime, 4, "0")),
connection.arrival = localtime(apoc.text.lpad(row.CRSArrTime, 4, "0"))
此查询
-
创建一个带有
Airport
标签的节点,该节点具有一个code
属性,该属性的值来自 CSV 文件中的Origin
列。 -
创建一个带有
Airport
标签的节点,该节点具有一个code
属性,该属性的值来自 CSV 文件中的Dest
列。 -
创建一个类型为
CONNECTED_TO
的关系,该关系根据 CSV 文件中的列具有多个属性。
如果运行此查询,您将看到以下输出
添加了 62 个标签,创建了 62 个节点,设置了 7062 个属性,创建了 1000 个关系,在 376 毫秒后完成。 |
这是一个起始模型,但您可以进行一些改进。
将属性转换为布尔值
CONNECTED_TO
关系上的 diverted
和 cancelled
属性包含 1
和 0
的字符串值。由于这些值代表布尔值,因此您可以使用 apoc.refactor.normalizeAsBoolean
过程将值从字符串转换为布尔值。
以下查询对 diverted
属性执行转换
MATCH (:Airport)-[connectedTo:CONNECTED_TO]->(:Airport)
CALL apoc.refactor.normalizeAsBoolean(connectedTo, "diverted", ["1"], ["0"])
RETURN count(*)
count(*) |
---|
1000 |
以下查询对 cancelled
属性执行转换
MATCH (origin:Airport)-[connectedTo:CONNECTED_TO]->(departure)
CALL apoc.refactor.normalizeAsBoolean(connectedTo, "cancelled", ["1"], ["0"])
RETURN count(*)
count(*) |
---|
1000 |
如果要更新大量关系,在尝试在一个事务中重构所有关系时,可能会收到 OutOfMemory
异常。因此,您可以使用 apoc.periodic.iterate
过程按批处理关系。以下查询对 cancelled
和 reverted
属性在同一查询中执行此操作
UNWIND ["cancelled", "reverted"] AS propertyToDelete
CALL apoc.periodic.iterate(
"MATCH (:Airport)-[connectedTo:CONNECTED_TO]->(:Airport) RETURN connectedTo",
"CALL apoc.refactor.normalizeAsBoolean(connectedTo, $propertyToDelete, ['1'], ['0'])
RETURN count(*)",
{params: {propertyToDelete: propertyToDelete}, batchSize: 100})
YIELD batches
RETURN propertyToDelete, batches
有关 UNWIND
子句的更多详细信息,请参阅 Cypher 手册 → UNWIND 页面。
先前查询中的 apoc.periodic.iterate
过程接受三个参数
-
一个外部 Cypher 查询,它找到并返回要处理的
CONNECTED_TO
关系流。 -
一个内部 Cypher 查询,它处理这些
CONNECTED_TO
关系,通过使用apoc.refactor.normalizeAsBoolean
过程将指定属性上的所有值转换为布尔值,该过程本身接受多个参数-
属性存在的实体
-
要规范化的属性的名称
-
应该被认为是
true
的值列表 -
应该被认为是
false
的值列表
-
-
过程的配置值,包括
-
params
- 传递给这些 Cypher 查询的参数。 -
batchSize
- 控制单个事务中运行的内部语句数。
-
运行查询后,您将看到以下输出
propertyToDelete | batches |
---|---|
"cancelled" |
10 |
"reverted" |
10 |
完成此操作后,您可以编写以下查询以返回所有取消的连接
MATCH (origin:Airport)-[connectedTo:CONNECTED_TO]->(destination)
WHERE connectedTo.cancelled
RETURN origin.code AS origin,
destination.code AS destination,
connectedTo.date AS date,
connectedTo.departure AS departure,
connectedTo.arrival AS arrival
origin | destination | date | departure | arrival |
---|---|---|---|---|
"LAS" |
"OAK" |
2008-01-03 |
07:00 |
08:30 |
"LAX" |
"SFO" |
2008-01-03 |
09:05 |
10:25 |
"LAX" |
"OAK" |
2008-01-03 |
11:00 |
12:15 |
"LAX" |
"SJC" |
2008-01-03 |
19:30 |
20:35 |
"LAX" |
"SFO" |
2008-01-03 |
16:20 |
17:40 |
"MDW" |
"STL" |
2008-01-03 |
11:10 |
12:15 |
"MDW" |
"BDL" |
2008-01-03 |
08:45 |
11:40 |
"MDW" |
"DTW" |
2008-01-03 |
06:00 |
08:05 |
"MDW" |
"STL" |
2008-01-03 |
14:45 |
15:50 |
"MDW" |
"BNA" |
2008-01-03 |
19:25 |
20:45 |
"OAK" |
"BUR" |
2008-01-03 |
13:10 |
14:15 |
"OAK" |
"BUR" |
2008-01-03 |
17:05 |
18:10 |
从关系创建节点
使用现有数据模型,编写一个查询来查找特定航班可能是一项复杂的任务。这是因为这里的航班以关系的形式表示。但是,您可以通过从 CONNECTED_TO
关系中存储的属性创建 Flight
节点来更改模型
以下查询执行此重构
CALL apoc.periodic.iterate(
"MATCH (origin:Airport)-[connected:CONNECTED_TO]->(destination:Airport) RETURN origin, connected, destination",
"CREATE (flight:Flight {
date: connected.date,
airline: connected.airline,
number: connected.flightNumber,
departure: connected.departure,
arrival: connected.arrival,
cancelled: connected.cancelled,
diverted: connected.diverted
})
MERGE (origin)<-[:ORIGIN]-(flight)
MERGE (flight)-[:DESTINATION]->(destination)
DELETE connected",
{batchSize: 100})
此查询使用 apoc.periodic.iterate
过程,以便您可以按批处理而不是在一个事务中执行重构。该过程接受三个参数
-
一个外部 Cypher 查询,它找到并返回要处理的
CONNECTED_TO
关系流以及需要处理的始发机场和目的机场。 -
一个内部 Cypher 查询,它处理这些实体,创建一个带有
Flight
标签的节点,并从该节点到始发机场和目的机场创建关系。 -
batchSize
配置,它将单个事务中运行的内部语句数设置为100
。
如果执行查询,您将看到以下输出
batches | total | timeTaken | committedOperations | failedOperations | failedBatches | retries | errorMessages | batch | operations | wasTerminated |
---|---|---|---|---|---|---|---|---|---|---|
10 |
1000 |
0 |
1000 |
0 |
0 |
0 |
{} |
{total: 10, committed: 10, failed: 0, errors: {}} |
{total: 1000, committed: 1000, failed: 0, errors: {}} |
FALSE |
您也可以使用 apoc.refactor.extractNode
过程执行此重构。
CALL apoc.periodic.iterate(
"MATCH (origin:Airport)-[connected:CONNECTED_TO]->(destination:Airport)
RETURN origin, connected, destination",
"CALL apoc.refactor.extractNode([connected], ['Flight'], 'DESTINATION', 'ORIGIN')
YIELD input, output, error
RETURN input, output, error",
{batchSize: 100});
这与前面的查询相同,但外部 Cypher 查询使用 apoc.refactor.extractNode
过程创建 Flight
节点,并创建到始发机场和目的机场的关系。如果运行此查询,我们将看到以下输出
batches | total | timeTaken | committedOperations | failedOperations | failedBatches | retries | errorMessages | batch | operations | wasTerminated |
---|---|---|---|---|---|---|---|---|---|---|
10 |
1000 |
0 |
1000 |
0 |
0 |
0 |
{} |
{total: 10, committed: 10, failed: 0, errors: {}} |
{total: 1000, committed: 1000, failed: 0, errors: {}} |
FALSE |
从属性创建节点
目前,航空公司名称存储在 Flight
节点上的 airline
属性中。这意味着,如果您想返回所有航空公司的流,您必须扫描每个航班,并检查每个航班的 airline
属性。
您可以通过为每个航空公司创建一个带有 Airline
标签的节点来使此任务更简单、更高效
首先,在 Airline
标签和 name
属性上创建一个约束,以避免重复的航空公司节点
CREATE CONSTRAINT airline_id
FOR (airline:Airline) REQUIRE airline.name IS UNIQUE
107 毫秒后可用 0 行,在另外 0 毫秒后消耗。添加了 1 个约束 |
现在,您可以运行以下查询来执行重构
CALL apoc.periodic.iterate(
'MATCH (flight:Flight) RETURN flight',
'MERGE (airline:Airline {name:flight.airline})
MERGE (flight)-[:AIRLINE]->(airline)
REMOVE flight.airline',
{batchSize:10000, iterateList:true, parallel:false}
)
您再次使用 apoc.periodic.iterate
过程,并使用以下参数
-
一个外部 Cypher 语句,它返回要处理的
Flight
节点流。 -
这是一个内部 Cypher 语句,用于处理
Flight
节点并根据airline
属性创建Airline
节点。它还从Flight
节点到Airline
节点创建了一个AIRLINE
关系。之后,您可以从Flight
节点中删除airline
属性。
如果您运行此查询,输出将如下所示:
batches | total | timeTaken | committedOperations | failedOperations | failedBatches | retries | errorMessages | batch | operations | wasTerminated |
---|---|---|---|---|---|---|---|---|---|---|
1 |
1000 |
0 |
1000 |
0 |
0 |
0 |
{} |
{total: 1, committed: 1, failed: 0, errors: {}} |
{total: 1000, committed: 1000, failed: 0, errors: {}} |
FALSE |
然后,您可以编写以下查询来查找航空公司和涉及每家航空公司的航班数量
MATCH (airline:Airline)<-[:AIRLINE]-(:Flight)
RETURN airline.name AS airline, count(*) AS numberOfFlights
此查询与之前查询的功能相同,但外部 Cypher 查询使用apoc.refactor.extractNode
过程创建Flight
节点并创建到始发机场和目的机场的关系。如果您运行此查询,您将获得以下输出:
航空公司 | 航班数 |
---|---|
"WN" |
1000 |