title: SilverStripe - Filter DataList results to remove duplicates and group by value
body: <p>I'm trying something different with DataLists in SilverStripe. The goal is to return a list of stores sorted by state in this format. </p>
  
  <p>There is an admin model that has been created called <code>StoreLocation.php</code> which contains the store data:</p>
  
  <pre><code>class StoreLocation extends DataObject {
  
      private static $db = array(
          'Address'   =&gt; 'Varchar(250)',
          'City'      =&gt; 'varchar(100)',
          'State'     =&gt; 'varchar(2)',
          'Zip'       =&gt; 'varchar(10)'
      );
  
      private static $has_one = array(
          'Store' =&gt; 'Store'
      );
  
      private static $summary_fields = array(
          'getStoreSummaryLabel' =&gt; 'Store Info'
      );
  
      private static $field_labels = array(
          'Address',
          'City',
          'State',
          'Zip'
      );
  
      public function getStoreSummaryLabel() {
          $storeName = $this-&gt;Store()-&gt;Name;
          return sprintf("%s (%s)", $storeName, $this-&gt;addressPretty());
      }
  
      // tidy up the CMS by not showing these fields
      public function getCMSFields() {
          $fields = parent::getCMSFields();
  
          $siteConfig = SiteConfig::current_site_config();
  
          $statesDropdown = DropdownField::create("State", "State", $siteConfig-&gt;stateList())
              -&gt;setEmptyString('Select State');
  
          if ($siteConfig-&gt;GoogleAPIKey == "") {
              $fields-&gt;addFieldToTab("Root.Main", new HeaderField("error","Error: No Google API Key Defined!",2));
          }
  
          $fields-&gt;removeFieldsFromTab('Root.Main', [
              'State',
          ]);
  
          return $fields;
      }
  
      public function addressPretty() {
          return sprintf("%s %s, %s %s", $this-&gt;data()-&gt;Address, $this-&gt;data()-&gt;City, $this-&gt;data()-&gt;State, $this-&gt;data()-&gt;Zip);
      }
  }
  </code></pre>
  
  <p>The <code>Store</code> <code>DataObject</code> which is referenced by <code>StoreLocation</code>:</p>
  
  <pre><code>class Store extends DataObject {
      private static $db = array(
          'Name' =&gt; 'Varchar(100)'
      );
  
      private static $has_one = array(
          'Logo' =&gt; 'Image'
      );
  
      private static $has_many = array(
          'Locations' =&gt; 'StoreLocation'
      );
  
      private static $summary_fields = array(
          'Name' =&gt; 'Name'
      );
  
      private static $field_labels = array(
          'Name',
          'Logo'
      );
  
      // tidy up the CMS by not showing these fields
      public function getCMSFields() {
          $fields = parent::getCMSFields();
          return $fields;
      }
  
      static $default_sort = "Name ASC";
  }
  </code></pre>
  
  <p>The problem is I need to return a list of stores by state but remove any duplicate store names since I am not displaying the store addresses. I only need to display the names (I don't know why. This is just how it has to be). So, for example, there could be 5 results for Store ABC in Alabama because there are 5 locations for it, but I just want to return Store ABC once for Alabama.</p>
  
  <p>Simply returning a list of <code>StoreLocation</code> data in this format returns the correct results but with tons of duplicates and the state name is repeated above each store:</p>
  
  <p>UPDATE: I found a possible solution after discovering this link which contains a custom nested grouped lists class file: <a href="https://www.silverstripe.org/community/forums/general-questions/show/24195" rel="nofollow noreferrer">https://www.silverstripe.org/community/forums/general-questions/show/24195</a></p>
  
  <p>With the use of that class, I was able to make a GroupedList in which I can sort by 2 different aspects of a data object:</p>
  
  <pre><code>public function GroupedEntries() {
      return NestedGroupedList::create(
          StoreLocation::get()-&gt;sort('State ASC, StoreID ASC')
      );
  }
  </code></pre>
  
  <p>Then, in the template, I set up this:</p>
  
  <pre><code>&lt;div class="col-md-12" style="padding-bottom:40px;"&gt;
      &lt;div class="retailer-listing" style="text-align: left;"&gt;
          &lt;% loop $GroupedEntries.GroupedBy('State,StoreID') %&gt;
              &lt;strong&gt;$State&lt;/strong&gt;&lt;br /&gt;
                  &lt;% loop $Children %&gt;
                          &lt;% loop $Children.First %&gt;
                              $Store.Name&lt;br /&gt;
                          &lt;% end_loop %&gt;
                  &lt;% end_loop %&gt;
          &lt;% end_loop %&gt;
       &lt;/div&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p>This seems to be returning the correct organization of results, sorting the first instance of each store name alphabetically by state. It took some guess and check testing until I got the loops just right.</p>