<p>Your issue is that as soon as you call <code>.find()</code>, your <a href="https://craftcms.com/docs/templating/elementcriteriamodel" rel="nofollow">ElementCriteriaModel</a> returns <em>an array of elements</em>. In other words, the ElementCriteriaModel is gone, and it's no longer possible to add parameters (i.e. <code>id()</code>) or call other methods that you'd otherwise be able to call on that object (i.e. <code>.first()</code>).</p>

<p>On the other hand – like you say in your description; if you don't use <code>.find()</code> (keeping the ElementCriteriaModel around) you'll hit the database every time you use the <code>.id()</code> method.</p>

<p>A workaround could be to loop over the categories and add them to an indexed array (using the category IDs as keys) and then pull your individual categories from that index instead of the original array.</p>

<p>Important: One gotcha to note is that the <code>|merge</code> filter will actually renumber numeric keys, so you have to add an arbitrary character to the ID to retain the actual ID values (the example below uses an underscore prefix):</p>

<pre><code>{# Get the categories #}
{% set categories = craft.categories.group('products').limit(null).find() %}

{# Create category index #}
{% set categoryIndex = {} %}
{% for category in categories %}
    {% set categoryIndex = categoryIndex|merge({('_'~category.id): category}) %}
{% endfor %}

{# Do your thing #}
&lt;h1&gt;&lt;a href="{{ categoryIndex['_102'].url }}"&gt;{{ categoryIndex['_102'].title|raw }}&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;{{ categoryIndex['_126'].title|raw }}&lt;/h2&gt;
</code></pre>

<p>Note that when you want to pull a category from the index, <code>categoryIndex['_102']</code> and <code>categoryIndex._102</code> will both work – I just find that the former looks a bit cleaner, with the underscore in front of it (obviously you can use any character, it doesn't have to be an underscore).</p>

<p><strong>Edit</strong></p>

<p>As pointed out by @carlcs in the comments below, another approach to creating the index is to use the <a href="https://craftcms.com/docs/templating/filters#group" rel="nofollow"><code>group</code></a> filter. With this approach you'll avoid the underscore hack and the <code>for</code> loop. The gotcha here is that this filter will actually create an array of arrays, meaning you'll have to use <code>categoryIndex[102][0]</code> (note the <code>[0]</code> at the end) to access a specific category (you could also use <code>categoryIndex[102]|first</code>, of course):</p>

<pre><code>{% set categories = craft.categories.group('products').limit(null).find() %}
{% set categoryIndex = categories|group('id') %}

&lt;h1&gt;&lt;a href="{{ categoryIndex[102][0].url }}"&gt;{{ categoryIndex[102][0].title|raw }}&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;{{ categoryIndex[126][0].title|raw }}&lt;/h2&gt;
</code></pre>

CC BY-SA 3.0
2016-08-11T07:48:46.753Z
Good solution, I was just thinking that you might be able to achieve the same using the group filter, and It might even be possible to do so without this underscore hack that is required because of how merge works. `categories|group('id')`
carlcs
CC BY-SA 3.0
2016-08-11T11:24:16.653Z
Good idea, @carlcs – the `group` filter is definitely an option, too. I'll edit my answer to include it.
Mats Mikkel Rummelhoff
CC BY-SA 3.0
2016-08-10T18:54:37.730Z
2016-08-11T11:30:36.717Z
2016-08-11T11:30:36.717Z
Mats Mikkel Rummelhoff
<p>I am working on a site that needs to both list all categories and also pull out individual categories by their id.</p>

<p>I would like to query the DB once for all the categories (to improve performance), and then pull out which ones I want, but am struggling to find a solution.</p>

<pre><code>{% set catNavigation = craft.categories.group('products').limit(null).find() %}

&lt;h1&gt;&lt;a href="{{ catNavigation.id(102).first.url }}"&gt;{{ catNavigation.id(102).first.title|raw }}&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;{{ catNavigation.id(126).first.title|raw }}&lt;/h2&gt;
</code></pre>

<p>This is giving me an error.</p>

<pre><code>Array to string conversion
</code></pre>

<p>So I guess I can't grab pieces of an element criteria model after using find.</p>

<p>If I don't use find, then I query the DB multiple times.</p>

<p>I thought about creating my own array of categories dynamically with twig, but that has not gone well.</p>

<p>Is there a way to accomplish what I'm after? It's as if I need to search within an array of data that has already been returned from the DB. Hope that makes sense.</p>

CC BY-SA 3.0
2016-08-10T17:13:57.053Z
If you've applied `.find()` (which you have), then you're no longer dealing with an Element Criteria Model. The `find` parses the ECM into an array of elements (in this case, an array of Category Models).
Lindsey D
CC BY-SA 3.0
2016-08-10T17:28:04.483Z
I see, is there a way to grab individual categories from that array. Or would trying to search through an array of category models negate any performance increase gained by reducing queries?
mjr
CC BY-SA 3.0
2016-08-10T18:58:31.987Z
Unless you have a _serious_ amount of categories, looping through them a few times will almost certainly be more performant than having an X amount of additional database queries. You can even make use of the [NM Break and Continue](https://github.com/marionnewlevant/craft-mnbreakandcontinue) plugin in order to break out of the `for` loop as soon as you have a match. If that doesn't float your boat, I added an answer with a different approach below – it requires looping through all the categories, but only once :)
Mats Mikkel Rummelhoff
<categories><elementcriteriamodel><performance>
Query all categories once and get individual categories from array
# Query all categories once and get individual categories from array

(Asked by: username_0 on 2016-08-10)

I am working on a site that needs to both list all categories and also pull out individual categories by their id.
I would like to query the DB once for all the categories (to improve performance), and then pull out which ones I want, but am struggling to find a solution.
{% set catNavigation = craft.categories.group('products').limit(null).find() %}

{{ catNavigation.id(102).first.title|raw }}
{{ catNavigation.id(126).first.title|raw }}

This is giving me an error.
Array to string conversion

So I guess I can't grab pieces of an element criteria model after using find.
If I don't use find, then I query the DB multiple times.
I thought about creating my own array of categories dynamically with twig, but that has not gone well.
Is there a way to accomplish what I'm after? It's as if I need to search within an array of data that has already been returned from the DB. Hope that makes sense.

**Tags:** categories, elementcriteriamodel, performance
> **Comments:**
> **username_1**: If you've applied `.find()` (which you have), then you're no longer dealing with an Element Criteria Model. The `find` parses the ECM into an array of elements (in this case, an array of Category Models).  
> **username_0**: I see, is there a way to grab individual categories from that array. Or would trying to search through an array of category models negate any performance increase gained by reducing queries?  
> **username_2**: Unless you have a _serious_ amount of categories, looping through them a few times will almost certainly be more performant than having an X amount of additional database queries. You can even make use of the [NM Break and Continue](https://github.com/marionnewlevant/craft-mnbreakandcontinue) plugin in order to break out of the `for` loop as soon as you have a match. If that doesn't float your boat, I added an answer with a different approach below – it requires looping through all the categories, but only once :)  

---

## 1 answers

### Answer by username_2 on 2016-08-10. Score: 4 | Accepted answer

Your issue is that as soon as you call .find(), your ElementCriteriaModel returns an array of elements. In other words, the ElementCriteriaModel is gone, and it's no longer possible to add parameters (i.e. id()) or call other methods that you'd otherwise be able to call on that object (i.e. .first()).
On the other hand – like you say in your description; if you don't use .find() (keeping the ElementCriteriaModel around) you'll hit the database every time you use the .id() method.
A workaround could be to loop over the categories and add them to an indexed array (using the category IDs as keys) and then pull your individual categories from that index instead of the original array.
Important: One gotcha to note is that the |merge filter will actually renumber numeric keys, so you have to add an arbitrary character to the ID to retain the actual ID values (the example below uses an underscore prefix):
{# Get the categories #}
{% set categories = craft.categories.group('products').limit(null).find() %}

{# Create category index #}
{% set categoryIndex = {} %}
{% for category in categories %}
    {% set categoryIndex = categoryIndex|merge({('_'~category.id): category}) %}
{% endfor %}

{# Do your thing #}
{{ categoryIndex['_102'].title|raw }}
{{ categoryIndex['_126'].title|raw }}

Note that when you want to pull a category from the index, categoryIndex['_102'] and categoryIndex._102 will both work – I just find that the former looks a bit cleaner, with the underscore in front of it (obviously you can use any character, it doesn't have to be an underscore).
Edit
As pointed out by @carlcs in the comments below, another approach to creating the index is to use the group filter. With this approach you'll avoid the underscore hack and the for loop. The gotcha here is that this filter will actually create an array of arrays, meaning you'll have to use categoryIndex[102][0] (note the [0] at the end) to access a specific category (you could also use categoryIndex[102]|first, of course):
{% set categories = craft.categories.group('products').limit(null).find() %}
{% set categoryIndex = categories|group('id') %}

{{ categoryIndex[102][0].title|raw }}
{{ categoryIndex[126][0].title|raw }}

> **Comments:**
> **username_3**: Good solution, I was just thinking that you might be able to achieve the same using the group filter, and It might even be possible to do so without this underscore hack that is required because of how merge works. `categories|group('id')`  
> **username_2**: Good idea, @carlcs – the `group` filter is definitely an option, too. I'll edit my answer to include it.  

---

