RethinkDB - 更新嵌套数组

时间:2015-05-15 17:20:49

标签: javascript database node.js rethinkdb

我的调查表看起来像这样:

{
  id: Id,
  date: Date,
  clients: [{
    client_id: Id,
    contacts: [{
      contact_id: Id,
      score: Number,
      feedback: String,
      email: String
    }]
  }]
}

我需要更新特定联系人下的scorefeedback字段。目前,我正在运行这样的更新:

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方法时,它指定了如何更新嵌套的键:值对。但是,我找不到任何更新数组中单个项目的示例。

是否有更好的,更有希望更新的方式来更新嵌套数组中的项目?

6 个答案:

答案 0 :(得分:9)

您可能需要将数组filter从数组中取出所需的值,然后再将其追加到数组中。然后,您可以将更新的数组传递给update方法。

示例

假设您有一个包含两个客户的文档,这两个客户都有namescore,并且您希望更新其中一个得分:

{
  "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)
    }
  }]
}

答案 1 :(得分:4)

它对我有用

r.table(...).get(...).update({
contacts: r.row('Contacts').changeAt(0,
  r.row('Contacts').nth(0).merge({feedback: "NICE"}))
 })

答案 2 :(得分:1)

数据库架构

{
  "clients": [
    {
      "name":  "jacob" ,
      "score": 200
    } ,
    {
      "name":  "jorge" ,
      "score": 57
    }
  ] ,
  "id":  "70589f08-284c-495a-b089-005812ec589f"
}

然后您可以使用mapbranch查询来执行此操作。

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)})
    })

答案 3 :(得分:1)

ReQL解决方案

在ReThinkDB(和大多数查询语言)中,创建查询以就地更新对象的JSON数组是一个相当复杂的过程。我所知道的ReQL最好的(也是唯一的)解决方案是结合使用updateoffsetsOfdochangeAtmerge功能。此解决方案将保留对象在数组中的顺序,并且仅修改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所提供的代码(上面显示了在Stack Overflow上询问问题的用户-如上所示)看起来更易于编写,但性能可能不如ReQL解决方案。

1)ReQL解决方案在查询服务器(即数据库端)上运行,因此在数据库写入过程中对代码进行了优化(性能更高)。而以上代码并未充分利用查询服务器,而是发出了读写请求pluck().run()update().run(),并且数据在客户端请求端(即NodeJs端)进行处理pluck()查询运行后(性能降低)。

2)上面的代码要求查询服务器将所有数据发送回客户端请求端(即NodeJs端),因此响应有效负载(互联网带宽使用/下载大小)可能为几兆字节。 ReQL解决方案是在查询服务器上处理的,因此响应有效负载通常仅确认写入已完成,换句话说,只有几个字节被发送回客户端请求侧。在单个请求中完成。

ReQL太复杂了

但是,ReQL(尤其是SQL)在处理JSON时似乎过于复杂,在我看来,在处理JSON时应使用JSON。

我还建议ReThinkDB社区采用ReQL的替代方法,该替代方法使用JSON(https://github.com/rethinkdb/rethinkdb/issues/6736)。

更新嵌套JSON数组的解决方案应该简单...

r('database.table').update({
  clients: [{
    contacts: [{
      score: 0,
      feedback: 'xyz',
    }]
  }]
});

答案 4 :(得分:0)

tfmontague走在正确的道路上,但我认为他的答案可以大大改善。因为他使用...(0),所以他的答案有可能引发错误。

zabusa还提供了使用mapbranch的ReQL解决方案,但是没有显示完整的嵌套更新。我将扩展这项技术。

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' }
          ]
      }
    ]
}

答案 5 :(得分:0)

迟到总比不到好

我遇到了同样的问题,我可以通过两种方法解决它:

使用特定的} def

client_id

没有特定的r.db('nameDB').table('nameTable').get('idRegister') .update({'clients': r.row('clients') .map(elem=>{ return r.branch( elem('client_id').eq('your_specific_client_id'), elem.merge({ contacts: elem('contacts').map(elem2=> r.branch( elem2('contact_id').eq('idContact'), elem2.merge({ score: 99999, feedback: 'yourString' }), elem2 ) ) }), elem ) }) })

client_id

我希望它对您有用,即使很久以前发生了