Método para crear una subconsulta utilizando JDatabase

31

En http://docs.joomla.org/Selecting_data_using_JDatabase , no existe un método documentado para escribir una subconsulta utilizando JDatabase.

https://gist.github.com/gunjanpatel/8663333 ejemplifica una forma de lograr esto con (se omiten algunos bits):

$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);

Este parece un buen enfoque plausible, pero ¿hay uno mejor?

entre cerebro
fuente
44
Puede omitir llamar a toString () en $ subQuery. Joomla! lo manejará automáticamente por usted. Aparte de eso, uso este mismo método y me está funcionando bien.
Zachary Draper
También es el mismo método que estamos usando en com_content en core github.com/joomla/joomla-cms/blob/staging/components/…
George Wilson
@ZacharyDraper interesante. ¿Puedes mostrar el código responsable de eso?
Dmitry Rekun
3
@ZacharyDraper: PHP (en lugar de Joomla! Per se) lo maneja por usted ( __toString()) es un método "mágico".
MrWhite
Sí, gracias w3d.
Zachary Draper

Respuestas:

16

Sí, en lo que a mí respecta, la forma en que construiste la subconsulta es la adoptada por la mayoría de los desarrolladores de extensiones de joomla.

Utilizo el mismo método en algunas de mis extensiones y extensiones personalizadas para clientes.

No existe una forma "oficial" de hacerlo, pero hacerlo como se muestra le permite usar el generador de consultas y aún así mantener una buena legibilidad

Skullbock
fuente
10

AFAIK no hay una forma integrada de hacer subconsultas fáciles, lo que probablemente sea una deficiencia en el sistema y debe corregirse a través de PR.

Sin embargo, no veo ningún problema con su ejemplo, parece bastante razonable.

~~~

Aquí hay un ejemplo en respuesta al comentario de @ DavidFritsch a continuación. Sin embargo, cuanto más lo pienso, mejor me gusta el enfoque más simple que se muestra en el OP. Está más claro lo que está pasando.

$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
fuente
1
¿Tienes alguna idea de cómo hacer que esto funcione? Estoy tratando de imaginar el formato que usarías para hacer que esto funcione en un objeto de consulta y en realidad nada se siente más fácil que este método.
David Fritsch
1
Puede valer la pena crear un subQuerySelectmétodo en el que le permita hacerlo un poco más "limpiamente". Editaré mi respuesta para proporcionar y ejemplo.
Don Gilbert
Me encantaría ver eso en Joomla
fruppel
3

También hay una forma de ejecutar consultas que contienen subconsultas utilizando la API de la plataforma Joomla. La idea básica sobre cómo usar subconsultas se basa en gunjanpatel .

Aquí hay un ejemplo para ejecutar consultas en modelos de conjuntos anidados :

Consulta 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;

y la consulta transformada para ser ejecutada por 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
fuente
1
Se ve bien pero es absolutamente de la misma manera que en el ejemplo del OP: Realice la subconsulta primero y úsela luego en la consulta principal. La pregunta era si hay una mejor manera.
Fruppel
1

Ofreceré mi versión del fragmento, luego explicaré mi justificación e incluiré citas del manual de estándares de codificación de Joomla (que tendrá un formato de bloque de citas).

$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);

Use el Encadenamiento de consultas para conectar una serie de métodos de consulta, uno tras otro, y cada método devuelve un objeto que puede admitir el siguiente método. Esto mejora la legibilidad y simplifica el código resultante.

  • Escribo las consultas más internas primero y avanzo a la consulta más externa. Esto me permite encadenar todos los métodos de creación de consultas directamente al getQuery()método. Efectivamente, el nombre de la variable solo se escribe una vez al generar la consulta individual.
    Aquí hay un excelente ejemplo de una gran anidación de consultas (cuando pensé que era lindo alinear las flechas de encadenamiento).

  • Intento evitar hacer varias llamadas select()y / o where()llamadas dentro de la misma consulta porque he visto que esto genera confusión entre los desarrolladores menos experimentados . Debido a que estos métodos aceptan matrices, me parece más legible y una mejor práctica de codificación emplearlos.

  • y finalmente el tema más controvertido ...

    Los nombres de tabla y los nombres de columna de tabla siempre deben incluirse en el método quoteName () para escapar del nombre de tabla y las columnas de tabla. Los valores de campo marcados en una consulta siempre deben incluirse en el método quote () para escapar del valor antes de pasarlo a la base de datos. Los valores de los campos enteros marcados en una consulta también deben escribirse a (int).

    Estoy muy en conflicto con esta postura. Cuando llegué por primera vez a Joomla el año pasado, pensé, ¡no voy a hacer llamadas inútiles (sin beneficio para la estabilidad, seguridad, legibilidad de la consulta) en valores estáticos! Sin embargo, mi empleador le gusta la idea de no cruzar la línea de Joomla, y tengo que admitir que por lo general tienen un alto aprecio por las reglas, así que he estado riega abajo de mis consultas con quote(), (int)y quoteName()que también significa un montón de concatenación de cadenas (todo correctamente espaciado). Los resultados finales de mi trabajo son bloques de consulta horriblemente hinchados que incluso a mí me cuesta mucho mirar. Las líneas peores / más largas que no se prestan al apilamiento vertical son las join()llamadas debido al nombre de la tabla, el alias, ONy luego una o más condiciones que pueden o no requerir comillas.Puedo apreciar que esta política se implementa teniendo en cuenta la seguridad para los desarrolladores novatos, pero seguro que me gustaría si esta política se atenuara de alguna manera con la sensibilidad de que no todos los codificadores de Joomla son ignorantes. Quiero decir, mira qué tan limpio y breve se ve el código sin las llamadas innecesarias.

  • En cuanto a la limpieza:

    • Casi nunca uso *en mis cláusulas SELECT
    • Yo nunca llamo __toString()
    • No cito enteros, los lanzo como enteros
    • No escribo ASCporque esa es la dirección de clasificación predeterminada
    • Hago todo lo posible para no usar palabras clave mysql al crear nuevos nombres de tablas y columnas
    • Como cuestión de preferencia personal, tiendo a utilizar comillas dobles en los argumentos de cadena de mi método para mantener la uniformidad, distinguirme de las comillas simples de mysql, y para poder disfrutar de la interpolación variable que escribo típicamente con " sintaxis compleja ".
    • Utilizo nombres informativos de variables y comentarios para ayudar en la legibilidad de mis consultas anidadas, y mi código en general
    • Pruebo mi código antes de que salga de mi custodia
mickmackusa
fuente