Regardless of the number of elements involved, Craft will return an iterable ElementCriteriaModel whenever you query for content (e.g. via craft.entries
, or using relational FieldTypes like Assets, Entries or Categories).
This means that even if you know that there'll only be a single element returned (e.g. when you query for a Single section entry, or have a relational field restricted to a single element) using a for
loop to actually access that element is totally viable and whether to actually use a loop or not in such cases, is mostly a matter of personal preference.
Looping on single-element query results may seem counter-intuitive, although you're not actually looping on a single element, but the ElementCriteriaModel (which just happens to "contain" a single element and not several). Personally I tend to only use loops when I actually need loops (i.e. there is or could be more than one element returned), but its up to you.
In any case, beyond personal preference there are a few minor differences to each approach.
Using a {% for %}
loop, it's easy to render alternative content if the query doesn't return anything, by adding an {% else %}
statement:
{% for entry in craft.entries.section('mySingleSection') %}
<h1>{{ entry.title }}</h1>
{% else %}
<p>Sorry, the entry wasn't found!</p>
{% endfor %}
Using .first()
and {% set %}
, you should – in most cases – add an {% if %}
statement testing the value returned, to avoid template errors if the query doesn't return a valid element:
{% set entry = craft.entries.section('mySingleSection').first() %}
{% if entry %}
<h1>{{ entry.title }}</h1>
{% endif %}
Also using .first()
and {% set %}
, it's easy to declare fallback data if the query doesn't return anything, using the |default
filter – this may negate the need for an {% if %}
statement:
{% set imageUrl = entry.assetsField.first().url|default('/assets/fallback.jpg') %}
<img src="{{ imageUrl }}" />
Performance-wise, I doubt there'll be any real-world difference between the two methods, with only a single element involved.
As for the query part of your question; even if you're using a for
loop, Craft won't run the craft.entries
query in your example more than once – which is why you're seeing the same amount of queries in your profiling report. When talking about queries and performance, it's however worth noting that both the amount of queries and the efficiency of those queries are relevant. In your example, the first query will probably be a tiny, tiny bit faster than the second (Gustavs' answer explains why).