Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongodb sort by version x.y.z

Our application is based on MongoDB. We store the "names", "versions" and various attributes of IT assets in MongoDB. We have a new search requirement in which we need to retrieve the latest version of assets based on keywords. The catch here is that my versions numbers are of the format x.y.z (i.e. major.minor.maintenance)

Basically, I should have the result set sorted first by major version (desc), then minor version (desc) and then maintenance version (desc). e.g. For the version numbers given below: 1.0.10 1.1.0 11.0.0 2.0.1 The sorted version should be: 1.0.10 1.2.10 2.0.1 11.0.0 Is it possible for me to sort this correctly at Mongo layer itself? Unfortunately the version number is just stored as a single string value, and not a structured object in the x.y.z format.

Thanks in advance! Zeba

like image 219
zeba Avatar asked Nov 07 '13 14:11

zeba


2 Answers

You need to store a second representation of the field, mongo can't sort on semantic versions. I chose to pad each component to 4 characters.

var document = {
  version: "1.2.3",
  padded_version: "0001.0002.0003",
}

Then you can sort on lexicographic string index.

db.ensureIndex({ padded_version: -1 }) 

You can also bit-shift each component:

var document = {
  version: "1.2.3",
  integer_version: 1 << 16 + 2 << 8 + 3,
}

It limits you to 256 new versions however with potential overflow issues, etc. I would stick with zero-padded string version.

like image 161
AJcodez Avatar answered Oct 02 '22 10:10

AJcodez


I know it's not the one of the most recent questions, but I have a different solution.

Option with one projection (slower):

{$project: {
  version: {
    major: { $arrayElemAt: [{ $map: { input: { $split: ["$metadata.appVersion", "."] }, as: "vn", in: { $toInt: "$$vn" } } }, 0] },
    minor: { $arrayElemAt: [{ $map: { input: { $split: ["$metadata.appVersion", "."] }, as: "vn", in: { $toInt: "$$vn" } } }, 1] },
    patch: { $arrayElemAt: [{ $map: { input: { $split: ["$metadata.appVersion", "."] }, as: "vn", in: { $toInt: "$$vn" } } }, 2] }
  }
}

Option with two projections (aggregate needed):

{
  $project: {
    versionAsArray: { 
      $map: { 
        input: { 
          $split: ["$metadata.appVersion", "."] 
        }, 
        as: "vn", 
        in: { $toInt: "$$vn" } 
      } 
    }
  }
},
{
  $project: {
    version: {
      version: {
        major: { $arrayElemAt: ["$versionAsArray", 0] },
        minor: { $arrayElemAt: ["$versionAsArray", 1] },
        patch: { $arrayElemAt: ["$versionAsArray", 2] },
      }
    }
  }
}

and on the end you can use normal $sort: { version: 1 }

like image 40
kpostekk Avatar answered Oct 02 '22 10:10

kpostekk