草庐IT

node.js - 为数组中的每个元素添加唯一值

coder 2023-11-05 原文

我对MongoDB还不太熟悉,我正在尝试将嵌入数组合并到MongoDB集合中,我的项目集合架构如下:

Projects:
{
    _id: ObjectId(),
    client_id: String,
    description: String,
    samples: [
        {
            location: String,      //Unique
            name: String,
        }
      ...
    ]
}

用户可以上传以下形式的json文件:
[
    {
        location: String,     //Same location as in above schema
        concentration: float
    }
  ...
]

样本数组的长度与上载的数据数组的长度相同。我试图找出如何将数据字段添加到samples数组的每个元素中,但是我无法根据mongodb文档找到如何添加的方法。我可以将json数据作为“data”加载到中,并希望基于公共“location”字段进行合并:
db.projects.update({_id: myId}, {$set : {samples.$[].data : data[location]}});

但是我想不出如何在update query中获取json数组的索引,而且我还没有在mongodb文档中找到任何示例,或者类似这样的问题。
任何帮助都将不胜感激!

最佳答案

MongoDB 3.6位置过滤更新
所以您实际上是在正确的“ballpark”中使用了positional all $[]操作符,但问题是它只适用于“every”数组元素。因为您想要的是“匹配”条目,所以实际上您想要的是positional filtered $[<identifier>]运算符。
正如您所注意到的,您的"location"将是唯一的并且在数组中。使用“索引位置”对于原子更新来说确实不可靠,但实际上匹配“唯一”属性是可靠的。基本上你需要从这样的事情:

let input = [
  { location: "A", concentration: 3, other: "c" },
  { location: "C", concentration: 4, other: "a" }
];

对此:
{
  "$set": {
    "samples.$[l0].concentration": 3,
    "samples.$[l0].other": "c",
    "samples.$[l1].concentration": 4,
    "samples.$[l1].other": "a"
  },
  "arrayFilters": [
    {
      "l0.location": "A"
    },
    {
      "l1.location": "C"
    }
  ]
}

实际上,这只是将一些基本函数应用于所提供的输入数组的问题:
let arrayFilters = input.map(({ location },i) => ({ [`l${i}.location`]: location }));

let $set = input.reduce((o,{ location, ...e },i) =>
  ({
    ...o,
    ...Object.entries(e).reduce((oe,[k,v]) => ({ ...oe, [`samples.$[l${i}].${k}`]: v }),{})
  }),
  {}
);

log({ $set, arrayFilters });

Array.map()只需获取input的值并创建一个标识符列表,以匹配location中的arrayFilters值。$set语句的构造使用Array.reduce()进行两次迭代,以便在从考虑中删除location之后,合并处理的每个数组元素和该数组元素中存在的每个键,因为这不会被更新。
或者,使用for..of循环:
let arrayFilters = [];
let $set = {};

for ( let [i, { location, ...e }] of Object.entries(input) ) {
  arrayFilters.push({ [`l${i}.location`]: location });
  for ( let [k,v] of Object.entries(e) ) {
    $set[`samples.$[l${i}].${k}`] = v;
  }
}

注意我们在这里使用Object.entries()以及在构造中使用"object spread" ...。如果您发现自己处于一个没有这种支持的javascript环境中,那么Object.keys()Object.assign()基本上是无变化的替换。
然后可以在更新中实际应用,如下所示:
Project.update({ client_id: 'ClientA' }, { $set }, { arrayFilters });

因此positional filtered $[<identifier>]实际上用于在$set修饰符内和arrayFilters选项内创建条目的“匹配对”。因此,对于每个update()我们创建一个与"location"中的值匹配的标识符,然后在实际的arrayFilters语句中使用相同的标识符,以便只更新与标识符条件匹配的数组条目。
唯一有“标识符”的真正规则是不能以数字开头,它们“应该”是唯一的,但这不是一个规则,你只需要得到第一个匹配就行了。但更新只触及那些实际符合条件的条目。
ealier mongodb固定索引
如果没有这种支持,那么你基本上就要回到“指数头寸”了,这确实不太可靠。通常,在更新之前,您实际上需要阅读每个文档并确定数组中已经存在的内容。但至少假定指数持仓的“平价”,那么:
let input = [
  { location: "A", concentration: 3 },
  { location: "B", concentration: 5 },
  { location: "C", concentration: 4 }
];

