SALT > Manuel > Tutoriel > Base de données > API Query >

Création d'une requête

Une requête SELECT se créé à partir de l'objet que l'on souhaite récupérer avec la méthode statique ::query().
<?php
$q 
MonObjet::query();
Cela permettra à la requête de récupérer les méta-données de l'objet (nom de la table, nom et types des champs, etc...)

Choix des champs à récupérer

Lors de la création de la requête, on peut indiquer un paramètre TRUE pour dire qu'on souhaite récupérer tout les champs.
Le framework ne génèrera jamais de SELECT * pour éviter de récupérer des champs non mappés et pour contrôler la manière dont ils sont récupérés.
<?php
$q 
MonObjet::query(TRUE); // créé une requête qui récupère tout les champs de l'objet
Si on veux récupérer une liste de champs de l'objet, il suffit de les lister avec selectFields(array) : <?php
$q 
MonObjet::query();
$q->selectFields(array('champ1''champ2''champ3'));
Si on souhaite indiquer les champs à récuperer un à un ou si on veux donner un alias particulier à un champ, on doit appeler selectField($field, $alias = NULL) <?php
$q
->selectField('champ1''champ1withAlias');
Enfin, pour les cas les plus complexes, si ce qu'on souhaite récupérer n'est pas un champ de l'objet par exemple, ou est une fonction d'un champ de l'objet, on doit utiliser select(SqlExpr $expr, $alias = NULL)
Dans ce cas, il faudra toujours spécifier un $alias, il n'est facultatif que dans le cas ou SqlExpr est un champ d'une autre requête, dans ce cas là le nom du champ sera utilisé si l'alias n'est pas fourni.
<?php
$q
->select(SqlExpr::_MAX($q->champ1), 'maxChamp1'); // ajoute SELECT MAX(t0.champ1) as maxChamp1

Récupération de champs non mappés

Il est possible de récupérer des champs qui ne sont pas mappés dans l'objet
Par exemple, lorsqu'on récupère un COUNT ou un MAX, la colonne ne peut être mappé dans l'objet

Pour cela, il suffit de les ajouter à la clause SELECT, ils seront automatiquement mappés sur des propriétés dynamiques de l'instance des objets récupérés et accessibles normalement comme des champs déclarés.
Cependant, ils seront tous déclarés comme des champs de type TEXT pouvant être NULL. De plus, la méthode COLUMN() affichera le nom du champs au lieu d'un texte représentant le champ. Ce comportement peut être redéfini dans un DAOConverter
<?php
$qPerson 
Person::query();
$qCity City::query();
$qCity->join($qPerson'id''='$qPerson->city);

$qCity->selectField('city_name');
$qCity->select// ajout d'un champ supplémentaire
        
SqlExpr::_COUNT($qPerson->person_name->distinct()),
        
'nbPerson' // alias du champ
        
);

// lors de l'exécution, la propriété 'nbPerson' sera automatiquement créée et accessible
$result $DBtest->execQuery($qCity);

$city salt\first($result->data);
echo 
$city->city_name// affiche le champ déclaré
echo $city->nbPerson// affiche le champ supplémentaire non déclaré
echo City::COLUMN('city_name'); // affiche le nom du champ
echo City::COLUMN('nbPerson'); // affiche "nbPerson"

$city2 = new City();
echo 
$city2->nbPerson// leve une exception : champ inexistant sur cette instance

Clause WHERE

Il y a 2 méthodes principales pour ajouter des conditions : whereAnd et whereOr et elles fonctionnent de la même manière.
whereAnd va ajouter une condition "AND ..."
whereOr va ajouter une condition "OR ..."
Il est déconseillé de mixer les deux dans une même instance de requête, les résultats étant imprévisibles. (voir Sous-requête plus bas pour cela)

Les méthodes whereAnd et whereOr prennent 3 paramètres :
<?php
$q 
MonObjet::query();

