Méthode de création d'une sous-requête à l'aide de JDatabase

31

Sur http://docs.joomla.org/Selecting_data_using_JDatabase , il n'y a pas de méthode documentée pour écrire une sous-requête à l'aide de JDatabase.

https://gist.github.com/gunjanpatel/8663333 illustre une façon d'y parvenir avec (quelques bits omis):

$subQuery = $db->getQuery(true);
$query    = $db->getQuery(true);

// Create the base subQuery select statement.
$subQuery->select('*')
    ->from($db->quoteName('#__sub_table'))
    ->where($db->quoteName('subTest') . ' = ' . $db->quote('1'));

// Create the base select statement.
$query->select('*')
    ->from($db->quoteName('#__table'))
    ->where($db->quoteName('state') . ' = ' . $db->quote('1'))
    ->where($db->quoteName('subCheckIn') . ' IN (' . $subQuery->__toString() . ')')
    ->order($db->quoteName('ordering') . ' ASC');

// Set the query and load the result.
$db->setQuery($query);

Cela semble être une bonne approche plausible, mais y en a-t-il une meilleure?

entre cervelle
la source
4
Vous pouvez omettre d'appeler toString () sur $ subQuery. Joomla! le traitera automatiquement pour vous. En dehors de cela, j'utilise cette même méthode et cela fonctionne bien pour moi.
Zachary Draper
C'est également la même méthode que nous utilisons dans com_content dans le noyau github.com/joomla/joomla-cms/blob/staging/components/…
George Wilson
@ZacharyDraper intéressant. Pouvez-vous montrer le code qui en est responsable?
Dmitry Rekun
3
@ZacharyDraper: PHP (plutôt que Joomla! En soi) le gère pour vous ( __toString()) est une méthode "magique".
MrWhite
Oui, merci w3d.
Zachary Draper

Réponses:

16

Oui, en ce qui me concerne, la façon dont vous avez construit la sous-requête est celle adoptée par la majorité des développeurs d'extensions de joomla.

J'utilise cette même méthode sur certaines de mes extensions et extensions personnalisées faites pour les clients.

Il n'y a pas de façon "officielle" de le faire, mais le faire comme vous l'avez montré vous permet d'utiliser le générateur de requêtes tout en conservant une bonne lisibilité

Skullbock
la source
10

AFAIK, il n'y a pas de méthode intégrée pour effectuer des sous-requêtes faciles, ce qui est probablement une lacune du système et devrait être corrigé via PR.

Cependant, je ne vois aucun problème avec votre exemple - semble assez raisonnable.

~~~

Voici un exemple en réponse au commentaire de @ DavidFritsch ci-dessous. Mais plus j'y pense, mieux j'aime l'approche plus simple affichée dans l'OP. C'est plus clair ce qui se passe.

$query = $this->db->getQuery(true)
  ->select('a.*')
  ->subQuery()
    ->select('b.*')
    ->from('#__table_b AS b')
    ->as('subQueryResult')
  ->endSubQuery()
  ->from('#__table_a AS a');
Don Gilbert
la source
1
Avez-vous une idée de comment cela pourrait fonctionner? J'essaie d'imaginer le format que vous utiliseriez pour faire fonctionner cela sur un objet de requête et rien ne semble plus facile que cette méthode.
David Fritsch
1
Il pourrait être utile de créer une subQuerySelectméthode dans laquelle elle vous permet de le faire un peu plus "proprement". Je vais modifier ma réponse pour fournir et un exemple.
Don Gilbert du
J'aimerais voir ça à Joomla
fruppel
3

Il existe également un moyen d'exécuter des requêtes contenant des sous-requêtes à l'aide de l'API Joomla Platform. L'idée de base sur la façon d'utiliser les sous-requêtes est basée sur gunjanpatel .

Voici un exemple pour exécuter des requêtes sur des modèles d'ensembles imbriqués :

Requête SQL:

-- Find the Immediate Subordinates of a Node
SELECT node.title, (COUNT(parent.id) - (sub_tree.depth + 1)) AS depth
FROM lubd3_usergroups AS node,
        lubd3_usergroups AS parent,
        lubd3_usergroups AS sub_parent,
        (
                SELECT node.id, (COUNT(parent.id) - 1) AS depth
                FROM lubd3_usergroups AS node,
                        lubd3_usergroups AS parent
                WHERE node.lft BETWEEN parent.lft AND parent.rgt
                        AND node.id = 1
                GROUP BY node.id
                ORDER BY node.lft
        )AS sub_tree
WHERE node.lft BETWEEN parent.lft AND parent.rgt
        AND node.lft BETWEEN sub_parent.lft AND sub_parent.rgt
        AND sub_parent.id = sub_tree.id
GROUP BY node.id
-- not showing the parent node
HAVING depth = 1
-- showing the parent node
-- HAVING depth <= 1
ORDER BY node.lft;

et la requête transformée à exécuter par Joomla:

// Create the subQuery select statement.
// Nested Set Queries: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
// CROSS JOIN: http://www.informit.com/articles/article.aspx?p=30875&seqNum=5
$subQuery->select(array('node.id', '(COUNT(parent.id) - 1) AS depth'))
    ->from($db->quoteName('#__usergroups') . 'node')
    ->join('CROSS', $db->quoteName('#__usergroups', 'parent'))
    ->where($db->quoteName('node.lft') . ' BETWEEN  ' . $db->quoteName('parent.lft') . ' AND ' . $db->quoteName('parent.rgt') . ' AND ' . $db->quoteName('node.id') . ' = ' . $db->quote('1'))
    ->group($db->quoteName('node.id'))
    ->order($db->quoteName('node.lft'));

