<p>A bit late, but I've just come across this very problem and found a solution that works well enough. Thought I'd post it here in case anyone is trying to do something similar.</p>
<hr />
<p>As far as I can tell, doing this in the database query itself (without too many complicated custom selects / conditions) is not feasible. Category queries do have an option to <a href="https://craftcms.com/docs/3.x/categories.html#leaves" rel="nofollow noreferrer">only return leaf nodes</a> (categories without a descendant), but that doesn't match the problem. After all, we do want to display categories that don't have leaf nodes if none of those leaves are selected.</p>
<p>So the only solution I could find is to fetch all selected categories and then filter them. We want to filter out all categories with descendants that are selected on this entry. We can take advantage of the fact that Craft automatically selects all ancestors of a term when a lower-level term is selected. Because of this, we know that for every category that we don't want to show, one other category in the list will have this category as it's <a href="https://docs.craftcms.com/api/v3/craft-base-element.html#parent" rel="nofollow noreferrer">parent</a>. So we can filter the list by checking for this condition:</p>
<pre><code>{% set allSelectedCategories = entry.businessTypes.all() %}
{% set leafNodesOnly = allSelectedCategories|filter(category =&gt;
    (allSelectedCategories|filter(
        c =&gt; c.parent is not empty and c.parent.id == category.id
    ) is empty)
) %}
</code></pre>
<p>The problem with that is that it has quadratic complexity since for every node, we're checking every node (the fact that Twig doesn't have something like <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some" rel="nofollow noreferrer">Array.prototype.some</a> doesn't help). If your entries have many categories, this may slow down the template considerably.</p>
<hr />
<p>We can also take advantage of the fact that category queries return their results in hierarchical order by default. So instead of checking if ANY category in the array is a child of the current one, we can just check the next category in the list. If it isn't a child of the current category, the current category in the loop is a leaf node.</p>
<pre><code>{% set allSelectedCategories = entry.businessTypes.all() %}
{% set leafNodesOnly = allSelectedCategories|filter((category, index) =&gt;
    allSelectedCategories[index + 1] is not defined or
    allSelectedCategories[index + 1].parent is empty or
    allSelectedCategories[index + 1].parent.id != category.id
) %}
</code></pre>
<p>However, this approach <strong>does not work if you've changed the sort order</strong> to anything other than the default.</p>

CC BY-SA 4.0
2021-09-13T14:47:55.670Z
2021-09-13T14:47:55.670Z
MoritzLost
<p>I could not test it, but you could try:</p>

<h2>If you always have a Parent and 2 levels</h2>

<pre><code>entry.businessTypes.level('&gt;= 2').all()
</code></pre>

<h2>If you want to only get the lowest, you could try to assign it with a condition:</h2>

<pre><code>{% if categories = entry.businessTypes.children %} 
    {% set categories = entry.businessTypes.level('&gt;= 2').all() %}
{% else categories = entry.businessTypes.children %} 
    {% set categories = entry.businessTypes.all() %}
{% endif %}

{% for category in categories %} ... {% endfor %}
</code></pre>

CC BY-SA 4.0
2020-04-09T10:59:52.397Z
2020-04-09T10:59:52.397Z
Max Strebel
<p>I have a category tree like this and I have an entry that has four categories <strong>A, A1, B, C2</strong>:</p>

<ul>
<li>A

<ul>
<li>A1</li>
<li>A2</li>
</ul></li>
<li>B

<ul>
<li>B1</li>
<li>B2</li>
</ul></li>
<li>C

<ul>
<li>C1</li>
<li>C2</li>
</ul></li>
</ul>

<p>I want a query that ignores A and returns only A1, B, C2, because I don't want to display the parent category of a sub-category that's also displayed.</p>

<p><code>{% for category in entry.businessTypes.all() %}{{ category.title }}{% endfor %}</code>
returns all: A, A1, B, C2</p>

<p><code>{% for category in entry.businessTypes.leaves().all() %}{{ category.title }}{% endfor %}</code>
returns A2, C2 but not B.</p>

<p><code>{% for category in entry.businessTypes.hasDescendants().all() %}{{ category.title }}{% endfor %}</code>
returns A, B but not A1, C2.</p>

