RethinkDB - обновление вложенного массива
У меня есть таблица опроса, которая выглядит так:
{
id: Id,
date: Date,
clients: [{
client_id: Id,
contacts: [{
contact_id: Id,
score: Number,
feedback: String,
email: String
}]
}]
}
Мне нужно обновить поля score
и feedback
под определенным контактом. В настоящее время я запускаю обновление следующим образом:
function saveScore(obj){
var dfd = q.defer();
var survey = surveys.get(obj.survey_id);
survey
.pluck({ clients: 'contacts' })
.run()
.then(results => {
results.clients.forEach((item, outerIndex) => {
item.contacts.forEach((item, index, array) => {
if(Number(item.contact_id) === Number(obj.contact_id)) {
array[index].score = obj.score;
console.log(outerIndex, index);
}
});
});
return survey.update(results).run()
})
.then(results => dfd.resolve(results))
.catch(err => dfd.resolve(err));
return dfd.promise;
};
Когда я смотрю на метод update, он указывает, как обновлять пары вложенных ключей: значение. Тем не менее, я не могу найти примеры для обновления отдельного элемента в массиве.
Есть ли лучший и, надеюсь, более чистый способ обновления элементов во вложенном массиве?
Ответы
Ответ 1
Вам может потребоваться получить массив filter
из требуемого значения в массиве, а затем добавить его снова в массив. Затем вы можете передать обновленный массив методу update
.
Пример
Скажем, у вас есть документ с двумя клиентами, у которых есть name
и score
, и вы хотите обновить счет в одном из них:
{
"clients": [
{
"name": "jacob" ,
"score": 200
} ,
{
"name": "jorge" ,
"score": 57
}
] ,
"id": "70589f08-284c-495a-b089-005812ec589f"
}
Вы можете получить этот конкретный документ, запустить команду update
с помощью анонимной функции, а затем передать новый обновленный массив в свойство clients
.
r.table('jacob').get("70589f08-284c-495a-b089-005812ec589f")
.update(function (row) {
return {
// Get all the clients, expect the one we want to update
clients: row('clients').filter(function (client) {
return client('name').ne('jorge')
})
// Append a new client, with the update information
.append({ name: 'jorge', score: 57 })
};
});
Я действительно думаю, что это немного громоздко, и, вероятно, более приятный и элегантный способ сделать это, но это должно решить вашу проблему.
Схема базы данных
Возможно, стоит создать таблицу contacts
для всех ваших контактов, а затем выполнить какое-то соединение с вами. Тогда ваше свойство contacts
в вашем массиве clients
будет выглядеть примерно так:
{
id: Id,
date: Date,
clients: [{
client_id: Id,
contact_scores: {
Id: score(Number)
},
contact_feedbacks: {
Id: feedback(String)
}
}]
}
Ответ 2
он работает для меня
r.table(...).get(...).update({
contacts: r.row('Contacts').changeAt(0,
r.row('Contacts').nth(0).merge({feedback: "NICE"}))
})
Ответ 3
схема базы данных
{
"clients": [
{
"name": "jacob" ,
"score": 200
} ,
{
"name": "jorge" ,
"score": 57
}
] ,
"id": "70589f08-284c-495a-b089-005812ec589f"
}
то вы можете сделать это с помощью запросов map
и branch
.
r.db('users').table('participants').get('70589f08-284c-495a-b089-005812ec589f')
.update({"clients": r.row('clients').map(function(elem){
return r.branch(
elem('name').eq("jacob"),
elem.merge({ "score": 100 }),
elem)})
})
Ответ 4
Решение ReQL
Создание запроса для обновления массива объектов JSON на месте - довольно сложный процесс в ReThinkDB (и в большинстве языков запросов). Лучшее (и единственное) решение в ReQL, о котором я знаю, - это использовать комбинацию функций update
, offsetsOf
, do
, changeAt
и merge
. Это решение будет сохранять порядок объектов в массиве и изменять значения только тех объектов, которые совпадают в методах offsetsOf
.
Следующий код (или что-то подобное) можно использовать для обновления массива объектов (т. clients
), которые содержат массив объектов (т.е. contracts
).
Где '%_databaseName_%'
, '%_tableName_%'
, '%_documentUUID_%'
, %_clientValue_%
и %_contractValue_%
должны быть обеспечены.
r.db('%_databaseName_%').table('%_tableName_%').get('%_documentUUID_%').update(row =>
row('clients')
.offsetsOf(clients => client('client_id').eq('%_clientValue_%'))(0)
.do(clientIndex => ({
clients: row('clients')(clientIndex)
.offsetsOf(contacts => contact('contact_id').eq('%_contactValue_%')))(0)
.do(contactIndex => ({
contacts: row(clientIndex)
.changeAt(contractIndex, row(clientIndex)(contractIndex).merge({
'score': 0,
'feedback': 'xyz'
}))
})
}))
)
Зачем пытаться превратить это в ReQL?
survey
.pluck({ clients: 'contacts' }).run()
.then(results => {
results.clients.forEach((item, outerIndex) => {
item.contacts.forEach((item, index, array) => {
if(Number(item.contact_id) === Number(obj.contact_id)) {
array[index].score = obj.score;
console.log(outerIndex, index);
}
});
});
return survey.update(results).run()
})
Хотя код, предоставленный Jacob (пользователь, который задал вопрос здесь о переполнении стека - показан выше), может показаться более простым для написания, производительность, вероятно, не так хороша, как решение ReQL.
1) Решение ReQL работает на сервере запросов (то есть на стороне базы данных), и поэтому код оптимизируется во время записи в базу данных (более высокая производительность). Принимая во внимание, что код выше, не в полной мере использует сервер запросов, и делает запрос на чтение и запись pluck().run()
и update().run()
, а данные обрабатываются на стороне клиентского запроса ( то есть сторона NodeJs) после выполнения запроса pluck pluck()
(более низкая производительность).
2) Приведенный выше код требует, чтобы сервер запросов отправлял обратно все данные стороне запроса клиента (то есть стороне NodeJ), и поэтому полезная нагрузка ответа (использование полосы пропускания интернета/размер загрузки) может составлять несколько мегабайт. В то время как решение ReQL обрабатывается на сервере запросов, и поэтому полезная нагрузка ответа, как правило, просто подтверждает, что запись была завершена, другими словами, только несколько байтов отправляются обратно на сторону клиентского запроса. Что делается в одном запросе.
ReQL слишком сложный
Однако ReQL (и особенно SQL) кажется слишком сложным при работе с JSON, и мне кажется, что JSON следует использовать при работе с JSON.
Я также предложил сообществу ReThinkDB принять альтернативу ReQL, которая вместо этого использует JSON (https://github.com/rethinkdb/rethinkdb/issues/6736).
Решение для обновления вложенных массивов JSON должно быть таким простым, как...
r('database.table').update({
clients: [{
client_id: 0,
contacts: [{
contact_id: 0,
score: 0,
feedback: 'xyz',
}]
}]
});
Ответ 5
Tfmontague находится на правильном пути, но я думаю, что его ответ может быть значительно улучшен. Поскольку он использует ...(0)
есть возможность для его ответа выбросить ошибки.
zabusa также предоставляет решение ReQL с использованием map
и branch
но не показывает полное вложенное обновление. Я остановлюсь на этой технике.
Выражения ReQL являются составными, поэтому мы можем изолировать сложность и избежать повторений. Это сохраняет код ровным и чистым.
Сначала напишите простую функцию mapIf
const mapIf = (rexpr, test, f) =>
rexpr.map(x => r.branch(test(x), f(x), x));
Теперь мы можем написать упрощенную функцию updateClientContact
const updateClientContact = (doc, clientId, contactId, patch) =>
doc.merge
( { clients:
mapIf
( doc('clients')
, c => c('client_id').eq(clientId)
, c =>
mapIf
( c('contacts')
, c => c('contact_id').eq(contactId)
, c =>
c.merge(patch)
)
)
}
);
Используйте это так
// fetch the document to update
const someDoc =
r.db(...).table(...).get(...);
// create patch for client id [1] and contact id [12]
const patch =
updateClientContact(someDoc, 1, 12, { name: 'x', feedback: 'z' });
// apply the patch
someDoc.update(patch);
Вот конкретный пример, который вы можете запустить в reql>...
const testDoc =
{ clients:
[ { client_id: 1
, contacts:
[ { contact_id: 11, name: 'a' }
, { contact_id: 12, name: 'b' }
, { contact_id: 13, name: 'c' }
]
}
, { client_id: 2
, contacts:
[ { contact_id: 21, name: 'd' }
, { contact_id: 22, name: 'e' }
, { contact_id: 23, name: 'f' }
]
}
, { client_id: 3
, contacts:
[ { contact_id: 31, name: 'g' }
, { contact_id: 32, name: 'h' }
, { contact_id: 33, name: 'i' }
]
}
]
};
updateClientContact(r.expr(testDoc), 2, 23, { name: 'x', feedback: 'z' });
Результат будет
{ clients:
[ { client_id: 1
, contacts:
[ { contact_id: 11, name: 'a' }
, { contact_id: 12, name: 'b' }
, { contact_id: 13, name: 'c' }
]
}
, { client_id: 2
, contacts:
[ { contact_id: 21, name: 'd' }
, { contact_id: 22, name: 'e' }
, { contact_id: 23, name: 'x', feedback: 'z' } // <--
]
}
, { client_id: 3
, contacts:
[ { contact_id: 31, name: 'g' }
, { contact_id: 32, name: 'h' }
, { contact_id: 33, name: 'i' }
]
}
]
}