1

I have just imported some data from a SQL database into a MongoDB collection and am doing some refactoring. I've just converted a string date value from 'yyyy-mm-dd' format with:

db.Statistic.find({d: {$exists: false}}).forEach(function (x) {
  var d = ISODate(x.date);
  db.Statistic.update(x, {$set: {"d": d}});
});

which has worked well. Now I want to rebuild the objects with an _id which uses this date so that I don't need a separate date field. I have tried (and many variations):

db.Statistic.find({d: {$exists: true}}).forEach(function (x) {
  var oldId = x._id;
  x._id = new ObjectId(x.d);
  db.Statistic.save(x);
  db.Statistic.remove({_id: oldId});
});

but I get Error: invalid object id: length I assume because the console's ObjectId constructor doesn't accept dates to build a new objectId. Is there any way to do this in the console?

user2845946
  • 1,755
  • 29
  • 38
Nic Cottrell
  • 9,401
  • 7
  • 53
  • 76

2 Answers2

1

Yes you have two problems in your logic here.

Firstly using the ObjectId() function you are either specifying a stringified version of an actual ObjectId as the argument or nothing. So you can't seed it as a date as you are trying to do.

Second problem is you just cannot update (which is what save is a special shortcut for) the _id field of your document. Think of it as a primary key. Changing it is not allowed.

While you think of a different strategy for you new _id's (different question), take note that to do this kind of change you need to "write out" to a new collection.

That said, generating the ObjectId in the shell has this restriction on the constructor. Some driver implementations do provide a method for using your own date seed in the Id generation. You are still subject to the same restrictions I have mentioned. But the information may be of use. Here are two posts that reference this:

Generating Mongo ObjectId (_id) with custom time?

Create MongoDB ObjectID from date in the past using PHP driver

Community
  • 1
  • 1
Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • About part 2, I am aware of that - which is why I don't try and change the _id of an existing saved document. I am save a new document (just happen to reuse the fields) and then delete the old document so it's inside the same collection. I agree that I probably should have imported into a temporary collection and then saved the refactored object into the final collection. – Nic Cottrell Feb 24 '14 at 09:42
  • @NicholasTolleyCottrell Ah. the **save** function (link in answer) explicitly keeps the original `_id` intact. So that's the problem there. Explained now. – Neil Lunn Feb 24 '14 at 09:47
  • I don't think that's the problem here since it doesn't even get to the save. According to your docs: `If the document contains an _id field, then the save() method performs an upsert, querying the collection on the _id field. If a document does not exist with the specified _id value, the save() method performs an insert.` Since I am setting a new non-existent _id, the upsert should correctly insert a new document for me. – Nic Cottrell Feb 24 '14 at 09:57
  • @NicholasTolleyCottrell Bottom line is that your supposed usage of invoking `ObjectId` with a date in the constructor does not work. The answer tells you why and gives you alternatives, along with information on what you were doing. If you need help coming up with a new strategy beyond that then perhaps a new question. Needless to say, to avoid confusion, use `insert` instead, as that is what you intend to do. – Neil Lunn Feb 24 '14 at 10:01
0

I've managed to achieve what I wanted. I ended up with a little script - I've included debugging information in case that is useful to somebody in the future:

db.Statistic.find().forEach(function (x) {
  var oldId = x._id;
  print("oldId=" + oldId);
  var oldMachineTime = ("" + oldId).substring(8, 24);
  print("oldMachineTime=" + oldMachineTime);
  print("x.d.getTime=" + x.d.getTime());
  var timePart = Math.floor(x.d.getTime() / 1000);
  print("timePart=" + timePart);
  var timePart16 = timePart.toString(16);
  print("timePart16=" + timePart16);
  var newHex = timePart16 + oldMachineTime;
  print("newHex=" + newHex);
  x._id = ObjectId(newHex);
  print("x._id=" + x._id);
  db.Statistic.save(x);
  db.Statistic.remove({_id: oldId});
});

I then confirmed with a subset of the data:

db.Statistic.find(...).sort({"date": 1}).limit(1000).forEach(function (x) {
  print("id=" + x._id);
  var idDate = new Date(x._id.getTimestamp());
  print("id.date=" + idDate);
  print("date=" + x.date);
});

In summary, I now have integrated the original dates from my SQL table into the ObjectId for these objects. I can now remove the old date field and read it from _id.getTimestamp() when needed.

Nic Cottrell
  • 9,401
  • 7
  • 53
  • 76