let $set = input.reduce((o,e,i) =>
  ({ ...o, [`samples.${i}.concentration`]: e.concentration }),{}
);

log({ $set });

生成更新语句,如:
{
  "$set": {
    "samples.0.concentration": 3,
    "samples.1.concentration": 5,
    "samples.2.concentration": 4
  }
}

或者没有平价:
let input = [
  { location: "A", concentration: 3, other: "c" },
  { location: "C", concentration: 4, other: "a" }
];


// Need to get the document to compare without parity
let doc = await Project.findOne({ "client_id": "ClientA" });

let $set = input.reduce((o,e,i) =>
  ({
    ...o,
    ...Object.entries(e).filter(([k,v]) => k !== "location")
      .reduce((oe,[k,v]) =>
        ({
          ...oe,
          [`samples.${doc.samples.map(c => c.location).indexOf(e.location)}`
            + `.${k}`]: v
        }),
        {}
      )
  }),
  {}
);

log({ $set });


await Project.update({ client_id: 'ClientA' },{ $set });

生成与索引匹配的语句(在实际阅读文档之后):
{
  "$set": {
    "samples.0.concentration": 3,
    "samples.0.other": "c",
    "samples.2.concentration": 4,
    "samples.2.other": "a"
  }
}

当然要注意的是,对于每个“更新集”,除了首先从文档中读取以确定要更新哪些索引之外,您没有其他选择。这通常不是一个好主意,因为除了在写入之前需要读取每个文档的开销之外,不能绝对保证数组本身在读取和写入之间由其他进程保持不变,因此使用“硬索引”可以假定g仍然是相同的,但事实上可能并非如此。
早期MongoDB位置匹配
在数据允许的情况下,通常最好循环标准positional matched $set更新。这里$确实是唯一的,因此它是一个很好的候选,而且最重要的是您不需要阅读现有文档来比较索引的数组:
let input = [
  { location: "A", concentration: 3, other: "c" },
  { location: "C", concentration: 4, other: "a" }
];

let batch = input.map(({ location, ...e }) =>
  ({
    updateOne: {
      filter: { client_id: "ClientA", 'samples.location': location },
      update: {
        $set: Object.entries(e)
          .reduce((oe,[k,v]) => ({ ...oe,  [`samples.$.${k}`]: v }), {})
      }
    }
  })
);

log({ batch });

await Project.bulkWrite(batch);

alocation发送多个更新操作,但它只发送一个请求和响应,就像其他任何更新操作一样。实际上,如果您正在处理一个“更改列表”,那么返回文档以对每个更改进行比较,然后构造一个大的bulkWrite()是要进入的方向,而不是单个写入,而且这实际上也适用于前面的所有示例。
最大的区别是更改集中的“每个数组元素一个更新指令”。这是在没有“位置筛选”支持的版本中执行操作的安全方法,即使它意味着更多的写操作。
示范
下面是演示中的完整列表。注意我在这里使用“mongoose”是为了简单起见,但是对于实际的更新本身并没有什么真正的“mongoose特有的”。这同样适用于任何实现,特别是在本例中,使用bulkWrite()Array.map()处理构建列表的javascript示例。
const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/test';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const sampleSchema = new Schema({
  location: String,
  name: String,
  concentration: Number,
  other: String
});

const projectSchema = new Schema({
  client_id: String,
  description: String,
  samples: [sampleSchema]
});

