Перейти к содержанию

UPDATE

Каждая операция UPDATE ограничена одной коллекцией, и имя коллекции не должно быть динамическим. В одном запросе AQL допускается только один оператор UPDATE для одной коллекции, и за ним не могут следовать операции чтения или записи, обращающиеся к той же коллекции, операции обхода или функции AQL, которые могут читать документы.

Нельзя обновлять системные атрибуты _id, _key и _rev, но можно обновлять атрибуты _from и _to.

Обновление документа изменяет номер ревизии документа (атрибут _rev) на генерируемое сервером значение.

Синтаксис

Для операции обновления существует два синтаксиса:

UPDATE document IN collection
UPDATE keyExpression WITH document IN collection

Оба варианта могут опционально заканчиваться предложением OPTIONS { ... }.

collection должно содержать имя коллекции, в которой должен быть обновлен документ.

document должен быть объектом и содержать атрибуты и значения для обновления. Атрибуты, которые еще не существуют в хранимом документе, добавляются к нему. Существующие атрибуты устанавливаются в предоставленные значения атрибутов (за исключением неизменяемых атрибутов _id и _key и управляемого системой атрибута _rev). Операция оставляет нетронутыми другие существующие атрибуты, не указанные в document. Это отличает операцию UPDATE от операции REPLACE, которая затрагивает все атрибуты хранимого документа, а не только те, которые вы указали в операции.

Под-атрибуты рекурсивно объединяются по умолчанию, но вы можете позволить атрибутам верхнего уровня заменить существующие, отключив опцию mergeObjects.

UPDATE <document> IN <collection>

При использовании первого синтаксиса объект document должен иметь атрибут _key с ключом документа. Существующий документ с этим ключом обновляется атрибутами, предоставленными объектом document (за исключением системных атрибутов _id, _key и _rev).

Следующий запрос добавляет или обновляет атрибут name документа, идентифицированного ключом my_key в коллекции users. Ключ передается через атрибут _key наряду с другими атрибутами:

1
UPDATE { _key: "my_key", name: "Jon" } IN users

Следующий запрос является некорректным, поскольку объект не содержит атрибута _key и поэтому невозможно определить обновляемый документ:

1
UPDATE { name: "Jon" } IN users

Вы можете объединить операцию UPDATE с циклом FOR для определения необходимых ключевых атрибутов, как показано ниже:

1
2
FOR u IN users
  UPDATE { _key: u._key, name: CONCAT(u.firstName, " ", u.lastName) } IN users

Обратите внимание, что операции UPDATE и FOR независимы друг от друга, и u не определяет автоматически документ для оператора UPDATE. Таким образом, следующий запрос является некорректным:

1
2
FOR u IN users
  UPDATE { name: CONCAT(u.firstName, " ", u.lastName) } IN users

UPDATE <keyExpression> WITH <document> IN <collection>

При использовании второго синтаксиса документ для обновления определяется keyExpression. Это может быть либо строка с ключом документа, либо объект, содержащий атрибут _key с ключом документа, либо выражение, которое оценивается как одно из этих двух. Существующий документ с этим ключом обновляется атрибутами, предоставленными объектом document (за исключением системных атрибутов _id, _key и _rev).

Следующий запрос добавляет или обновляет атрибут name документа, идентифицированного ключом my_key в коллекции users. Ключ передается в виде строки в keyExpression. Атрибуты для добавления или обновления передаются отдельно как объект document:

1
UPDATE "my_key" WITH { name: "Jon" } IN users

Объект document может содержать атрибут _key, но он игнорируется.

Вы не можете определить документ для обновления с помощью атрибута _id или передать идентификатор документа в виде строки (например, "users/john"). Однако вы можете использовать PARSE_IDENTIFIER(<id>).key в качестве keyExpression для получения ключа документа в виде строки:

1
2
LET key = PARSE_IDENTIFIER("users/john").key
UPDATE key WITH { ... } IN users

Сравнение синтаксисов

Оба синтаксиса операции UPDATE позволяют вам определить документ для изменения и атрибуты для добавления или обновления. Документ для обновления эффективно идентифицируется ключом документа в сочетании с указанной коллекцией.

Операция UPDATE поддерживает различные способы указания ключа документа. Вы можете выбрать наиболее удобный для вас вариант синтаксиса.

Следующие запросы эквивалентны:

1
2
FOR u IN users
  UPDATE u WITH { name: CONCAT(u.firstName, " ", u.lastName) } IN users
1
2
FOR u IN users
  UPDATE u._key WITH { name: CONCAT(u.firstName, " ", u.lastName) } IN users
