Search code examples
mongodbaggregation-frameworkprojection

How to sort a dictionary keys and pick the first in MongoDb?


I'm running the following query as described in the docs.

db.getCollection('things')
  .find(
    { _id: UUID("...") },
    { _id: 0, history: 1 }
  )

It produces a single element that, when unfolded in the GUI, shows the dictonary history. When I unfold that, I get to see the contents: bunch of keys and correlated values.

Now, I'd like to sort the keys alphabetically and pick n first ones. Please note that it's not an array but a dictionary that is stored. Also, it would be great if I could flatten the structure and pop up my history to be the head (root?) of the document returned.

I understand it's about projection and slicing. However, I'm not getting anywhere, despite many attempts. I get syntax errors or a full list of elements. Being rather nooby, I fear that I require a few pointers on how to diagnose my issue to begin with.

Based on the comments, I tried with aggregate and $sort. Regrettably, I only seem to be sorting the current output (that produces a single document due to the match condition). I want to access the elements inside history.

db.getCollection('things')
  .aggregate([
    { $match: { _id: UUID("...") } },
    { $sort: { history: 1 } }
  ])

I'm sensing that I should use projection to pull out a list of elements residing under history but I'm getting no success using the below.

db.getCollection('things')
  .aggregate([
    { $match: { _id: UUID("...") } },
    { $project: { history: 1, _id: 0 } }
  ])

Solution

  • It is a long process to just sort object properties by alphabetical order,

    • $objectToArray convert history object to array in key-value format
    • $unwind deconstruct above generated array
    • $sort by history key by ascending order (1 = ascending, -1 = descending)
    • $group by _id and reconstruct history key-value array
    • $slice to get your number of properties from dictionary from top, i have entered 1
    • $arrayToObject back to convert key-value array to object format
    db.getCollection('things').aggregate([
      { $match: { _id: UUID("...") } },
      { $project: { history: { $objectToArray: "$history" } } },
      { $unwind: "$history" },
      { $sort: { "history.k": 1 } },
      {
        $group: {
          _id: "$_id",
          history: { $push: "$history" }
        }
      },
      { 
        $project: { 
          history: { 
            $arrayToObject: { $slice: ["$history", 1] } 
          } 
        } 
      }
    ])
    

    Playground


    There is another option, but as per MongoDB, it can not guarantee this will reproduce the exact result,

    • $objectToArray convert history object to array in key-value format
    • $setUnion basically this operator will get unique elements from an array, but as per experience, it will sort elements by key ascending order, so as per MongoDB there is no guarantee.
    • $slice to get your number of properties from dictionary from top, i have entered 1
    • $arrayToObject back to convert key-value array to object format
    db.getCollection('things').aggregate([
      { $match: { _id: UUID("...") } },
      {
        $project: {
          history: {
            $arrayToObject: {
              $slice: [
                { $setUnion: { $objectToArray: "$history" } },
                1
              ]
            }
          }
        }
      }
    ])
    

    Playground