Статьи

Neo4j’s Cypher: знакомство с MERGE

Я пытался получить вид Cypher «s функции MERGE и начал с написания небольшого файла для импорта некоторых людей со случайными свойствами , используя Java-Факер библиотеку.

public class Merge {
    private static Label PERSON = DynamicLabel.label("Person");
 
    public static void main(String[] args) throws IOException {
        File dbFile = new File("/tmp/test-db");
        FileUtils.deleteRecursively(dbFile);
 
        Faker faker = new Faker();
        Random random = new Random();
        GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase(dbFile.getPath());
 
        Transaction tx = db.beginTx();
 
        for (int i = 0; i < 100000; i++) {
            Node person = db.createNode(PERSON);
 
            person.setProperty("name", faker.name());
            person.setProperty("firstName", faker.firstName());
            person.setProperty("lastName", faker.lastName());
            person.setProperty("country", faker.country());
            person.setProperty("age", random.nextInt(50));
        }
 
        tx.success();
        tx.close();
    }
}

Мы можем написать следующий запрос, чтобы получить образец импортированных людей:

$ MATCH (p:Person) RETURN p LIMIT 5;
==> +------------------------------------------------------------------------------------------------------------------+
==> | p                                                                                                                |
==> +------------------------------------------------------------------------------------------------------------------+
==> | Node[1344]{name:"Benton Swaniawski",firstName:"Rossie",lastName:"Ankunding",country:"Guadeloupe",age:30}         |
==> | Node[1345]{name:"Dagmar Bartell",firstName:"Ashlynn",lastName:"Watsica",country:"French Guiana",age:35}          |
==> | Node[1346]{name:"Ms. Missouri Gaylord",firstName:"Muriel",lastName:"Streich",country:"Chile",age:43}             |
==> | Node[1347]{name:"Melvina Heathcote",firstName:"Geovanni",lastName:"Marks",country:"United Arab Emirates",age:33} |
==> | Node[1348]{name:"Brendan Schaefer",firstName:"Dayne",lastName:"Haley",country:"Tokelau",age:24}                  |
==> +------------------------------------------------------------------------------------------------------------------+

Мы можем использовать функцию MERGE, чтобы убедиться, что узел с конкретными свойствами существует, поэтому мы можем написать что-то вроде следующего:

MERGE (p:Person {name: "Benton Swaniawski",
                 firstName:"Rossie",
                 lastName:"Ankunding", 
                 country:"Guadeloupe",
                 age:30})
RETURN p

Если мы посмотрим на вывод PROFILE запроса, мы увидим что-то вроде следующего:

UpdateGraph(commands=["
	MergeNodeAction(
		p,
		Map(firstName(1) -> Literal(Rossie), country(3) -> Literal(Guadeloupe), 
		name(0) -> Literal(Benton Swaniawski), 
		lastName(2) -> Literal(Ankunding), 
		age(4) -> Literal(30)),
		List(Person(0)),
		ArrayBuffer(Property(p,lastName(2)) == Literal(Ankunding), 
		Property(p,name(0)) == Literal(Benton Swaniawski), 
		Property(p,age(4)) == Literal(30), 
		Property(p,country(3)) == Literal(Guadeloupe),
		Property(p,firstName(1)) == Literal(Rossie)),
		List(LabelAction(p,LabelSetOp,List(Person(0))), 
		PropertySetAction(Property(p,name),Literal(Benton Swaniawski)), 
		PropertySetAction(Property(p,country),Literal(Guadeloupe)), 
		PropertySetAction(Property(p,age),Literal(30)), 
		PropertySetAction(Property(p,lastName),Literal(Ankunding)), 
		PropertySetAction(Property(p,firstName),
		Literal(Rossie))),
		List(),
		Some(PlainMergeNodeProducer(<function2>)))"], _rows=1, _db_hits=100219)

Особенность в том, что было 100,219 db_hits, что значительно замедляет запрос.

Если мы хотим использовать MERGE, нам нужно убедиться, что у нас есть индекс или ограничение для одного из свойств, например

$ CREATE INDEX ON :Person(name);
==> +-------------------+
==> | No data returned. |
==> +-------------------+
==> Indexes added: 1

Если мы посмотрим на профиль этого, то увидим, что число db_hits уменьшилось, поскольку теперь он использует индекс для выполнения части поиска, которая требуется MERGE:

UpdateGraph(commands=["
	MergeNodeAction(
		p,
		Map(firstName(1) -> Literal(Rossie), country(3) -> Literal(Guadeloupe), 
		name(0) -> Literal(Benton Swaniawski), 
		... 
		Some(PlainMergeNodeProducer(<function2>)))"], _rows=1, _db_hits=4)

Мы можем пойти еще дальше, включив только свойство, которое действует как наш «ключ» (то есть имя), в первую часть оператора и установив другие свойства только в случае необходимости:

MERGE (p:Person {name: "Benton Swaniawski"})
ON CREATE SET p.firstName="Rossie", 
              p.lastName="Ankunding", 
              p.country="Guadeloupe",
              p.age=30
RETURN p

Если мы профилируем этот запрос, мы увидим, что ситуация улучшилась:

UpdateGraph(commands=["MergeNodeAction(
	p,
	Map(name(0) -> Literal(Benton Swaniawski)),
	List(Person(0)),ArrayBuffer(),
	List(LabelAction(p,LabelSetOp,List(Person(0))), 
	PropertySetAction(Property(p,name),Literal(Benton Swaniawski)), 
	PropertySetAction(Property(p,firstName),Literal(Rossie)), 
	PropertySetAction(Property(p,lastName),Literal(Ankunding)), 
	PropertySetAction(Property(p,country),Literal(Guadeloupe)), 
	PropertySetAction(Property(p,age),Literal(30))),
	List(),
	Some(PlainMergeNodeProducer(<function2>)))"], _rows=1, _db_hits=0)

В некоторых случаях мы можем захотеть обновить свойство каждый раз, когда «ключ» сопоставляется в операторе MERGE, что мы можем сделать так:

MERGE (p:Person {name: "Benton Swaniawski"})
ON MATCH SET p.times = COALESCE(p.times, 0) + 1
RETURN p

Вы также можете использовать MERGE для создания отношений, но сейчас я просто хотел изучить, как это следует использовать в контекстных узлах, которые, как мне кажется, я сейчас понял.

Всегда рад принять советы о том, как сделать что-то лучше!