Как создать новый узел, только если узел не существует в neo4j

Я использую neo4j и vertx.

Я использую neo4j-jdbc.

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

 private final Logger logger = Logger.getLogger(Neo4jRepo.class);
    private Connection conn = null;

    public Neo4jRepo() {
        logger.debug("Neo4jRepo, Init");
        try {
            Class.forName("org.neo4j.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:neo4j://localhost:7474/");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);

        }
    }
..
public boolean addDistanceUserListByForUserId(String userId) {
        try {
            final PreparedStatement ps = conn.prepareStatement( "create (n:User {name:{1}})" );
            ps.setString( 1, "userId" );
            ps.execute();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return true;
    }

Спасибо, Рэй.


person rayman    schedule 17.09.2015    source источник
comment
Если вы используете neo4J в качестве локального хоста, вам следует обратить внимание на встроенный neo4j, который намного лучше работает.   -  person Supamiu    schedule 17.09.2015
comment
neo4j будет внешним сервисом. Мы думали о том, чтобы встроить его. но чем мы теряем графический интерфейс и другие вещи. как вы думаете, встроенный лучше, чем ext?   -  person rayman    schedule 17.09.2015
comment
Встроенный лучше, если вы находитесь на том же сервере :) GUI можно восстановить с помощью встроенного :)   -  person Supamiu    schedule 17.09.2015
comment
на случай, если я работаю удаленно. Как я могу получить Node по идентификатору с помощью соединителя jdbc? любая идея, как сопоставить это?   -  person rayman    schedule 17.09.2015
comment
Никогда не используйте nodeId в качестве данных, вы должны добавить свое собственное свойство id и установить его уникальным с помощью ограничений. В противном случае вы можете получить узел по его идентификатору с помощью шифра (start n=node(‹id›))   -  person Supamiu    schedule 17.09.2015
comment
Я не использую его в качестве данных. Скажем, я хочу знать, существует ли узел (по его идентификатору) и, если он есть, как получить сам узел в той же транзакции? может ли MERGE вернуть мне узел, если он уже существует?   -  person rayman    schedule 17.09.2015


Ответы (2)


Используйте MERGE вместо CREATE:

"MERGE (n:User {name:{1}})"

[ОБНОВЛЕНИЕ 1]

Ниже приведен пример того, как изменить addDistanceUserListByForUserId(), чтобы он возвращал Map<String, Object>, содержащий свойства созданного/существующего User.

public Map<String, Object> addDistanceUserListByForUserId(String userId) {
    try {
        final PreparedStatement ps = conn.prepareStatement(
            "MERGE (n:User {name:{1}}) RETURN n" );
        ps.setString( 1, "userId" );
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            return (Map<String, Object>) rs.getObject("n");
        }
    } catch (SQLException e) { 
        throw new RuntimeException(e);
    } 
    return null; 
}

[ОБНОВЛЕНИЕ 2]

В вашем конкретном случае, поскольку у вас есть ограничение уникальности для :User(name), а существующие узлы User могут иметь свойства, отличные от name, простой запрос MERGE (n:User {name:{1}}) может вызвать ошибку ConstraintViolation, если существующий User с тем же name существует, но также имеет другие свойства.

Чтобы обойти это, попробуйте заменить оператор MERGE более сложным запросом:

OPTIONAL MATCH (n:User { name:{1} })
WITH (CASE WHEN n IS NULL THEN [1] ELSE [] END ) AS todo
FOREACH (x IN todo | CREATE (:User { name:{1} }))
WITH todo
MATCH (n:User { name:{1} })
RETURN n;

Вот объяснение этого запроса:

  • Он использует OPTIONAL MATCH, так что если User с указанным name не найдено, остальная часть запроса не пропускается (но n будет NULL).
  • Затем он создает коллекцию todo, которая сообщает предложению FOREACH, следует ли создавать новый узел User. Предложение FOREACH ничего не делает, если коллекция todo пуста.
  • Между изменяющим предложением (например, FOREACH) и последующим предложением MATCH должно быть предложение WITH. На самом деле нам не нужно ничего передавать вперед, поэтому мы просто используем todo, так как он у нас уже есть под рукой.
  • Нам нужен еще один запрос MATCH, потому что мы хотим вернуть узел, который либо был найден, либо только что создан. Но нет способа получить узел, созданный FOREACH, если мы не сделаем еще один MATCH.
person cybersam    schedule 17.09.2015
comment
Смотрите мой обновленный ответ. Я не тестировал этот код, но похоже, что он должен работать. - person cybersam; 18.09.2015
comment
Это работает, но я использую уникальные ограничения на имя, и у меня есть другое свойство, называемое возрастом. поэтому, если пользователь уже существует с именем, но имеет возраст diff, и я пытаюсь объединиться, я получаю это исключение: Ошибка выполнения оператора (ов) шифра [{code = Neo.ClientError.Schema.ConstraintViolation, message = Node 15 уже существует с label Имя пользователя и свойства=[Jossef]}] - person rayman; 18.09.2015
comment
Я обновил свой ответ предложением, чтобы вы могли обойти проблему ConstraintViolation`. - person cybersam; 18.09.2015

Вы должны использовать MERGE вместо CREATE. Это создаст узел, если он не существует. Если узел уже существует, то MERGE будет действовать как обычная операция MATCH.

Важно! следует использовать только свойства с уникальным ограничением в синтаксисе {} свойств. Neo4j объединит узлы с одинаковыми свойствами в {}.

Если у вас есть дополнительные данные, которые нужно установить на узле после слияния, то:

MERGE (user:User {name: "John", age: 20}) - НЕПРАВИЛЬНЫЙ способ. Поскольку в базе данных нет узла с такой комбинацией свойств name и age, база данных попытается создать этот узел. И это приведет к ConstraintViolation, потому что такое name уже существует.

Правильная версия:

MERGE (user:User {name: "John"}) 
SET user.age = 20

Эта версия сначала будет искать узел (и создавать его при необходимости), а только потом устанавливать свойство на этом узле.

person FylmTM    schedule 18.09.2015