// exemple simple
$q->whereAnd('champ''>='1);                   // AND champ >= :vX
// avec un tableau de valeurs, et un OR : éviter de mixer AND et OR comme ca
$q->whereOr('champ''IN', array('a''b''c')); // OR champ IN (:vX, :vY, :vZ)
// exemple d'injection SQL dans l'opérateur... à éviter ;o)
$q->whereAnd('champ''=1 OR 1=1 OR 1='0);      // AND champ =1 OR 1=1 OR 1= :vX

// pour IS NULL / IS NOT NULL
$q->whereAnd('champ''IS'SqlExpr::value(null));        // AND champ IS NULL
$q->whereAnd('champ''IS'SqlExpr::value(null)->not()); // AND champ IS NOT NULL

// condition complexe : AND CONCAT(champ1, :v0) = CONCAT(:v1, champ2)
$q->whereAnd(
    
SqlExpr::_CONCAT($q->champ1'@'),
    
'=',
    
SqlExpr::_CONCAT('@'$q->champ2));
Le 1er et le 3ème paramètre ne sont pas interchangeable :

Clause WHERE - Objets

Il est possible d'ajouter automatiquement une condition sur le champ principal d'un objet que l'on a déjà récupéré.
Le champ principal est celui enregistré dans la méthode metadata() avec registerId. <?php
$q 
MonObjet::query();

$q->whereAndObject($monObjet); // avec un objet simple, :v0 est la valeur du champ "id" de $monObjet
echo $q->toSQL(); // SELECT FROM mon_objet t0 WHERE t0.id = :v0

$q->whereAndObject(array($monObjet1$monObjet2)); // avec une liste d'objets
echo $q->toSQL(); // SELECT FROM mon_objet t0 WHERE t0.id IN (:v0, :v1)

Clause WHERE - Sous-requête

Pour ajouter des parenthèses et ordonner les conditions, il faut créer des sous requêtes à l'aide de getSubQuery(). Si on utilise un nouvel appel à ::query(), l'alias sera différent et les champs ne correspondront pas.
Une fois la sous requête créée, on utilisera whereAndQuery ou whereOrQuery pour l'ajouter à la requête principale. <?php
$q 
MonObjet::query();

// Création d'une sous requete pour ajouter des parenthèses autour des conditions OR
$subQuery $q->getSubQuery();
$subQuery->whereOr('champ2''=''1');
$subQuery->whereOr('champ2''=''2');

// Conditions sur la requete principale
$q->whereAnd('champ1''=''aze');
$q->whereAndQuery($subQuery);

echo 
$q->toSQL(); // SELECT FROM mon_objet t0 WHERE t0.champ1 = :v2 AND (t0.champ2 = :v0 OR t0.champ2 = :v1)

Clause WHERE - EXISTS

La méthode whereAndExists($otherQuery) permet de générer une condition de la forme : WHERE EXISTS (SELECT 1 FROM ...)
<?php
// Recherche des villes contenant au moins une personne
$q City::query(TRUE);

// Construction de la requête sur les personnes
$q2 Person::query();
$q2->whereAnd('city''='$q->id); // on référence le champ ID de $q, c'est à dire de City

$q->whereAndExists($q2); // on fait le lien entre les deux requêtes

echo $q->toSQL(); // affiche :
// SELECT t0.id as id, t0.city_name as city_name
// FROM city t0
// WHERE EXISTS (
//        SELECT 1
//        FROM person t1
//        WHERE t1.city = t0.id
// )
On peut également utiliser whereAndExists($otherQuery, FALSE) pour générer WHERE NOT EXISTS

Jointure

L'ajout d'une jointure avec une autre table se fait avec join()
Les paramètres sont : Il n'est pas possible d'ajouter une jointure sans avoir au moins une condition dans la clause ON. Maintenant, vous pouvez construire une condition du genre 1=1 si vous voulez.
Le paramètre $type peut être changé pour indiquer un autre type de jointure valide. Par exemple 'LEFT OUTER'

