Перед прочтением этого материала рекомендуем вам ознакомиться со следующими материалами:
Общая информация
Использование UNION
при составлении запросов позволяет удобно комбинировать результаты двух или более SELECT
запросов, которые необязательно соединены какими-либо отношениями.
Это также может быть полезной оптимизацией производительности. Использование UNION
для объединения результата запросов иногда может быть намного быстрее, чем единичный запрос с WHERE
, в особенности тогда, когда запрос включает в себя объединение с другими большими таблицами.
Для тех, кто хоть немного знаком с теорией набора - UNION
делает именно то, что от него ожидают. Он сливает набор результатов из одного запроса с набором результатов другого запроса в один единый набор результатов.
Для того чтобы использовать UNION
, вы должны быть знакомы с основными требованиями, предъявляемыми SQL-сервером, который вы используете. Они не зависят от Joomla, но если вы не будете с ними считаться, то получите ошибки базы данных. Например, SELECT
запрос должен возвращать такое же самое число полей, в той же самой последовательности и с тем же самым типом данных.
Простой пример
Предположим, что вы хотите сделать рассылку группе людей, но их имена и адреса электронной почты находятся в разных таблицах. Давайте разберем конкретный пример. Вы хотите сделать рассылку клиентам и поставщикам, и их имена и адреса электронной почты находятся в таблицах customers
и suppliers
соответственно.
Этот запрос вытащит всю необходимую информацию по клиентам для рассылки:
$query
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__customers'));
А этот запрос сделает тоже самое по поставщикам:
$query
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__suppliers'));
Вы можете объединить это в один запрос следующим образом:
$query
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__customers'))
->union(
$q2->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__suppliers'))
);
$mailshot = $db->setQuery($query)->loadObjectList();
На самом деле набор результатов, который получается при использовании union()
, будет немного отличаться от отдельных запросов, потому что union()
автоматически удалит дубликаты. Если же вы хотите, чтобы в результирующем наборе остались дубликаты (что в принципе с математической точки зрения уже не является набором), используйте метод unionAll()
вместо union()
.
Разные варианты использования union
Методы union()
и unionAll()
довольно гибки при передаче в них аргументов. Вы можете передать просто строку запроса, объект JDatabaseQuery или массив объектов JDatabaseQuery. Предположим, что у вас есть три таблицы, похожие на пример выше:
$q1
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__customers'))
$q2
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__suppliers'))
$q3
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__ shareholders '));
Тогда все эти запросы дадут один и тот же результат:
// Метод union может стыковаться
$q1
->union($q2)
->union($q3);
// Метод union принимает строковые запросы
$q1
->union($q2)
->union('SELECT name, email FROM shareholders');
// Метод union принимает массив объектов JDatabaseQuery
$q1->union(
array($q2, $q3)
);
// Не важно, какой из объектов является корневым. В данном случае сам запрос будет немного другим, но результат будет таким же.
$q2->union(
array($q1, $q3)
);
Использование UNION вместо OR
В некоторых случаях использование union()
может привести к значительному приросту производительности по сравнению с использованием OR
или WHERE
вместе c IN
.
Предположим, что у вас есть таблица продуктов, и вы хотите выделить только те продукты, которые принадлежат двум конкретным категориям. Обычно это делают так:
$query
->select('*')
->from($db->quoteName('#__products'))
->where(
$db->quoteName('category')
. ' = '
. $db->quote('catA'), 'or')
->where(
$db->quoteName('category')
. ' = '
. $db->quote('catB')
);
$products = $db->setQuery($query)->loadObjectList();
Но, вполне вероятно, что вы увидите прирост производительности при использовании union()
:
$query
->select('*')
->from($db->quoteName('#__products'))
->where(
$db->quoteName('category')
. ' = '
. $db->quote('catA')
);
$q2
->select('*')
->from($db->quoteName('#__products'))
->where(
$db->quoteName('category')
. ' = '
. $db->quote('catB')
$query->union($q2);
$products = $db->setQuery($query)->loadObjectList();
Сортировка результата
Если вы хотите отсортировать результат, вы должны знать о том, каким образом база данных поступает с условием ORDER BY
. Далее речь пойдет о MySQL, но вероятно это также применимо и к другим движкам базы данных.
Предположим, вы хотите отсортировать имена и адреса электронной почты из примера с рассылкой выше в алфавитном порядке:
$q2
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__suppliers'));
$query
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__customers'))
->union($q2)
->order($db->quoteName('name'));
$mailshot = $db->setQuery($query)->loadObjectList();
Давайте представим, что дополнительно вам необходимо сделать так, чтобы сначала шли все клиенты, а потом поставщики. Этот запрос не даст ожидаемого результата:
$q2
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__suppliers'))
->order($db->quoteName('name'));
$query
->select(
$db->quoteName(
array('name', 'email')
)
)
->from($db->quoteName('#__customers'))
->order($db->quoteName('name'))
->union($q2);
$mailshot = $db->setQuery($query)->loadObjectList();
Все дело в том, что ORDER BY
в отдельном операторе SELECT
никак не влияет на сортировку в результирующем наборе. Запрос выше синтаксически верен, но MySQL оптимизатор просто проигнорирует ORDER BY
в запросе SELECT
по suppliers
, и будет применен ORDER BY
из SELECT
запроса по customers
.
Чтобы обойти эту проблему, необходимо добавить дополнительную колонку к набору результата и сортировать по ней. Вот один из вариантов:
$q2
->select(
$db->quoteName(
array('name , email, 1 as sort_col')
)
)
->from($db->quoteName('#__suppliers'));
$query
->select(
$db->quoteName(
array('name , email, 2 as sort_col')
)
)
->from($db->quoteName('#__customers'))
->union($q2)
->order(
$db->quoteName('sort_col')
. ', '
. $db->quoteName('name')
);
$mailshot = $db->setQuery($query)->loadObjectList();
Расширенная сортировка
Однако возникают случаи, когда важно иметь возможность сортировки ORDER BY
по отдельным запросам, которые не будут проигнорированы оптимизатором.
Предположим, вы хотите отправить специальное предложение для топ 10 клиентов и топ 5 поставщиков. Для этого примените условие LIMIT
в комбинации с ORDER BY
и используйте фиктивный запрос:
$q2
->select(
$db->quoteName(
array('name , email, 1 as sort_col')
)
->from($db->quoteName('#__suppliers'))
->order($db->quoteName('turnover') .' DESC')
->setLimit(5);
$q1
->select(
$db->quoteName(
array('name , email, 2 as sort_col')
)
->from($db->quoteName('#__customers'))
->order($db->quoteName('turnover') .' DESC')
->setLimit(10);
$query
->select(
$db->quoteName(
array('name , email, 0 as sort_col')
)
->from($db->quoteName('#__customers'))
->where('1 = 0')
->union($q1)
->union($q2)
->order('sort_col, name');
->order(
$db->quoteName('sort_col')
. ', '
. $db->quoteName('name')
);
$mailshot = $db->setQuery($query)->loadObjectList();
Если не использовать фиктивный запрос, то сортировка и лимит будут добавлены к результирующему набору, а не к отдельному.