Answers.Body: <p>Solved this by merging the search parameters together for both the multiple categories and the number fields if they were set in the url parameters. This solves the loop.last pagination problem as well. Here is the code I used:</p>
  
  <pre><code>{# set base params #}
  {% set params = {
      section: 'vehicles',
      limit: null
  } %}
  {% set relatedParams = ['and'] %}
  
  {# add category parameters if they exist #}
  {% if craft.app.request.getParam('make') %}
      {% set category = craft.categories.group('makeModel').slug(craft.app.request.getParam('make')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  {% if craft.app.request.getParam('model') %}
      {% set category = craft.categories.group('makeModel').slug(craft.app.request.getParam('model')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  {% if craft.app.request.getParam('transmission') %}
      {% set category = craft.categories.group('transmission').slug(craft.app.request.getParam('transmission')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  {% if craft.app.request.getParam('body') %}
      {% set category = craft.categories.group('class').slug(craft.app.request.getParam('body')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  
  {# add number fields with greater/lesser than comparisons #}
  {% if craft.app.request.getParam('min-year') %}
      {% set minYear = craft.app.request.getParam('min-year') %}
      {% set params = params|merge({'year':'&gt;=' ~ minYear}) %}
  {% endif %}
  {% if craft.app.request.getParam('max-year') %}
      {% set maxYear = craft.app.request.getParam('max-year') %}
      {% set params = params|merge({'year':'&lt;=' ~ maxYear}) %}
  {% endif %}
  {% if craft.app.request.getParam('min-price') %}
      {% set minPrice = craft.app.request.getParam('min-price') %}
      {% set params = params|merge({'price':'&gt;=' ~ minPrice}) %}
  {% endif %}
  {% if craft.app.request.getParam('max-price') %}
      {% set maxPrice = craft.app.request.getParam('max-price') %}
      {% set params = params|merge({'price':'&lt;=' ~ maxPrice}) %}
  {% endif %}
  
  {# add relatedParams to params #}
  {% if relatedParams|length &gt; 1 %}
      {% set params = params|merge({'relatedTo':relatedParams}) %}
  {% endif %}
  
  {# perform search #}
  {% paginate craft.entries(params).limit(12) as pageInfo, pageEntries %}
  </code></pre>
  
  <p>I believe all the code is Craft 3 compliant as well. Searching looks for all parameters to be met so this works well for product searches.</p>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2018-04-19T03:06:11.270Z
Answers.LastActivityDate: 2018-04-19T03:06:11.270Z
Answers.UserDisplayName: Chattervast
Body: <p>Looking for the solution to searching entries in one section that are related to multiple categories and match number field criteria. This is for a used auto website.</p>
  
  <p><strong>Categories:</strong></p>
  
  <p><strong>Make/Model/Trim:</strong> Nested category levels for better searching a leveling of vehicles. Category example: Chevrolet -> Malibu -> LS</p>
  
  <p><strong>Transmission:</strong> Automatic and Manual</p>
  
  <p><strong>Class:</strong> Coupe, Sedan, SUV, etc...</p>
  
  <p><strong>Fields:</strong>
  Price - number field with up to 2 decimal places.
  Year - 4 digit number no punctuation or decimal places.</p>
  
  <p><strong>The search bar fields:</strong>
  Make
  Model
  Transmission
  Class
  Minimum Year
  Maximum Year
  Minimum Price
  Maximum Price</p>
  
  <p>Current search form submits a url such as:
  <a href="http://examplesite.com/search?make=chevrolet&amp;model=malibu&amp;transmission=automatic&amp;min-year=2007&amp;max-year=2017&amp;min-price=3000&amp;max-price=20000&amp;class=sedan" rel="noreferrer">http://examplesite.com/search?make=chevrolet&amp;model=malibu&amp;transmission=automatic&amp;min-year=2007&amp;max-year=2017&amp;min-price=3000&amp;max-price=20000&amp;class=sedan</a></p>
  
  <p>Whether or not a search field was used it still applies at empty field to the url.</p>
  
  <p>I've read every Craft CMS Multi search page and SO question on this as well as the new Craft CMS 3 documentation. I can't seem to find solution to the categories part first off and then a decent way to handle the number criteria. Also, I am planning on paginating the entries (causes a separate issue of not being able to use loop.last, which I can solve with some loop counters).</p>
  
  <p>Current search page code:</p>
  
  <pre><code>{% set make = craft.app.request.getParam('make') %}
  {% set model = craft.app.request.getParam('model') %}
  {% set transmission = craft.app.request.getParam('transmission') %}
  {% set class = craft.app.request.getParam('class') %}
  
  {% set minYear = craft.app.request.getParam('min-year') %}
  {% set maxYear = craft.app.request.getParam('max-year') %}
  {% set minPrice = craft.app.request.getParam('min-price') %}
  {% set maxPrice = craft.app.request.getParam('max-price') %}
  
  {% set makeCat = make ? craft.categories.group('makeModelTrim').slug(make).find() %}
  {% set modelCat = model ? craft.categories.group('makeModelTrim').slug(model).find() %}
  {% set transmissionCat = transmission ? craft.categories.group('transmission').slug(transmission).find() %}
  {% set classCat = class ? craft.categories.group('class').slug(class).find() %}
  
  {% set makeParam = makeCat ? {targetElement: ['and']|merge(makeCat)} %}
  {% set modelParam = modelCat ? {targetElement: ['and']|merge(modelCat)} %}
  {% set transmissionParam = transmissionCat ? {targetElement: ['and']|merge(transmissionCat)} %}
  {% set classParam = classCat ? {targetElement: ['and']|merge(classCat)} %}
  
  {% set relationParams = [makeParam, modelParam, transmissionParam, classParam]|filter %}
  
  {% set entries = craft.entries({section: 'vehicles',relatedTo: relationParams ? ['and']|merge(relationParams) : null}) %}
  </code></pre>
  
  <p>Works but is kinda wonky. I would like the related entries to match all of the given categories but it currently pulls vehicles that match any of the categories. If Chevrolet and Automatic transmission are given criteria, both Chevrolets and Automatics are pulled which is not what I want.</p>
  
  <p>Is there a way to add the min/max year and price to the related to parameters? If so then that would solve the next issue of sorting through the entries though it's difficult to paginate the entries if I have to throw out some retrieved ones after the fact. When I try to convert the set entries to paginate with this:</p>
  
  <pre><code>{% paginate craft.entries({section: 'vehicles',relatedTo: relationParams ? ['and']|merge(relationParams) : null}).limit(12) as pageInfo, pageEntries %}
  </code></pre>
  
  <p>It doesn't seem to work though I can get the count of pageEntries and it will return some number. But what I'm currently doing is the former setting of the entries to the related parameters then running this:</p>
  
  <pre><code>{% for vehicle in pageEntries if minYear|length and minYear &lt;= vehicle.year and maxYear|length and maxYear &gt;= vehicle.year and minPrice|length and minPrice &lt;= vehicle.price and maxPrice|length and maxPrice &gt;= vehicle.price %}
  </code></pre>
  
  <p>Which doesn't work. I'm guessing that my if statements aren't in order or can't be used that way. I could have a complicated if/else or case scenario to handle whether min/max price and year have been set rather than checking it in the for loop, though I'm not certain what's best.</p>
  
  <p>I'm sure someone reading this has a simple solution to multi category and multi field entry searching. And if it's possible to eliminate the the empty parameters in the url, that would be helpful as well. I currently have empty options with no values set holding the place of the select drop-downs until a value is selected.</p>
  
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2018-04-18T20:06:31.353Z
Comments.Text: Why would you want to include the min/max price in a `relatedTo` parameter? You can set this parameter via magic setter `craft.entries.minPrice('>=50').maxPrice('<=100).minYear(>=2004).maxYear('<=2016).all()` however. I'm usually no fan of complex search functions in twig because of certain experiences in the past years. It's much easier to extend the query more freely and flexible via php in order to join `relation` tables on your own to build more complex nested relationships
Comments.UserDisplayName: Robin Schambach
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2018-04-19T02:05:05.813Z
Comments.Text: Wow! Thank you! I have never heard of magic setter and it never came up in any of my searches for something like this. I will definitely us this. Yes I could do something in php but I think coming up with something here will be simpler and I'm not as familiar with running php commands directly to pull craft entries.
Comments.UserDisplayName: Chattervast
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2018-04-19T04:51:32.530Z
Comments.Text: You are using magic methods the entire time. `entry.fieldHandle` is a magic getter too. Usually you won't see any search result here on Stack Exchange for similar questions without them.
Comments.UserDisplayName: Robin Schambach
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2018-04-19T13:48:11.877Z
Comments.Text: Thanks! I guess I just didn't know the term.
Comments.UserDisplayName: Chattervast
Tags: <entries><categories><craft3><search>
Title: Craft 3 Multi Category and Field Search
ThreadText: # Craft 3 Multi Category and Field Search
  
  (Asked by: username_0 on 2018-04-18)
  
  Looking for the solution to searching entries in one section that are related to multiple categories and match number field criteria. This is for a used auto website.
  
  **Categories:**
  
  **Make/Model/Trim:** Nested category levels for better searching a leveling of vehicles. Category example: Chevrolet -> Malibu -> LS
  
  **Transmission:** Automatic and Manual
  
  **Class:** Coupe, Sedan, SUV, etc...
  
  **Fields:**
  Price - number field with up to 2 decimal places.
  Year - 4 digit number no punctuation or decimal places.
  
  **The search bar fields:**
  Make
  Model
  Transmission
  Class
  Minimum Year
  Maximum Year
  Minimum Price
  Maximum Price
  
  Current search form submits a url such as:
  <http://examplesite.com/search?make=chevrolet&model=malibu&transmission=automatic&min-year=2007&max-year=2017&min-price=3000&max-price=20000&class=sedan>
  
  Whether or not a search field was used it still applies at empty field to the url.
  
  I've read every Craft CMS Multi search page and SO question on this as well as the new Craft CMS 3 documentation. I can't seem to find solution to the categories part first off and then a decent way to handle the number criteria. Also, I am planning on paginating the entries (causes a separate issue of not being able to use loop.last, which I can solve with some loop counters).
  
  Current search page code:
  
  ```
  {% set make = craft.app.request.getParam('make') %}
  {% set model = craft.app.request.getParam('model') %}
  {% set transmission = craft.app.request.getParam('transmission') %}
  {% set class = craft.app.request.getParam('class') %}
  
  {% set minYear = craft.app.request.getParam('min-year') %}
  {% set maxYear = craft.app.request.getParam('max-year') %}
  {% set minPrice = craft.app.request.getParam('min-price') %}
  {% set maxPrice = craft.app.request.getParam('max-price') %}
  
  {% set makeCat = make ? craft.categories.group('makeModelTrim').slug(make).find() %}
  {% set modelCat = model ? craft.categories.group('makeModelTrim').slug(model).find() %}
  {% set transmissionCat = transmission ? craft.categories.group('transmission').slug(transmission).find() %}
  {% set classCat = class ? craft.categories.group('class').slug(class).find() %}
  
  {% set makeParam = makeCat ? {targetElement: ['and']|merge(makeCat)} %}
  {% set modelParam = modelCat ? {targetElement: ['and']|merge(modelCat)} %}
  {% set transmissionParam = transmissionCat ? {targetElement: ['and']|merge(transmissionCat)} %}
  {% set classParam = classCat ? {targetElement: ['and']|merge(classCat)} %}
  
  {% set relationParams = [makeParam, modelParam, transmissionParam, classParam]|filter %}
  
  {% set entries = craft.entries({section: 'vehicles',relatedTo: relationParams ? ['and']|merge(relationParams) : null}) %}
  
  ```
  
  Works but is kinda wonky. I would like the related entries to match all of the given categories but it currently pulls vehicles that match any of the categories. If Chevrolet and Automatic transmission are given criteria, both Chevrolets and Automatics are pulled which is not what I want.
  
  Is there a way to add the min/max year and price to the related to parameters? If so then that would solve the next issue of sorting through the entries though it's difficult to paginate the entries if I have to throw out some retrieved ones after the fact. When I try to convert the set entries to paginate with this:
  
  ```
  {% paginate craft.entries({section: 'vehicles',relatedTo: relationParams ? ['and']|merge(relationParams) : null}).limit(12) as pageInfo, pageEntries %}
  
  ```
  
  It doesn't seem to work though I can get the count of pageEntries and it will return some number. But what I'm currently doing is the former setting of the entries to the related parameters then running this:
  
  ```
  {% for vehicle in pageEntries if minYear|length and minYear <= vehicle.year and maxYear|length and maxYear >= vehicle.year and minPrice|length and minPrice <= vehicle.price and maxPrice|length and maxPrice >= vehicle.price %}
  
  ```
  
  Which doesn't work. I'm guessing that my if statements aren't in order or can't be used that way. I could have a complicated if/else or case scenario to handle whether min/max price and year have been set rather than checking it in the for loop, though I'm not certain what's best.
  
  I'm sure someone reading this has a simple solution to multi category and multi field entry searching. And if it's possible to eliminate the the empty parameters in the url, that would be helpful as well. I currently have empty options with no values set holding the place of the select drop-downs until a value is selected.
  
  **Tags:** entries, categories, craft3, search
  
  > **Comments:**
  > **username_1**: Why would you want to include the min/max price in a `relatedTo` parameter? You can set this parameter via magic setter `craft.entries.minPrice('>=50').maxPrice('<=100).minYear(>=2004).maxYear('<=2016).all()` however. I'm usually no fan of complex search functions in twig because of certain experiences in the past years. It's much easier to extend the query more freely and flexible via php in order to join `relation` tables on your own to build more complex nested relationships  
  > **username_0**: Wow! Thank you! I have never heard of magic setter and it never came up in any of my searches for something like this. I will definitely us this. Yes I could do something in php but I think coming up with something here will be simpler and I'm not as familiar with running php commands directly to pull craft entries.  
  > **username_1**: You are using magic methods the entire time. `entry.fieldHandle` is a magic getter too. Usually you won't see any search result here on Stack Exchange for similar questions without them.  
  > **username_0**: Thanks! I guess I just didn't know the term.  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_0 on 2018-04-19. Score: 8 | Accepted answer
  
  Solved this by merging the search parameters together for both the multiple categories and the number fields if they were set in the url parameters. This solves the loop.last pagination problem as well. Here is the code I used:
  
  ```
  {# set base params #}
  {% set params = {
      section: 'vehicles',
      limit: null
  } %}
  {% set relatedParams = ['and'] %}
  
  {# add category parameters if they exist #}
  {% if craft.app.request.getParam('make') %}
      {% set category = craft.categories.group('makeModel').slug(craft.app.request.getParam('make')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  {% if craft.app.request.getParam('model') %}
      {% set category = craft.categories.group('makeModel').slug(craft.app.request.getParam('model')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  {% if craft.app.request.getParam('transmission') %}
      {% set category = craft.categories.group('transmission').slug(craft.app.request.getParam('transmission')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  {% if craft.app.request.getParam('body') %}
      {% set category = craft.categories.group('class').slug(craft.app.request.getParam('body')).one() %}
      {% set relatedParams = relatedParams|merge([{ targetElement:category }]) %}
  {% endif %}
  
  {# add number fields with greater/lesser than comparisons #}
  {% if craft.app.request.getParam('min-year') %}
      {% set minYear = craft.app.request.getParam('min-year') %}
      {% set params = params|merge({'year':'>=' ~ minYear}) %}
  {% endif %}
  {% if craft.app.request.getParam('max-year') %}
      {% set maxYear = craft.app.request.getParam('max-year') %}
      {% set params = params|merge({'year':'<=' ~ maxYear}) %}
  {% endif %}
  {% if craft.app.request.getParam('min-price') %}
      {% set minPrice = craft.app.request.getParam('min-price') %}
      {% set params = params|merge({'price':'>=' ~ minPrice}) %}
  {% endif %}
  {% if craft.app.request.getParam('max-price') %}
      {% set maxPrice = craft.app.request.getParam('max-price') %}
      {% set params = params|merge({'price':'<=' ~ maxPrice}) %}
  {% endif %}
  
  {# add relatedParams to params #}
  {% if relatedParams|length > 1 %}
      {% set params = params|merge({'relatedTo':relatedParams}) %}
  {% endif %}
  
  {# perform search #}
  {% paginate craft.entries(params).limit(12) as pageInfo, pageEntries %}
  
  ```
  
  I believe all the code is Craft 3 compliant as well. Searching looks for all parameters to be met so this works well for product searches.
  
  
  ---
  
  