Finding an item in a nested object of objects

Hi, so I have the following query set up (with data):

query test {
  character(charID="a", enemy:"c") {
    charID
    enemy {
      name
      weapons {
        type
        power
      }
    }
  }
}

Suppose my data takes this shape (when the query is run):

{
  "data": {
    "charEnemy": [
      {
        "charID": "a",
        "enemy": [
          {
            "name": "q",
            "weapons": [
                    { "type": "metal", "power": "5" }
             ]
          },
          {
            "name": "X",
            "weapons": [
                    { "type": "metal", "power": "6" }
             ]
          },
          {
            "name": "c",
            "weapons": [
                    { "type": "metal", "power": "7" }
              ]
          },
        ]
      }
    ]
}

How should I go about querying a specific CharID with a specific enemy name? If I query charID=“a” and enemy name “X” for example, I only want to be able to access enemy[1], or rather, everything within the object with that specific name.

I currently have this but it returns the entire charEnemy object which has both conditions met. It doesn’t ONLY return the charID, with that specific enemy object, but rather returns that charID with ALL the enemy objects associated with it: return charEnemy.find({charID: args.charID, enemy : {$elemMatch: {name : "X"}}}); How do I make it such that I get that entire charEnemy object with that charID and enemy name, but not the other enemy objects within the enemy array?

Hi Ajay_Pillay, I think you are looking for the dot notation to access embedded documents. To return only specific fields, you have to specify all of them inside your projection. In your case, this query should work:

charEnemy.find({ 'data.charEnemy.enemy.name': 'X' }, { 'data.charEnemy.enemy.name': 1, 'data.charEnemy.enemy.weapons': 1 })

2 Likes

I need to match two things - charID and an enemy name. Also take note this isn’t my actual data - the actual data is named differently and has many more dimensions but this is just a simplified form of it (with different names too - so it may seem pretty dumb).

Currently my data is stored in this form (if I may use set builder notation for simplicity) { character, { enemy1, enemy2, enemy3, enemy4 } } however when I query, I would like to return {character, {enemy1, enemy2 } } for example, if I request for (character, enemy1, enemy2). So here you can see that my requests take a different form, a character name as the first input (mandatory), and an optional number of enemy names. But the thing is with the matching currently set up, when I query (character, enemy1), it returns the entire character object because enemy2, enemy3, enemy4 etc are all associated with it.

When I tried out what you suggested - I was able to obtain specific fields. It only returns the enemy.name and enemy.weapons field, which is great! However, if I have two objects like { charA, { enemyA, {nameA, weaponA}}, { charB, { enemyB, {nameB, weaponA}, { enemyC, {nameC, weaponC}}, and I run the query as suggested, charEnemy.find({ 'data.charEnemy.enemy.weapon': 'weaponA' }, { 'data.charEnemy.enemy.name': 1, 'data.charEnemy.enemy.weapons': 1 }), it returns { {nameA,weaponA} , {{ nameB,weaponA },{nameC,weaponC}} } which is not correct because I only want the enemies with weaponA, but because it was found in charB, I get the entire of charB’s “enemy” object/array, when I only want that specific entry which had the match.

Hopefully this makes sense. It’s a little tough to explain it precisely but if it’s still unclear I’d be happy to draw out a diagram of some sort to help visualize this.

If you want to find a specific enemy of a specific character, you can add a field ‘charID’ inside your query:

charEnemy.find({ 'data.charEnemy.charID': 'charA', 'data.charEnemy.enemy.weapons': 'weaponA' }, { 'data.charEnemy.enemy.name': 1, 'data.charEnemy.enemy.weapons': 1 })

This query should only return the enemy with ‘weaponA’ from the character named ‘charA’.

Nope I think the relation that you’re working upon is slightly different. I will include some images here so that it’s illustrated clearer. This is in Robo3T.

These are my database entries:
image

image
image
image

When I run this command db.getCollection('test').find({ 'charID': 'a', 'enemy.name': 'enemyA' }, { 'enemy.name': 1, 'enemy.weapon': 1 }), the result is:
image

But this is not what I want returned, what I want to get should look like this (I can include the type by extending my query to include 'enemy.type':1 as well but that’s just a little addition that doesn’t change the goal):

{
    "_id" : ObjectId("5fdf16c956d6a07c2880d8a3"),
    "charID" : "a",
    "enemy" : [ 
        {
            "name" : "enemyA",
            "weapon" : "weaponA",
            "type" : "metal"
        }
    ]
}

So I want to return all the fields within that enemy object, but not any other enemy objects.

1 Like

One way to achieve this result is to use an $unwind aggregation pipeline, which will return a document for each entry in the array:

db.test.aggregate([
	{
		$unwind: '$enemy'
	},
	{
		$match: {
			charID: 'a',
			'enemy.name': 'enemyA'
		}
	}
])

I think there are many ways to obtain the same result, maybe playing around with the aggregation pipeline operators $indexOfArray and $arrayElemAt.

If your database is pretty big, consider adding another $match stage to your pipeline, preselecting the ‘charID’ your are interested in:

db.test.aggregate([
	{
		$match: {
			charID: 'a'	
		}
	},
	{
		$unwind: '$enemy'
	},
	{
		$match: {
			'enemy.name': 'enemyA'
		}
	}
])

This way, your will skip the creation of multiple documents for each ‘charID’. I’m sure there is a neater way to pur things together, but it should do the work.

1 Like

Thank you, this was what I needed. I will definitely look into what the most optimal solution is but I think aggregating works very well so far.

You are welcome, I’m glad it helps.

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.