1
2
FOR u IN users
  UPDATE { _key: u._key } WITH { name: CONCAT(u.firstName, " ", u.lastName) } IN users
1
2
FOR u IN users
  UPDATE { _key: u._key, name: CONCAT(u.firstName, " ", u.lastName) } IN users

Выражения динамических ключей

Операция UPDATE может обновлять произвольные документы, используя любой из двух синтаксисов:

1
2
FOR i IN 1..1000
  UPDATE { _key: CONCAT("test", i), name: "Paula" } IN users
1
2
FOR i IN 1..1000
  UPDATE CONCAT("test", i) WITH { name: "Paula" } IN users

Нацелить на другую коллекцию

Документы, которые изменяет операция UPDATE, могут находиться в другой коллекции, чем те, которые были созданы предыдущей операцией FOR:

1
2
3
FOR u IN users
  FILTER u.active == false
  UPDATE u WITH { status: "inactive" } IN backup

Обратите внимание, как документы считываются из коллекции users, но обновляются в другой коллекции backup. Чтобы это работало, обе коллекции должны использовать совпадающие ключи документов.

Хотя переменная u содержит целый документ, она используется только для определения целевого документа. Атрибут _key объекта извлекается, и целевой документ определяется только значением строки ключа документа и указанной коллекцией операции UPDATE (backup). Ссылка на исходную коллекцию (users) отсутствует.

Использование текущего значения атрибута документа

Псевдопеременная OLD не поддерживается в выражениях WITH (она доступна после UPDATE). Чтобы получить доступ к текущему значению атрибута, обычно можно обратиться к документу через переменную цикла FOR, который используется для итерации по коллекции:

1
2
3
4
FOR doc IN users
  UPDATE doc WITH {
    fullName: CONCAT(doc.firstName, " ", doc.lastName)
  } IN users

Если цикла нет, потому что обновляется только один документ, то может не быть переменной, как указано выше (doc), которая позволит вам ссылаться на обновляемый документ:

1
UPDATE "john" WITH { ... } IN users

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

1
2
3
4
LET doc = FIRST(FOR u IN users FILTER u._key == "john" RETURN u)
UPDATE doc WITH {
  fullName: CONCAT(doc.firstName, " ", doc.lastName)
} IN users

Таким образом можно изменить существующий атрибут на основе его текущего значения, например, увеличить счетчик:

1
2
3
UPDATE doc WITH {
  karma: doc.karma + 1
} IN users

Если атрибут karma еще не существует, doc.karma оценивается в null. Выражение null + 1 приводит к тому, что новый атрибут karma устанавливается в 1. Если атрибут уже существует, то он увеличивается на 1.

Массивы также могут быть изменены:

1
2
3
UPDATE doc WITH {
  hobbies: PUSH(doc.hobbies, "swimming")
} IN users

Если атрибут hobbies еще не существует, его удобно инициализировать как ["swimming" ] и в противном случае расширить.

Параметры запроса

Вы можете опционально установить параметры запроса для операции UPDATE:

1
UPDATE ... IN users OPTIONS { ... }

ignoreErrors

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

1
2
3
4
FOR i IN 1..1000
  UPDATE CONCAT("test", i)
  WITH { foobar: true } IN users
  OPTIONS { ignoreErrors: true }

Вы не можете изменять системные атрибуты _id, _key и _rev, но попытки изменить их игнорируются и не считаются ошибками.

keepNull

При обновлении атрибута до значения null, ArangoDB не удаляет атрибут из документа, а сохраняет это значение null. Чтобы удалить атрибуты в операции обновления, задайте им значение null и установите опцию keepNull в false. При этом удаляются указанные вами атрибуты, но не все ранее сохраненные атрибуты со значением null:

1
2
3
FOR u IN users
  UPDATE u WITH { foobar: true, notNeeded: null } IN users
  OPTIONS { keepNull: false }

Приведенный выше запрос удаляет атрибут notNeeded из документов и нормально обновляет атрибут foobar.

mergeObjects

Опция mergeObjects управляет тем, объединяется ли содержимое объекта, если атрибут объекта присутствует как в запросе UPDATE, так и в обновляемом документе.

Следующий запрос устанавливает атрибут name обновленного документа в то же значение, которое указано в запросе. Это происходит из-за того, что опция mergeObjects установлена в false:

1
2
3
4
5
FOR u IN users
  UPDATE u WITH {
    name: { first: "foo", middle: "b.", last: "baz" }
  } IN users
  OPTIONS { mergeObjects: false }