const Project = mongoose.model('Project', projectSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    await Project.create({
      client_id: "ClientA",
      description: "A Client",
      samples: [
        { location: "A", name: "Location A" },
        { location: "B", name: "Location B" },
        { location: "C", name: "Location C" }
      ]
    });

    let input = [
      { location: "A", concentration: 3, other: "c" },
      { location: "C", concentration: 4, other: "a" }
    ];

    let arrayFilters = input.map(({ location },i) => ({ [`l${i}.location`]: location }));

    let $set = input.reduce((o,{ location, ...e },i) =>
      ({
        ...o,
        ...Object.entries(e).reduce((oe,[k,v]) => ({ ...oe, [`samples.$[l${i}].${k}`]: v }),{})
      }),
      {}
    );

    log({ $set, arrayFilters });

    await Project.update(
      { client_id: 'ClientA' },
      { $set },
      { arrayFilters }
    );

    let project = await Project.findOne();
    log(project);

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()

对于那些懒得运行的人,输出将显示更新的匹配数组元素:
Mongoose: projects.remove({}, {})
Mongoose: projects.insertOne({ _id: ObjectId("5b1778605c59470ecaf10fac"), client_id: 'ClientA', description: 'A Client', samples: [ { _id: ObjectId("5b1778605c59470ecaf10faf"), location: 'A', name: 'Location A' }, { _id: ObjectId("5b1778605c59470ecaf10fae"), location: 'B', name: 'Location B' }, { _id: ObjectId("5b1778605c59470ecaf10fad"), location: 'C', name: 'Location C' } ], __v: 0 })
{
  "$set": {
    "samples.$[l0].concentration": 3,
    "samples.$[l0].other": "c",
    "samples.$[l1].concentration": 4,
    "samples.$[l1].other": "a"
  },
  "arrayFilters": [
    {
      "l0.location": "A"
    },
    {
      "l1.location": "C"
    }
  ]
}
Mongoose: projects.update({ client_id: 'ClientA' }, { '$set': { 'samples.$[l0].concentration': 3, 'samples.$[l0].other': 'c', 'samples.$[l1].concentration': 4, 'samples.$[l1].other': 'a' } }, { arrayFilters: [ { 'l0.location': 'A' }, { 'l1.location': 'C' } ] })
Mongoose: projects.findOne({}, { fields: {} })
{
  "_id": "5b1778605c59470ecaf10fac",
  "client_id": "ClientA",
  "description": "A Client",
  "samples": [
    {
      "_id": "5b1778605c59470ecaf10faf",
      "location": "A",
      "name": "Location A",
      "concentration": 3,
      "other": "c"
    },
    {
      "_id": "5b1778605c59470ecaf10fae",
      "location": "B",
      "name": "Location B"
    },
    {
      "_id": "5b1778605c59470ecaf10fad",
      "location": "C",
      "name": "Location C",
      "concentration": 4,
      "other": "a"
    }
  ],
  "__v": 0
}

或按硬索引:
const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/test';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const sampleSchema = new Schema({
  location: String,
  name: String,
  concentration: Number,
  other: String
});

const projectSchema = new Schema({
  client_id: String,
  description: String,
  samples: [sampleSchema]
});

const Project = mongoose.model('Project', projectSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    await Project.create({
      client_id: "ClientA",
      description: "A Client",
      samples: [
        { location: "A", name: "Location A" },
        { location: "B", name: "Location B" },
        { location: "C", name: "Location C" }
      ]
    });

    let input = [
      { location: "A", concentration: 3, other: "c" },
      { location: "C", concentration: 4, other: "a" }
    ];


    // Need to get the document to compare without parity
    let doc = await Project.findOne({ "client_id": "ClientA" });

    let $set = input.reduce((o,e,i) =>
      ({
        ...o,
        ...Object.entries(e).filter(([k,v]) => k !== "location")
          .reduce((oe,[k,v]) =>
            ({
              ...oe,
              [`samples.${doc.samples.map(c => c.location).indexOf(e.location)}`
                + `.${k}`]: v
            }),
            {}
          )
      }),
      {}
    );

    log({ $set });


    await Project.update(
      { client_id: 'ClientA' },
      { $set },
    );

    let project = await Project.findOne();
    log(project);

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()

以及输出:
Mongoose: projects.remove({}, {})
Mongoose: projects.insertOne({ _id: ObjectId("5b1778e0f7be250f2b7c3fc8"), client_id: 'ClientA', description: 'A Client', samples: [ { _id: ObjectId("5b1778e0f7be250f2b7c3fcb"), location: 'A', name: 'Location A' }, { _id: ObjectId("5b1778e0f7be250f2b7c3fca"), location: 'B', name: 'Location B' }, { _id: ObjectId("5b1778e0f7be250f2b7c3fc9"), location: 'C', name: 'Location C' } ], __v: 0 })
Mongoose: projects.findOne({ client_id: 'ClientA' }, { fields: {} })
{
  "$set": {
    "samples.0.concentration": 3,
    "samples.0.other": "c",
    "samples.2.concentration": 4,
    "samples.2.other": "a"
  }
}
Mongoose: projects.update({ client_id: 'ClientA' }, { '$set': { 'samples.0.concentration': 3, 'samples.0.other': 'c', 'samples.2.concentration': 4, 'samples.2.other': 'a' } }, {})
Mongoose: projects.findOne({}, { fields: {} })
{
  "_id": "5b1778e0f7be250f2b7c3fc8",
  "client_id": "ClientA",
  "description": "A Client",
  "samples": [
    {
      "_id": "5b1778e0f7be250f2b7c3fcb",
      "location": "A",
      "name": "Location A",
      "concentration": 3,
      "other": "c"
    },
    {
      "_id": "5b1778e0f7be250f2b7c3fca",
      "location": "B",
      "name": "Location B"
    },
    {
      "_id": "5b1778e0f7be250f2b7c3fc9",
      "location": "C",
      "name": "Location C",
      "concentration": 4,
      "other": "a"
    }
  ],
  "__v": 0
}

当然还有标准的"positional" Array.reduce()语法和更新:
const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/test';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const sampleSchema = new Schema({
  location: String,
  name: String,
  concentration: Number,
  other: String
});

