Метод создания подзапроса с использованием JDatabase

31

На http://docs.joomla.org/Selecting_data_using_JDatabase нет документированного метода написания подзапроса с использованием JDatabase.

https://gist.github.com/gunjanpatel/8663333 иллюстрирует один из способов сделать это с помощью нескольких пропущенных битов:

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

Это кажется хорошим, правдоподобным подходом, но есть ли лучший?

промежуточный мозг
источник
4
Вы можете опустить вызов toString () в $ subQuery. Joomla! будет автоматически обрабатывать это для вас. Кроме того, я использую тот же метод, и он работает хорошо для меня.
Захари Дрейпер
Это также тот же метод, который мы используем в com_content в ядре github.com/joomla/joomla-cms/blob/staging/components/…
Джордж Уилсон,
@ZacharyDraper интересно. Можете ли вы показать код, который отвечает за это?
Дмитрий Рекун
3
@ZacharyDraper: PHP (а не Joomla! Per se) обрабатывает его для вас ( __toString()) - это «волшебный» метод.
MrWhite
Да, спасибо, w3d.
Захари Дрейпер

Ответы:

16

Да, насколько я понимаю, способ, которым вы создали подзапрос, - тот, который принят большинством разработчиков расширений joomla.

Я использую тот же метод на некоторых из моих расширений и пользовательских расширений, сделанных для клиентов.

Не существует «официального» способа сделать это, но выполнение, как вы показали, позволяет использовать построитель запросов и при этом сохранять хорошую читабельность.

Skullbock
источник
10

AFAIK Не существует встроенного способа выполнения простых подзапросов, что, вероятно, является недостатком системы и должно быть исправлено с помощью PR.

Однако я не вижу проблем с вашим примером - кажется достаточно разумным.

~~~

Вот пример в ответ на комментарий @ DavidFritsch ниже. Однако чем больше я об этом думаю, тем лучше мне нравится более простой подход, отображаемый в ОП. Это более понятно, что происходит.

$query = $this->db->getQuery(true)
  ->select('a.*')
  ->subQuery()
    ->select('b.*')
    ->from('#__table_b AS b')
    ->as('subQueryResult')
  ->endSubQuery()
  ->from('#__table_a AS a');
Дон гилберт
источник
1
У вас есть идеи, как это можно сделать, чтобы работать? Я пытаюсь представить формат, который вы бы использовали, чтобы заставить эту работу работать с одним объектом запроса, и на самом деле нет ничего проще, чем этот метод.
Дэвид Фрич
1
Возможно, стоит создать subQuerySelectметод, в котором он позволяет делать это более «чисто». Я отредактирую свой ответ, чтобы предоставить и пример.
Дон Гилберт
Я хотел бы видеть это в Joomla
Frupppel
3

Существует также способ выполнения запросов, которые содержат подзапросы с использованием API платформы Joomla. Основная идея о том, как использовать подзапросы, основана на gunjanpatel .

Вот пример выполнения запросов на моделях с вложенным множеством :

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;

и преобразованный запрос, который будет выполнен 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>";
Марио Нойбауэр
источник
1
Выглядит хорошо, но абсолютно так же, как в примере с OP: сначала создайте подзапрос, а затем используйте его в основном запросе. Вопрос был в том, есть ли лучший способ.
Frupppel
1

Я предложу свою версию сниппета, затем объясню свое обоснование и включу цитаты из руководства по стандартам кодирования Joomla (которое будет отформатировано как цитата).

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

Используйте цепочку запросов, чтобы соединить несколько методов запроса, один за другим, с каждым методом, возвращающим объект, который может поддерживать следующий метод. Это улучшает удобочитаемость и упрощает полученный код.

  • Сначала я пишу самые внутренние запросы и перехожу к самым внешним запросам. Это позволяет мне связать все методы построения запросов непосредственно с getQuery()методом. По сути, имя переменной записывается только один раз при построении отдельного запроса.
    Вот потрясающий пример некоторого сложного вложения запросов (когда я думал, что это было мило - выстроить стрелки в цепочку).

  • Я стараюсь избегать нескольких select()и / или where()вызовов в рамках одного запроса, потому что я видел, что это приводит к путанице с менее опытными разработчиками . Поскольку эти методы принимают массивы, я считаю их более удобными для чтения и лучшей практики кодирования.

  • и, наконец, самая спорная тема ...

    Имена таблиц и имена столбцов таблицы всегда должны быть заключены в метод quoteName (), чтобы избежать имени таблицы и столбцов таблицы. Значения полей, проверяемые в запросе, всегда должны быть заключены в метод quote () для экранирования значения перед его передачей в базу данных. Целочисленные значения полей, проверяемые в запросе, также должны быть приведены к типу (int).

    Я очень противоречив в этой позиции. Когда я впервые приехал в Joomla в прошлом году, я подумал, что не собираюсь делать бесполезные вызовы (без пользы для стабильности, безопасности, читаемости запроса) статических значений! Тем не менее, мой работодателю нравится идея Лишних линий Joomla, и я должен признать , что я , как правило, высокую оценку для правил, поэтому я поливанием моих запросов с quote(), (int)и quoteName()что также означает кучку конкатенации (все правильно расставлены). Конечными результатами моей работы являются ужасно раздутые блоки запросов, которые даже мне трудно разглядеть. Наихудшие / самые длинные строки, которые не поддаются вертикальному наложению, - это join()вызовы из-за имени таблицы, псевдонима ON, а затем одного или нескольких условий, которые могут требовать или не требовать цитирования.Я могу оценить, что эта политика реализована с учетом требований безопасности для начинающих разработчиков, но я уверен, что она хотела бы, чтобы эта политика была каким-то образом смягчена чувством, что не все кодировщики Joomla являются невежественными копировщиками. Я имею в виду, посмотрим, как чистый и краткий код выглядит без ненужных вызовов.

  • Что касается зачистки:

    • Я почти никогда не использую *в своих предложениях SELECT
    • Я никогда не звоню __toString()
    • Я не цитирую целые числа, я приведу их как целые числа
    • Я не пишу, ASCпотому что это направление сортировки по умолчанию
    • Я прилагаю все усилия, чтобы не использовать ключевые слова mysql при создании новых имен таблиц и столбцов
    • Что касается личных предпочтений, я склонен использовать двойные кавычки в строковых аргументах моего метода, чтобы поддерживать единообразие, отличаться от одинарных кавычек mysql, и поэтому я могу наслаждаться интерполяцией переменных, которую я обычно пишу со « сложным синтаксисом ».
    • Я использую информативные имена переменных и комментарии, чтобы помочь читаемости моих вложенных запросов, и мой код в целом
    • Я проверяю свой код, прежде чем он покинет мою опеку
Микмакаса
источник