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

Node.js 为数组中的每个元素添加唯一值,node.js,mongodb,mongoose,mongodb-query,Node.js,Mongodb,Mongoose,Mongodb Query,我是MongoDB的新手,我正在尝试将嵌入式数组合并到MongoDB集合中,我的项目集合模式如下: Projects: { _id: ObjectId(), client_id: String, description: String, samples: [ { location: String, //Unique name: String, } ...

我是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
    }
  ...
]
样本数组的长度与上载的数据数组的长度相同。我试图找出如何将数据字段添加到示例数组的每个元素中,但基于MongoDB文档,我无法找到如何添加数据字段。我可以将json数据作为“数据”加载到中,并希望基于公共“位置”字段进行合并:

但是我想不出如何在updatequery中获取json数组的索引,而且我还没有在mongodb文档中找到任何示例,或者类似的问题

任何帮助都将不胜感激

MongoDB 3.6位置过滤更新 因此,实际上你对操作符的理解是正确的,但问题是,它只适用于“每个”数组元素。因为您需要的是“匹配”条目,所以实际上您需要的是运算符

正如您所注意到的,您的
“位置”
将是唯一的,并且在阵列中。对于原子更新,使用“索引位置”确实不可靠,但实际上匹配“唯一”属性是不可靠的。基本上,您需要从以下内容中获得:

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 });
只需获取
输入的值
,并创建一个标识符列表,以匹配
数组过滤器中的
位置
值。
$set
语句的构造使用了两次迭代,能够合并处理的每个数组元素的键,以及该数组元素中存在的每个键,因为没有更新
位置

或者,使用<代码>循环..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;
  }
}
请注意,我们在这里以及在构造中使用。如果您发现自己处于一个没有这种支持的JavaScript环境中,那么和基本上都是不做任何更改的替代品

然后这些可以在更新中实际应用,如中所示:

Project.update({ client_id: 'ClientA' }, { $set }, { arrayFilters });
因此,这里实际使用的是在修饰符内和
update()
arrayFilters
选项内创建条目的“匹配对”。因此,对于每个
“位置”
,我们在
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 });
生成与索引匹配的语句(实际读取文档后):

当然要注意的是,对于每个“更新集”,除了先从文档中读取以确定要更新哪些索引之外,您真的没有其他选择。这通常不是一个好主意,因为除了写入前需要读取每个文档的开销之外,没有绝对的保证数组本身在读取和写入之间被其他进程保持不变,因此使用“硬索引”是假定所有内容都是一样的,但实际情况可能并非如此

早期MongoDB位置匹配 在数据允许的情况下,通常最好循环标准更新。这里的
location
确实是唯一的,因此它是一个很好的候选者,最重要的是,您不需要阅读现有文档来比较索引数组:

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);
A发送多个更新操作,但与任何其他更新操作一样,它只发送一个请求和响应。事实上,如果您正在处理一个“更改列表”,那么返回文档以对每个更改进行比较,然后构建一个大的更改,而不是单独编写,这实际上也适用于前面的所有示例

最大的区别是更改集中的“每个数组元素一条更新指令”。这是在没有“位置过滤”支持的版本中执行操作的安全方法,即使这意味着更多的写操作

示范 下面是演示中的完整列表。注意,为了简单起见,我在这里使用了“mongoose”,但是对于实际的更新本身并没有什么真正的“mongoose特定的”。这同样适用于任何实现,尤其是在本例中,使用和处理列表进行构造的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
}
当然还有标准语法和更新:

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
}

如果你需要一个键/值类型的东西,为什么不使用字典呢?提供的答案中是否有你认为不能解决你问题的东西?如果是,请对答案进行评论,以澄清哪些问题需要解决,哪些问题尚未解决。如果它确实回答了您提出的问题,那么请注意您提出的问题。请详细说明,对不起,我对本网站相当陌生,刚刚接受了您的回答,再次感谢!这是伟大的,工作完美,让我们不要说我有属性,我想更新,如
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
}