<p>Any suggestions are appreciated.</p>

CC BY-SA 4.0
2021-02-09T13:40:38.667Z
hey travis, did you find a solution for this problem?
Matthias Redl-Mann
CC BY-SA 4.0
2021-09-13T14:49:34.680Z
@MatthiasRedl-Mann I've just posted an answer with two working solutions, even though they do the filtering in memory instead of in the database query itself.
MoritzLost
<category><query>
How can I display just the "lowest" categories assigned to an entry and not parent categories?
# How can I display just the "lowest" categories assigned to an entry and not parent categories?

(Asked by: username_0 on 2020-04-08)

I have a category tree like this and I have an entry that has four categories **A, A1, B, C2**:

* A
  + A1
  + A2
* B
  + B1
  + B2
* C
  + C1
  + C2

I want a query that ignores A and returns only A1, B, C2, because I don't want to display the parent category of a sub-category that's also displayed.

`{% for category in entry.businessTypes.all() %}{{ category.title }}{% endfor %}`
returns all: A, A1, B, C2

`{% for category in entry.businessTypes.leaves().all() %}{{ category.title }}{% endfor %}`
returns A2, C2 but not B.

`{% for category in entry.businessTypes.hasDescendants().all() %}{{ category.title }}{% endfor %}`
returns A, B but not A1, C2.

Any suggestions are appreciated.

**Tags:** category, query
> **Comments:**
> **username_1**: hey travis, did you find a solution for this problem?  
> **username_2**: @MatthiasRedl-Mann I've just posted an answer with two working solutions, even though they do the filtering in memory instead of in the database query itself.  

---

## 2 answers

### Answer by username_2 on 2021-09-13. Score: 2

A bit late, but I've just come across this very problem and found a solution that works well enough. Thought I'd post it here in case anyone is trying to do something similar.

As far as I can tell, doing this in the database query itself (without too many complicated custom selects / conditions) is not feasible. Category queries do have an option to only return leaf nodes (categories without a descendant), but that doesn't match the problem. After all, we do want to display categories that don't have leaf nodes if none of those leaves are selected.
So the only solution I could find is to fetch all selected categories and then filter them. We want to filter out all categories with descendants that are selected on this entry. We can take advantage of the fact that Craft automatically selects all ancestors of a term when a lower-level term is selected. Because of this, we know that for every category that we don't want to show, one other category in the list will have this category as it's parent. So we can filter the list by checking for this condition:
{% set allSelectedCategories = entry.businessTypes.all() %}
{% set leafNodesOnly = allSelectedCategories|filter(category =>
    (allSelectedCategories|filter(
        c => c.parent is not empty and c.parent.id == category.id
    ) is empty)
) %}

The problem with that is that it has quadratic complexity since for every node, we're checking every node (the fact that Twig doesn't have something like Array.prototype.some doesn't help). If your entries have many categories, this may slow down the template considerably.

We can also take advantage of the fact that category queries return their results in hierarchical order by default. So instead of checking if ANY category in the array is a child of the current one, we can just check the next category in the list. If it isn't a child of the current category, the current category in the loop is a leaf node.
{% set allSelectedCategories = entry.businessTypes.all() %}
{% set leafNodesOnly = allSelectedCategories|filter((category, index) =>
    allSelectedCategories[index + 1] is not defined or
    allSelectedCategories[index + 1].parent is empty or
    allSelectedCategories[index + 1].parent.id != category.id
) %}

However, this approach does not work if you've changed the sort order to anything other than the default.


---

### Answer by username_3 on 2020-04-09. Score: 1

I could not test it, but you could try:
If you always have a Parent and 2 levels
entry.businessTypes.level('>= 2').all()

If you want to only get the lowest, you could try to assign it with a condition:
{% if categories = entry.businessTypes.children %} 
    {% set categories = entry.businessTypes.level('>= 2').all() %}
{% else categories = entry.businessTypes.children %} 
    {% set categories = entry.businessTypes.all() %}
{% endif %}

{% for category in categories %} ... {% endfor %}


---