Напротив, следующий запрос объединяет содержимое атрибута name в исходном документе со значением, указанным в запросе:

1
2
3
4
5
FOR u IN users
  UPDATE u WITH {
    name: { first: "foo", middle: "b.", last: "baz" }
  } IN users
  OPTIONS { mergeObjects: true }

Атрибуты в name, которые присутствуют в обновляемом документе, но не в запросе, сохраняются. Атрибуты, присутствующие в обоих документах, перезаписываются значениями, указанными в запросе.

Примечание: значение по умолчанию для mergeObjects равно true, поэтому нет необходимости указывать его явно.

waitForSync

Для обеспечения долговечности данных при выполнении запроса на обновление существует опция запроса waitForSync:

1
2
3
FOR u IN users
  UPDATE u WITH { foobar: true } IN users
  OPTIONS { waitForSync: true }

ignoreRevs

Чтобы случайно не перезаписать документы, которые были изменены с момента последнего извлечения, вы можете использовать опцию ignoreRevs, чтобы либо позволить ArangoDB сравнивать значение _rev и добиваться успеха, только если они совпадают, либо позволить ArangoDB игнорировать их (по умолчанию):

1
2
3
4
FOR i IN 1..1000
  UPDATE { _key: CONCAT("test", i), _rev: "1287623" }
  WITH { foobar: true } IN users
  OPTIONS { ignoreRevs: false }

exclusive

Движок RocksDB не требует блокировок на уровне коллекции. Различные операции записи в одну и ту же коллекцию не блокируют друг друга, если нет конфликтов запись-запись на одних и тех же документах. С точки зрения разработки приложений может быть желательным иметь исключительный доступ на запись в коллекции, чтобы упростить разработку. Обратите внимание, что записи не блокируют чтения в RocksDB. Исключительный доступ также может ускорить запросы на модификацию, поскольку мы избегаем проверки конфликтов.

Используйте опцию exclusive для достижения этого эффекта на основе каждого запроса:

1
2
3
4
FOR doc IN collection
  UPDATE doc
  WITH { updated: true } IN collection
  OPTIONS { exclusive: true }

refillIndexCaches

Нужно ли обновлять существующие записи в кэше границ в памяти при обновлении документов границ.

1
2
UPDATE { _key: "123", _from: "vert/C", _to: "vert/D" } IN edgeColl
  OPTIONS { refillIndexCaches: true }

Возвращение измененных документов

При желании можно вернуть документы, измененные запросом. В этом случае за операцией UPDATE должна следовать операция RETURN. Допускаются также промежуточные операции LET. Эти операции могут ссылаться на псевдопеременные OLD и NEW. Псевдопеременная OLD ссылается на ревизии документа до обновления, а NEW - на ревизии документа после обновления.

И OLD, и NEW содержат все атрибуты документа, даже те, которые не указаны в выражении обновления.

1
2
3
4
UPDATE document IN collection options RETURN OLD
UPDATE document IN collection options RETURN NEW
UPDATE keyExpression WITH document IN collection options RETURN OLD
UPDATE keyExpression WITH document IN collection options RETURN NEW

Ниже приведен пример использования переменной с именем previous для получения исходных документов до их модификации. Для каждого измененного документа возвращается ключ документа.

1
2
3
4
FOR u IN users
  UPDATE u WITH { value: "test" } IN users
  LET previous = OLD
  RETURN previous._key

Следующий запрос использует псевдо-значение NEW, чтобы вернуть обновленные документы без некоторых системных атрибутов:

1
2
3
4
FOR u IN users
  UPDATE u WITH { value: "test" } IN users
  LET updated = NEW
  RETURN UNSET(updated, "_key", "_id", "_rev")

Также можно вернуть и OLD, и NEW:

1
2
3
FOR u IN users
  UPDATE u WITH { value: "test" } IN users
  RETURN { before: OLD, after: NEW }

Транзакционность

На одном сервере обновления выполняются транзакционно по принципу "все или ничего".

Если используется движок RocksDB и включены промежуточные фиксации, запрос может выполнять промежуточные фиксации транзакций в случае, если запущенная транзакция (AQL-запрос) достигает заданных пороговых значений размера. В этом случае операции запроса, выполненные до этого момента, фиксируются и не откатываются в случае последующего отмены/отката. Это поведение можно контролировать, изменяя настройки промежуточной фиксации для движка RocksDB.

Для коллекций с шардированием вся операция запроса и/или обновления может не быть транзакционной, особенно если она затрагивает разные шарды и/или DB-серверы.

Комментарии