Une fois la jointure réalisée on peut utiliser la requête jointe pour récupérer un de ses champs avec ->champ et utiliser ce champ dans la requête principale, que ce soit pour le récupérer (clause SELECT), pour ajouter une condition dessus (clause WHERE) ou encore pour les clauses GROUP BY et ORDER BY
<?php
$qCity 
City::query();
$qPerson Person::query();

$qCity->join($qPerson'id''='$qPerson->city); // réalisation de la jointure

$qCity->selectField('city_name');
// Récupération d'un champ de la requête jointe
$qCity->select($qPerson->person_name);

// Utilisation d'un champ de la requête jointe dans une condition
$qCity->whereAnd($qPerson->id'IN', array(12));

// Utilisation d'un champ de la requête jointe dans un ORDER BY
$qCity->orderAsc($qPerson->person_name);

echo 
$qCity->toSQL(); // affiche :
// SELECT t0.city_name as city_name, t1.person_name as person_name    // Le champ récupéré
// FROM city t0
// INNER JOIN person t1 ON t0.id = t1.city
// WHERE t1.id IN (:v0, :v1)                     // la condition WHERE avec le champ de la jointure
// ORDER BY t1.person_name ASC                   // l'ORDER BY avec le champ de la jointure

Jointure - Clause ON

Il est également possible de construire une clause ON un peu plus complexe avec les 4 méthodes joinOnAnd, joinOnAndQuery, joinOnOr, joinOnOrQuery

Ces méthodes prennent les mêmes paramètres que les méthodes classiques whereAnd, whereOr, whereAndQuery, whereOrQuery à l'exception du 1er paramètre supplémentaire qui doit être la requête jointe afin d'identifier à quelle jointure ajouter la clause en cas de jointure multiples.
<?php
$qCity 
City::query();
$qPerson Person::query();

$qCity->join($qPerson'id''='$qPerson->city); // jointure simple

$qCity->selectField('city_name');

// Création d'une sous requête pour ajouter à la clause ON
$qCity2 $qCity->getSubQuery();                   // Uniquement pour l'exemple, il serait plus simple
$qCity2->whereOr('city_name''=''Paris');       //   de faire un
$qCity2->whereOr('city_name''=''Toulouse');    //   whereAnd('city_name', 'IN', array('Paris', 'Toulouse'))

// Ajout à la clause ON : on indique à nouveau $qPerson pour identifier la jointure.
$qCity->joinOnAndQuery($qPerson$qCity2);

echo 
$qCity->toSQL(); // affiche :
// SELECT t0.city_name as city_name
// FROM city t0
// INNER JOIN person t1
//        ON t0.id = t1.city
//        AND (t0.city_name = :v0 OR t0.city_name = :v1) // sous requête ajoutée sur la clause ON

Jointure - SELECT

Enfin, il est possible de générer une jointure avec une table construite à la volée en utilisant joinSelect <?php
$qCity 
City::query();
$qCity->selectField('city_name');

$qPerson Person::query();
$qPerson->selectField('person_name');
$qPerson->selectField('city');

$qCity->joinSelect($qPerson'id''='$qPerson->city); // joinSelect

$qCity->selectField($qPerson->person_name); // ajout d'un champ de la requête jointe

echo $qCity->toSQL(); // affiche :
// SELECT t0.city_name as city_name, t1.person_name as person_name
// FROM city t0
// INNER JOIN (
//        SELECT t1.person_name as person_name, t1.city as city
//        FROM person t1
// ) t1
//        ON t0.id = t1.city

Order By, Group By

Les clauses ORDER BY et GROUP BY peuvent être ajoutées avec orderAsc(), orderDesc(), groupBy()
Les 3 méthodes prennent un champ ou un SqlExpr en paramètre.

Utilisation d'une autre base de données

