Not only (DATA) loops can execute SQL requests [1], but they can run on every kind of data lists.
For example:
- a data array produced by any function (including PHP iterators)
 - a local file’s content, format XML, CSV, JSON, YAML, ...and so on.
 - a files list in a server’s directory
 - a web service request
 - a SQL request (e.g. as calculated by SPIP)
 - and so on. (LDAP, ...).
 
 (DATA) loop’s syntax and formats
That loop can iterate on any data array. It’s basic syntax is as follows:
<BOUCLE_example(DATA){source format, data}>
	#TAGS
</BOUCLE_example>
The {source format, data} criterion defines the data on which the loop should iterate.
A data source definition needs two elements:
— The data part: this element can be of various natures:
-  a data array, for example:  #ENV*
-  path to a file on the hard drive, example: sources/definitions.csv
-  a file or a web service’s URL, for example: http://per.sonn.es/bases/phobia.fr.yaml
-  or any other string the format will be able to convert to a data array, ex: "select * from flickr.photos.search where text='spip'"
— The format part is to be chosen from the following list:
-  table (a.k.a. array or tableau), for a previously declared array
-  csv, json, yaml for any file using one of those formats
-  file to loop on some file’s lines
-  glob or pregfiles to loop on files in a given directory (and more...)
-  rss (a.k.a atom) to read any news feed
-  plugins to list all enabled plugins on the website
-  yql to query on Yahoo Query Language web service
-  sql to send a raw request to the SQL server (you should use {source sql, connector:query} to query an external database)
-  ics to loop on calendars (needs icalendar plugin: read Plugin iCalendar for more)
-  and so on.
All those formats are already available and it’s very easy to add new ones, creating a simple function inc_FORMAT_to_array($u). As an example, here is the function that converts a JSON file to an array:
function inc_json_to_array_dist($u) {
  if (is_array($json = json_decode($u))
  OR is_object($json))
    return (array) $json;
}
We can declare a new (DATA) loop in a inc/my_source_to_array.php file
function inc_my_source_to_array_dist($data,$param1='',$param2='') {
     // $data contains (local | distant) file's content or, variable's value transmitted in criterion.
    // $param1, $param2... are optional parameters
...
}
and then display it:
<BOUCLE_example(DATA){source my_source,test}>
	#VALEUR
</BOUCLE_example>
Cache: 
-  The result of a DATA loop is cached. This behaviour can be disabled with the {datacache 0} criterion.
-  See more info on DATA loop caches
Filtering, ordering, paginating, merging
Filtering. Like any other SQL loops, (DATA) loops can be filtered with criteria like {valeur=x}: available operators are =, >, <, >=, <=, == (rational expression) and LIKE
However, this filtering is not applied beforehand — during the request, like we do in SQL — but afterwards, on the table of data initially returned.
Ordering. All kind of {par xx} orderings are also possible, with their variant {!par xx} to backward orders.
Paginating. Pagination works normally, so does the offset/limit {a,b} criterion.
Merging. The {fusion /x/y} criterion also works. For example, on a CSV file containing addresses, if the email field is numbered 3, we’ll then be able to keep only one record per email address with the following loop:
 <BOUCLE_csv(DATA){source csv, addresses.csv}{fusion /3}{par /0}{'<br>'}> 
 	#VALEUR{0}: #VALEUR{3} 
 </BOUCLE_csv> 
Merging is done after ordering and, only retains the first element it met. That way, if an array is ordered by {!par date} then merged with the email field, the  entry that will be retained for each email address, will be the latest one.
 {liste ...} criterion 
To simplify the writing of data arrays, when it comes to a simple list of items we want to manually provide, the (DATA) loop also accepts {liste ...} criterion, which let you build an array, separating each data with a comma ",".
Loop:
<BOUCLE_i(DATA){liste 3,4,5}{"<br>"}>
	<BOUCLE_j(DATA){liste 6,7,8}{" "}>
		[(#VALEUR|mult{#_i:VALEUR})]
	</BOUCLE_j>
</BOUCLE_i>
returns:
18 21 24
24 28 32
30 35 40
Note: We used #_i:VALEUR in j loop, as a reference for the value calculated by i loop.
 {enum ...} criteria 
<BOUCLE_enumerate(DATA){enum 2,10,2}>
      #VALEUR
</BOUCLE_enumerate>
returns:
2 4 6 8 10
<BOUCLE_enum(DATA){enum g,m}{", "}>
	#VALEUR
</BOUCLE_enum>
returns:
g, h, i, j, k, l, m
val1 and val2 are two numeric values or, 2 characters. SPIP finding out which one, of the 2 values, is the smallest, this loop is going to enumerate values between val1 and val2. In the first variant, no interval is set: it’s worth 1 by default.
Protecting iterated data
(DATA) loop automatically protects every tag inside it, because the data we carry on often comes from unknown, potentially harmful sources. This also affects forms tags (#FORMULAIRE_xx) that you’ll have to call with a "*", as explained in the following article on #TAG* and #TAG**
<BOUCLE_foreach_protect(DATA){liste a,b,c,d,e}>
	#FORMULAIRE_XXX*{#VALEUR}
</BOUCLE_foreach_protect>
Note that, if you use the "Bonux" plugin, it’s going to take a simple loop (POUR), which does not have that behaviour.
<BOUCLE_Foreach_non_protect(POUR){liste a,b,c,d,e}>
	#FORMULAIRE_XXX{#VALEUR}
</BOUCLE_Foreach_non_protect>
Read more: have fun with some `BOUCLE(DATA)` loop examples !