// Create the base select statement.
$query->select(array('node.title', '(COUNT(parent.id) - (sub_tree.depth + 1)) AS depth'))
    ->from($db->quoteName('#__usergroups') . 'node')
    ->join('CROSS', $db->quoteName('#__usergroups', 'parent'))
    ->join('CROSS', $db->quoteName('#__usergroups', 'sub_parent'))
    ->join('CROSS', '(' . $subQuery .') AS sub_tree')
    ->where($db->quoteName('node.lft') . ' BETWEEN  ' . $db->quoteName('parent.lft') . ' AND ' . $db->quoteName('parent.rgt')
    . ' AND ' . $db->quoteName('node.lft') . ' BETWEEN  ' . $db->quoteName('sub_parent.lft') . ' AND ' . $db->quoteName('sub_parent.rgt')
    . ' AND ' . $db->quoteName('sub_parent.id') . ' = ' . $db->quoteName('sub_tree.id'))
    ->group($db->quoteName('node.id'))
    ->having($db->quoteName('depth') . ' = ' . $db->quote('1'))
    ->order($db->quoteName('node.lft'));

// Set the query and load the result.
$db->setQuery($query);
$rowList = $db->loadAssocList();

echo "<pre>";
print_r($rowList);
echo "</pre>";
Mario Neubauer
la source
1
Semble bon mais est absolument de la même manière que dans l'exemple de l'OP: créez d'abord la sous-requête et utilisez-la ensuite dans la requête principale. La question était de savoir s'il y avait une meilleure façon.
fruppel
1

Je vais proposer ma version de l'extrait de code, puis expliquer ma justification et inclure des citations du manuel Joomla Coding Standards (qui sera formaté entre guillemets).

$subquery = $db->getQuery(true)
    ->select("checkin")
    ->from("#__sub_table")
    ->where("subTest = 1");

$query = $db->getQuery(true)
    ->select("*")
    ->from("#__table")
    ->where([
        "state = 1",
        "subCheckIn IN ({$subQuery})"
    ])
    ->order("ordering");

$db->setQuery($query);

Utilisez le chaînage des requêtes pour connecter un certain nombre de méthodes de requête, l'une après l'autre, chaque méthode renvoyant un objet pouvant prendre en charge la méthode suivante. Cela améliore la lisibilité et simplifie le code résultant.

  • J'écris d'abord les requêtes les plus internes et je passe à la requête la plus externe. Cela me permet de chaîner toutes les méthodes de construction de requêtes directement à la getQuery()méthode. En effet, le nom de la variable n'est écrit qu'une seule fois lors de la création de la requête individuelle.
    Voici un excellent exemple d'imbrication de requêtes lourdes (quand je pensais que c'était mignon d'aligner les flèches de chaînage).

  • J'essaye d'éviter de faire plusieurs select()et / ou where()appels dans la même requête car je l'ai vu conduire à la confusion de développeurs moins expérimentés . Parce que ces méthodes acceptent les tableaux, je trouve qu'il est plus lisible et mieux de les utiliser pour les coder.

  • et enfin le sujet le plus controversé ...

    Les noms de table et les noms de colonne de table doivent toujours être inclus dans la méthode quoteName () pour échapper au nom de table et aux colonnes de table. Les valeurs de champ vérifiées dans une requête doivent toujours être incluses dans la méthode quote () pour échapper la valeur avant de la transmettre à la base de données. Les valeurs de champ entier vérifiées dans une requête doivent également être de type cast en (int).

    Je suis très opposé à cette position. Quand je suis arrivé à Joomla l'année dernière, je pensais que je ne ferais pas d'appels inutiles (aucun avantage pour la stabilité, la sécurité, la lisibilité de la requête) sur des valeurs statiques! Cependant, mon employeur aime l'idée de suivre la ligne Joomla, et je dois admettre que j'ai généralement une haute appréciation pour les règles, donc je suis arrosant mes questions avec quote(), (int)et quoteName()qui signifie aussi des tas de concaténation de chaînes (tous correctement espacés). Les résultats finaux de mon travail sont des blocs de requête horriblement gonflés que même moi, j'ai du mal à regarder. Les lignes les pires / les plus longues qui ne se prêtent pas à l'empilement vertical sont les join()appels en raison du nom de la table, de l'alias ON, puis d'une ou plusieurs conditions qui peuvent ou non nécessiter des guillemets.Je peux comprendre que cette politique est mise en œuvre avec la sécurité à l'esprit pour les développeurs novices, mais j'aimerais bien que cette politique soit en quelque sorte tempérée par la sensibilité que tous les codeurs Joomla ne sont pas des copieurs-ignorants. Je veux dire, regardez à quel point le code est propre et bref sans les appels inutiles.

  • Quant au nettoyage:

    • Je n'utilise presque jamais *dans mes clauses SELECT
    • Je n'appelle jamais __toString()
    • Je ne cite pas d'entiers, je les mets en entiers
    • Je n'écris pas ASCparce que c'est la direction de tri par défaut
    • Je m'efforce de ne pas utiliser de mots clés mysql lors de la création de nouveaux noms de table et de colonne
    • Par préférence personnelle, j'ai tendance à utiliser des guillemets doubles sur les arguments de chaîne de ma méthode pour maintenir l'uniformité, à distinguer des guillemets simples de mysql, et à pouvoir profiter d'une interpolation variable que j'écris généralement avec une " syntaxe complexe ".
    • J'utilise des noms de variable informatifs et des commentaires pour aider à la lisibilité de mes requêtes imbriquées et de mon code en général
    • Je teste mon code avant qu'il ne quitte ma garde
mickmackusa
la source