title: Elasticsearch Sorting on Multiple Nested Fields with Nested Filtering
body: <p>Given the following data with nested objects (members within teams), I need to sort objects on multiple nested fields, first by height, then by weight.  This all needs to respect the filtering that is done on other nested fields (just position in this example).</p>
  <p><strong>Data</strong></p>
  <pre><code>PUT sample
  {
    &quot;mappings&quot;: {
      &quot;dynamic&quot;: &quot;strict&quot;,
      &quot;properties&quot;: {
        &quot;teamId&quot;: { &quot;type&quot;: &quot;keyword&quot;, &quot;index&quot;: true, &quot;doc_values&quot;: true },
        &quot;members&quot;: {
          &quot;type&quot;: &quot;nested&quot;,
          &quot;properties&quot;: {
            &quot;memberId&quot;: { &quot;type&quot;: &quot;keyword&quot;, &quot;index&quot;: true, &quot;doc_values&quot;: true },
            &quot;position&quot;: { &quot;type&quot;: &quot;keyword&quot;, &quot;index&quot;: true, &quot;doc_values&quot;: true},
            &quot;height&quot;: { &quot;type&quot;: &quot;integer&quot;, &quot;index&quot;: true, &quot;doc_values&quot;: true},
            &quot;weight&quot;: { &quot;type&quot;: &quot;integer&quot;, &quot;index&quot;: true, &quot;doc_values&quot;: true}
          }
        }
      }
    }
  }
   
  PUT sample/_doc/1
  {
        &quot;teamId&quot; : &quot;A&quot;
      , &quot;members&quot; :
        [
            { &quot;memberId&quot; : &quot;A1_X&quot; , &quot;position&quot;: &quot;X&quot;, &quot;height&quot;: 70, &quot;weight&quot;: 195}
          , { &quot;memberId&quot; : &quot;A2_Y&quot; , &quot;position&quot;: &quot;Y&quot;, &quot;height&quot;: 70, &quot;weight&quot;: 170}
          , { &quot;memberId&quot; : &quot;A3_Z&quot; , &quot;position&quot;: &quot;Z&quot;, &quot;height&quot;: 75, &quot;weight&quot;: 210}
        ]
  }
   
  PUT sample/_doc/2
  {
        &quot;teamId&quot; : &quot;B&quot;
      , &quot;members&quot; :
        [
            { &quot;memberId&quot; : &quot;B1_Z&quot; , &quot;position&quot;: &quot;Z&quot;, &quot;height&quot;: 80, &quot;weight&quot;: 220 }
          , { &quot;memberId&quot; : &quot;B2_X&quot; , &quot;position&quot;: &quot;X&quot;, &quot;height&quot;: 75, &quot;weight&quot;: 190 }
          , { &quot;memberId&quot; : &quot;B3_X&quot; , &quot;position&quot;: &quot;X&quot;, &quot;height&quot;: 70, &quot;weight&quot;: 200 }
          , { &quot;memberId&quot; : &quot;B4_Y&quot; , &quot;position&quot;: &quot;Y&quot;, &quot;height&quot;: 70, &quot;weight&quot;: 170 }
        ]
  }
   
   
  PUT sample/_doc/3
  {
        &quot;teamId&quot; : &quot;C&quot;
      , &quot;members&quot; :
        [
            { &quot;memberId&quot; : &quot;C1_Y&quot; , &quot;position&quot;: &quot;Y&quot;, &quot;height&quot;: 70, &quot;weight&quot;: 190 }
          , { &quot;memberId&quot; : &quot;C2_X&quot; , &quot;position&quot;: &quot;X&quot;, &quot;height&quot;: 75, &quot;weight&quot;: 180 }
          , { &quot;memberId&quot; : &quot;C3_Z&quot; , &quot;position&quot;: &quot;Z&quot;, &quot;height&quot;: 75, &quot;weight&quot;: 225 }
        ]
  }
  </code></pre>
  <p><strong>Query</strong></p>
  <pre><code>POST sample/_search?filter_path=hits.hits.inner_hits.members.hits.hits._source.height,hits.hits.inner_hits.members.hits.hits._source.weight,hits.hits.sort,hits.hits._source.teamId
  {
      &quot;size&quot; : 3
    , &quot;track_total_hits&quot; : true
    , &quot;query&quot; : { &quot;bool&quot; : { &quot;filter&quot; : [
        { &quot;match_all&quot; : { } }
      , { &quot;nested&quot;: { &quot;path&quot;: &quot;members&quot; , &quot;query&quot;: { &quot;bool&quot;: { &quot;must&quot;: [
            //nested filters
            { &quot;term&quot; : { &quot;members.position&quot; : &quot;X&quot; } }
          ] } }
          , &quot;inner_hits&quot; : { &quot;size&quot; : 3 }
      } }
    ]}}
    , &quot;sort&quot;: [
          { &quot;members.height&quot;: { &quot;order&quot;: &quot;asc&quot; , &quot;nested&quot;: { &quot;path&quot;: &quot;members&quot;, &quot;filter&quot;: { &quot;bool&quot;: { &quot;must&quot;: [
            //copy all nested filters below
            { &quot;term&quot; : { &quot;members.position&quot; : &quot;X&quot; } }
          ] } } } } }
        , { &quot;members.weight&quot;: { &quot;order&quot;: &quot;asc&quot; , &quot;nested&quot;: { &quot;path&quot;: &quot;members&quot;, &quot;filter&quot;: { &quot;bool&quot;: { &quot;must&quot;: [
            //copy all nested filters below
            { &quot;term&quot; : { &quot;members.position&quot; : &quot;X&quot; } }
          ] } } } } }
        , { &quot;teamId&quot;: { &quot;order&quot;: &quot;asc&quot; } }
      ]
  }
  </code></pre>
  <p><strong>Results</strong></p>
  <pre><code>{
    &quot;hits&quot; : {
      &quot;hits&quot; : [
        {
          &quot;_source&quot; : {
            &quot;teamId&quot; : &quot;B&quot;
          },
          &quot;sort&quot; : [
            70,
            190,
            &quot;B&quot;
          ],
          &quot;inner_hits&quot; : {
            &quot;members&quot; : {
              &quot;hits&quot; : {
                &quot;hits&quot; : [
                  {
                    &quot;_source&quot; : {
                      &quot;weight&quot; : 190,
                      &quot;height&quot; : 75
                    }
                  },
                  {
                    &quot;_source&quot; : {
                      &quot;weight&quot; : 200,
                      &quot;height&quot; : 70
                    }
                  }
                ]
              }
            }
          }
        },
        {
          &quot;_source&quot; : {
            &quot;teamId&quot; : &quot;A&quot;
          },
          &quot;sort&quot; : [
            70,
            195,
            &quot;A&quot;
          ],
          &quot;inner_hits&quot; : {
            &quot;members&quot; : {
              &quot;hits&quot; : {
                &quot;hits&quot; : [
                  {
                    &quot;_source&quot; : {
                      &quot;weight&quot; : 195,
                      &quot;height&quot; : 70
                    }
                  }
                ]
              }
            }
          }
        },
        {
          &quot;_source&quot; : {
            &quot;teamId&quot; : &quot;C&quot;
          },
          &quot;sort&quot; : [
            75,
            180,
            &quot;C&quot;
          ],
          &quot;inner_hits&quot; : {
            &quot;members&quot; : {
              &quot;hits&quot; : {
                &quot;hits&quot; : [
                  {
                    &quot;_source&quot; : {
                      &quot;weight&quot; : 180,
                      &quot;height&quot; : 75
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
  
  </code></pre>
  <p>So in the query above, I'm trying to</p>
  <ol>
  <li>Find teams that have members with position X.</li>
  <li>Sort the teams based on height then weight (both ascending) of such members, where the height and weight used must be from the same members.</li>
  </ol>
  <p>The query is close, but has 2 problems I'd like to fix.</p>
  <ol>
  <li><strong>It requires that I copy all the nested filters for each sorting field.</strong>  Is there a syntax that won't require me to do this?</li>
  <li><strong>The height it uses to sort by may not be from the same member as the weight that it uses to sort by.</strong>  In the results above, you can see that it puts team B first because it uses the height of 70 from B3_X and the weight of 190 from B2_X, which is not what I want.  I want it to use the height of 70 from B3_X and the weight of 200 from the same member.</li>
  </ol>
  <p><em>(Note that a hack I want to avoid is concatenating the 2 sorting fields into a single string or integer.  Although this would solve the problem as stated, it creates other problems in the real world scenario I'm basing this example on.)</em></p>