Il est possible de spécifier un préfixe sur la table à utiliser afin de référencer une table présente sur une autre base de données du même schéma et auquel l'utilisateur a accès. <?php
MonObjet
::query()->absoluteDatabase('NomDatabase'); // va générer une requête SELECT ... FROM NomDatabase.mon_objet t0 ...

Exécution d'une requête

Une fois la requête construite, on peut procéder à son exécution sur une base de données en utilisant une instance de DBHelper avec la méthode execQuery()

Les paramètres de execQuery sont :

L'objet Pagination est décrit dans la section suivante.

Pagination

Pour utiliser une pagination des résultats, il faut commencer par créer l'objet Pagination en lui donnant l'indice de début d'affichage. Lors de la 1ère requête, cet indice (offset) sera à 0 ou à NULL, mais pour les requêtes suivantes, il prendra la valeur adéquate correspondant à la page consultée.

En général on va donc créer l'objet pagination en lui passant en paramètre une variable récupérée de la requête. Par exemple : <?php
$pagination 
= new Pagination($Input->G->RAW->offset);
La taille de la page est par défaut de 100 éléments.
On peut changer cette taille en indiquant un 2ème paramètre au constructeur de Pagination <?php
$pagination 
= new Pagination($Input->G->RAW->offset50); // pages de 50 éléments seulement
Lorsqu'on va passer cet objet à la méthode execQuery, il va être modifié afin de contenir le nombre total de résultats (calculés par une requête COUNT générée par le framework). Ce nombre est accessible via getCount() après l'exécution de la requête.

La requête exécutée sera modifiée avec l'ajout d'une clause LIMIT $offset, $taillePage

L'objet contient des méthodes permettant de construire les liens pour aller d'une page à l'autre :
Enfin, si l'on souhaite exécuter une requête contenant une clause LIMIT a,b mais sans gérer de pagination (par exemple pour récupérer le 1er résultat d'une requête, on peut utiliser LIMIT 0, 1), on doit spécifier un 3ème paramètre au constructeur comme dans l'exemple ci-dessous. Cela permet d'éviter d'exécuter la requête de COUNT, inutile ici. <?php
$q 
MonObjet::query(TRUE);

$pagination = new Pagination(050);
$r $db->execQuery($q$pagination);
// Va produire les requêtes suivantes :
// SELECT count(*) as nb FROM mon_objet t0
// SELECT t0.... FROM mon_objet t0 LIMIT 0,50

$pagination = new Pagination(01TRUE); // avec le 3eme paramètre à TRUE, $pagination->getCount() ne sera pas renseigné
$r $db->execQuery($q$pagination);
// Va produire les requêtes suivantes :
// SELECT t0.... FROM mon_objet t0 LIMIT 0,1

Utilisation des résultats

La méthode execQuery renvoi un objet DBResult
Cet objet contient : Un cas classique d'utilisation est l'affichage des résultats sous forme de tableau :
<?php
$q 
MonObjet::query(TRUE);
...
$result $DB->execQuery($q);
?>
<table>
    <tr>
<?php // Affichage du nom des colonnes au debut du tableau ?>
<?php 
foreach($result->columns as $col) { ?>
        <th><?= MonObjet::COLUMN($col?></th>
<?php ?>
    </tr>
<?php // Affichage de chaque ligne ?>
<?php 
foreach($result->data as $row) { ?>
    <tr>
    <?php // Pour chaque ligne on boucle sur l'ensemble des colonnes pour les afficher ?>
    <?php foreach($result->columns as $col) { ?>
        <td><?= $row->VIEW->$col ?></td>
    <?php ?>
    </tr>
<?php ?>
</table>
Un autre cas d'utilisation est la récupération du 1er objet d'une requête, par exemple si la requête ne récupère qu'une seule ligne : <?php
$q 
MonObjet::query();
...
$monObjet salt\first($DB->execQuery($q)->data);
?>