const projectSchema = new Schema({
  client_id: String,
  description: String,
  samples: [sampleSchema]
});

const Project = mongoose.model('Project', projectSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    await Project.create({
      client_id: "ClientA",
      description: "A Client",
      samples: [
        { location: "A", name: "Location A" },
        { location: "B", name: "Location B" },
        { location: "C", name: "Location C" }
      ]
    });

    let input = [
      { location: "A", concentration: 3, other: "c" },
      { location: "C", concentration: 4, other: "a" }
    ];

    let batch = input.map(({ location, ...e }) =>
      ({
        updateOne: {
          filter: { client_id: "ClientA", 'samples.location': location },
          update: {
            $set: Object.entries(e)
              .reduce((oe,[k,v]) => ({ ...oe,  [`samples.$.${k}`]: v }), {})
          }
        }
      })
    );

    log({ batch });

    await Project.bulkWrite(batch);

    let project = await Project.findOne();
    log(project);

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()

输出:
Mongoose: projects.remove({}, {})
Mongoose: projects.insertOne({ _id: ObjectId("5b179142662616160853ba4a"), client_id: 'ClientA', description: 'A Client', samples: [ { _id: ObjectId("5b179142662616160853ba4d"), location: 'A', name: 'Location A' }, { _id: ObjectId("5b179142662616160853ba4c"), location: 'B', name: 'Location B' }, { _id: ObjectId("5b179142662616160853ba4b"), location: 'C', name: 'Location C' } ], __v: 0 })
{
  "batch": [
    {
      "updateOne": {
        "filter": {
          "client_id": "ClientA",
          "samples.location": "A"
        },
        "update": {
          "$set": {
            "samples.$.concentration": 3,
            "samples.$.other": "c"
          }
        }
      }
    },
    {
      "updateOne": {
        "filter": {
          "client_id": "ClientA",
          "samples.location": "C"
        },
        "update": {
          "$set": {
            "samples.$.concentration": 4,
            "samples.$.other": "a"
          }
        }
      }
    }
  ]
}
Mongoose: projects.bulkWrite([ { updateOne: { filter: { client_id: 'ClientA', 'samples.location': 'A' }, update: { '$set': { 'samples.$.concentration': 3, 'samples.$.other': 'c' } } } }, { updateOne: { filter: { client_id: 'ClientA', 'samples.location': 'C' }, update: { '$set': { 'samples.$.concentration': 4, 'samples.$.other': 'a' } } } } ], {})
Mongoose: projects.findOne({}, { fields: {} })
{
  "_id": "5b179142662616160853ba4a",
  "client_id": "ClientA",
  "description": "A Client",
  "samples": [
    {
      "_id": "5b179142662616160853ba4d",
      "location": "A",
      "name": "Location A",
      "concentration": 3,
      "other": "c"
    },
    {
      "_id": "5b179142662616160853ba4c",
      "location": "B",
      "name": "Location B"
    },
    {
      "_id": "5b179142662616160853ba4b",
      "location": "C",
      "name": "Location C",
      "concentration": 4,
      "other": "a"
    }
  ],
  "__v": 0
}

关于node.js - 为数组中的每个元素添加唯一值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50688190/

有关node.js - 为数组中的每个元素添加唯一值的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  5. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  8. ruby - 将 Bootstrap Less 添加到 Sinatra - 2

    我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它

  9. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  10. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

随机推荐