9.3. Zend_Db_Select

9.3.1. Введение

Zend_Db_Select является инструментом, помогающим строить SQL-операторы SELECT, не зависящие от типа СУБД. Очевидно, он не может быть идеальным, но он проделал значительный путь к тому, чтобы помочь сделать ваши запросы переносимыми между серверными СУБД. Кроме того, он помогает сделать ваши запросы устойчивыми против SQL-инъекций.

Наиболее легкий способ создания экземпляра Zend_Db_Select — использование метода Zend_Db_Adapter::select().

<?php
		
require_once 'Zend/Db.php';

$params = array (
    'host'     => '127.0.0.1',
    'username' => 'malory',
    'password' => '******',
    'dbname'   => 'camelot'
);

$db = Zend_Db::factory('PDO_MYSQL', $params);

$select = $db->select();
// $select теперь является объектом, сконфигурированным для использования
// исключительно с адаптером PDO_MYSQL 

?>

Далее вы строите запрос SELECT, используя этот объект и его методы, затем генерируете строку, которая передается Zend_Db_Adapter для запросов (метод query()) или извлечения (группа методов fetch*()).

<?php
	
//
// SELECT round_table.*
// FROM `round_table` round_table
// WHERE `noble_title` = 'Sir'
// ORDER BY `first_name`
// LIMIT 10 OFFSET 20
//

// вы можете делать это в итеративном стиле..
$select->from('round_table');
$select->where('noble_title = ?', 'Sir');
$select->order('first_name');
$select->limit(10,20);

// ...or a "fluent" style:
// ...или в стиле fluent
$select->from('round_table')
       ->where('noble_title = ?', 'Sir')
       ->order('first_name')
       ->limit(10,20);

// извлечение результата:
$sql = $select->__toString();
$result = $db->fetchAll($sql);

// второй способ: передача объекта $select
$result = $db->fetchAll($select);

// третий способ: Получение Zend_Db_Statement
// или PDOStatement непосредственно из объекта $select:
$stmt = $select->query();
$result = $stmt->fetchAll();

?>

Еще вы можете применять связанные параметры в своих запросах вместо подстановки по порядку следования.

<?php
	
//
// SELECT round_table.*
// FROM `round_table` round_table
// WHERE noble_title = 'Sir'
// ORDER BY `first_name`
// LIMIT 10 OFFSET 20
//

$select->from('round_table', '*')
       ->where('noble_title = :title')
       ->order('first_name')
       ->limit(10,20);

// извлечение результатов с использованием связанных параметров
$params = array('title' => 'Sir');
$result = $db->fetchAll($select, $params);

?>

9.3.2. Извлечение столбцов

Для извлечения столбцов из определенной таблицы используйте метод from(), задавая таблицу и столбцы, которые хотите получить из таблицы. Вы можете использовать псевдонимы таблиц и столбцов, также можете вызывать from() любое количество раз.

<?php
	
// создание объекта $db, затем получение инструмента SELECT
$select = $db->select();

// SELECT some_table.`a`, some_table.`b`, some_table.`c`
// FROM `some_table` some_table

$select->from('some_table', array('a', 'b', 'c'));

// SELECT bar.`col`
// FROM `foo` bar

$select->from(array('foo' => 'bar'), 'col');

?>

Второй аргумент метода from() должен быть скалярным значением для имени единственного столбца или массивом для имен нескольких столбцов. Скалярное значение или элемент массива могут быть строкой, которая интерпретируется как простое имя столбца, которое окружается кавычками и к нему добавляется псевдоним таблицы. Если вы опустите второй аргумент, то используется значение по умолчанию '*', которое не окружается кавычками, но к нему добавляется псевдоним таблицы. Если вам не нужны столбцы из этой таблицы в наборе результатов, то используйте пустой массив array().

Не задавайте несколько столбцов в виде одной строки с разделенными запятой именами столбцов. Этот синтаксис использовался в более ранних версиях Zend_Db и сейчас больше не поддерживается. Вместо этого используйте массив.

Псевдоним таблицы добавляется к каждому строковому элементу строкового типа второго аргумента, но если элемент является объектом Zend_Db_Expr, то его строковое значение используется без окружения кавычками и добавления псевдонима таблицы.

<?php

$select = $db->select();

// SELECT foo.col AS col1, bar.col AS col2
// FROM foo, bar
$select->from('foo', array(
    new Zend_Db_Expr('foo.col AS col1'),
    new Zend_Db_Expr('CURDATE()')
);
$select->from('bar', new Zend_Db_Expr('bar.col AS col2'));

?>

Используйте ассоциативный массив для объявления псевдонимов таблиц. Ключ является именем таблицы а значение - псевдонимом. Если вы определяете таблицу в виде простой строки вместо ассоциативного массива, то Zend_Db_Select генерирует псевдоним таблицы. По умолчанию псевдоним таблицы совпадает с именем таблицы, так же, как если бы вы использовали SQL-запрос вида "SELECT foo.* FROM `foo` foo". Если вы добавляете одну и ту же таблицу больше одного раза (например, для объединения таблицы с самой собой), то Zend_Db_Select использует уникальные псевдонимы для каждого экземпляра таблицы вида "foo_1", "foo_2" и т.д.

9.3.3. Объединение таблиц

Для извлечения столбцов с использованием объединения таблиц используйте метод join(). Сначала указывается имя присоединяемой таблицы, затем условие соединения, и в заключение столбцы, которые вы хотите получить из объединенных таблиц. Вы можете вызывать join() любое количество раз.

<?php
	
// создание объекта $db, затем получение инструмента SELECT
$select = $db->select();

// SELECT foo.*, bar.*
// FROM `foo` foo
// JOIN `bar` bar ON foo.id = bar.id

$select->from('foo');
$select->join('bar', 'foo.id = bar.id');

?>

Первый аргументом метода join() является спецификатор таблицы. Он аналогичен первыому аргументу метода from(). Это может быть как строка (имя таблицы), так и ассоциативный массив соотношений 'имя таблицы' => 'псевдоним'.

Второй аргумент метода join() является выражением, по которому формируется условие объединения. Вы можете использовать псевдонимы таблиц в них, но помните, что если вы не задали псевдоним таблицы, то Zend_Db_Select генерирует его. Если вы опустили условие объединения, то это будет эквивалентом использования CROSS JOIN или декартового произведения.

Третий аргумент метода join() является списком столбцов из объединяемой таблицы для включения в список выборки. Он аналогичен второму аргументу метода from() в том, что это может быть скалярное значение для имени столбца или массив скаляров для нескольких столбцов. Каждая скалярная величина может быть либо строкой, либо объектом Zend_Db_Expr. Если вы опустили этот аргумент, то используется значение по умолчанию '*'. Если вам не нужны столбцы из этой таблицы в наборе результатов выборки, то используйте пустой массив array().

Не задавайте несколько столбцов в виде одной строки с разделенными запятой именами столбцов. Этот синтаксис использовался в более ранних версиях Zend_Db и сейчас больше не поддерживается. Вместо этого используйте массив.

Поддерживаются следующие типы объединения:

  • INNER JOIN с методами join() или joinInner(). Все реляционные СУБД поддерживают этот тип объединения.

  • LEFT JOIN с методом joinLeft() Все реляционные СУБД поддерживают этот тип объединения.

  • RIGHT JOIN с методом joinRight(). Некоторые реляционные СУБД не поддерживают этот тип объединения.

  • FULL JOIN с методом joinFull(). Некоторые реляционные СУБД не поддерживают этот тип объединения.

  • CROSS JOIN с методом joinCross(). Этот метод не имеет параметры для определения условия объединения. Некоторые реляционные СУБД не поддерживают этот тип объединения.

  • NATURAL JOIN с методом joinNatural(). Этот метод не имеет параметры для определения условия объединения. Естетственное объединение подразумевает объединение по эквивалентности значений в столбцах с одинаковыми именами в двух таблицах. Поддерживается только NATURAL INNER JOIN.

9.3.4. Условия WHERE

Для добавления условий WHERE, используйте метод where(). Вы можете передать либо обычную строку, либо строку с метками заполнения и значения для подстановки с добавлением кавычек (подстановка выполняется с помощью Zend_Db_Adapter::quoteInto).

Повторные вызовы where() будет добавлять условия через AND. Если нужен OR, то используйте метод orWhere().

<?php
	
// создание объекта $db, затем получение  SELECT
$select = $db->select();

// SELECT r.*
// FROM `round_table` r
// WHERE noble_title = 'Sir'
//   AND favorite_color = 'yellow'

$select->from(array('round_table' => 'r'));
$select->where("noble_title = 'Sir'"); // встроенное значение
$select->where('favorite_color = ?', 'yellow'); // подставляемое значение

// SELECT foo.*
//     FROM `foo` foo
//     WHERE bar = 'baz'
//     OR id IN('1', '2', '3')

$select->from('foo');
$select->where('bar = ?', 'baz');
$select->orWhere('id IN(?)', array(1, 2, 3));

?>

Zend_Db_Select не добавляет кавычки и псевдонимы таблиц к именам столбцов в условиях WHERE. Если вы подставляете переменные PHP в строку для выражения WHERE, то вам нужно использовать метод quoteIdentifier() класса Zend_Db_Adapter для предотвращения ошибок синтаксиса.

<?php
...
$columnName = 'bar'; // или устанавливется из внешнего источника
$whereExpr = $db->quoteIdentifier($columnName) . ' = ?';
$select->where($whereExpr, 'baz');
?>

9.3.5. Группировка

Для группировки строк используйте метод group() столько раз, сколько нужно.

<?php
	
// создание объекта $db, затем получение инструмента SELECT
$select = $db->select();

// SELECT COUNT(id)
// FROM `foo` foo
// GROUP BY `bar`, `baz`

$select->from('foo', new Zend_Db_Expr('COUNT(id)'));
$select->group('bar');
$select->group('baz');

// эквивалентный вызов group():
$select->group(array('bar', 'baz'));

?>

Не задавайте несколько столбцов в виде одной строки с разделенными запятой именами столбцов. Этот синтаксис использовался в более ранних версиях Zend_Db и сейчас больше не поддерживается. Вместо этого используйте массив.

Добавление кавычек применяется к каждому элементу строкового типа аргумента group(), но если элемент является объектом Zend_Db_Expr, то его строковое значение используется без добавления кавычек.

9.3.6. Условия HAVING

Для добавления условий HAVING используйте метод having(). Этот метод в действии идентичен методу where().

Если вы вызываете having() несколько раз, условия будут добавляться через AND. Если нужен OR, то используйте метод orHaving().

<?php
	
// создание объекта $db, затем получение инструмента SELECT
$select = $db->select();

// SELECT COUNT(id) AS count_id
//     FROM `foo` foo
//     GROUP BY `bar`, `baz`
//     HAVING count_id > '1'

$select->from('foo', new Zend_Db_Expr('COUNT(id) AS count_id'));
$select->group(array('bar', 'baz'));
$select->having('count_id > ?', 1);

?>

Zend_Db_Select не добавляет кавычки и псевдонимы таблиц к именам столбцов в условиях HAVING. Если вы подставляете переменные PHP в строку для выражения HAVING, то вам нужно использовать метод quoteIdentifier() класса Zend_Db_Adapter для предотвращения ошибок синтаксиса.

<?php
...
$columnName = 'count_id'; // или устанавливается из внешнего ресурса
$havingExpr = $db->quoteIdentifier($columnName) . ' > ?';
$select->having($havingExpr, 1);
?>

9.3.7. Сортировка

Для сортировки столбцов используйте метод order() столько раз, сколько необходимо.

<?php
	
// создание объекта $db, затем получение инструмента SELECT
$select = $db->select();

// SELECT r.*
// FROM `round_table` r
// ORDER BY `noble_title` DESC, `first_name` ASC

$select->from('round_table');
$select->order('noble_title DESC');
$select->order('first_name');

// эквивалентный вызов order():
$select->order(array('noble_title DESC', 'first_name'));

?>

Не задавайте несколько столбцов в виде одной строки с разделенными запятой именами столбцов. Этот синтаксис использовался в более ранних версиях Zend_Db и сейчас больше не поддерживается. Вместо этого используйте массив.

Добавление кавычек применяется к каждому элементу строкового типа аргумента group(), но если элемент является объектом Zend_Db_Expr, то его строковое значение используется без добавления кавычек.

9.3.8. Ограничение по количеству строк и смещению

Zend_Db_Select предлагает независимую от типа СУБД поддержку LIMIT. Для многих баз данных — таких, как MySQL и PostgreSQL — это относительно легко, так как они поддерживают синтаксис "LIMIT :количество_строк [OFFSET :смещение]".

Для некоторых других баз данных это не так просто, поскольку они не поддерживают оператор LIMIT. Microsoft SQL Server имеет оператор TOP, который можно использовать вместо LIMIT. Oracle и DB2 требуют, чтобы запрос был сформирован специальным образом для эмуляции LIMIT. Zend_Db_Select может переписывать SELECT в соответствии с каждым адамтером базы данных для обеспечения функционала LIMIT.

Для ограничения возвращаемых результатов по количеству строк и смещению используйте метод limit() c количеством строк и смещением (необязательный параметр).

<?php
	
// простой "LIMIT :количество_строк"
$select = $db->select();
$select->from('foo');
$select->order('id');
$select->limit(10);

// Для MySQL/PostgreSQL/SQLite, это будет преобразовано в:
//
// SELECT foo.*
// FROM "foo" foo
// ORDER BY "id" ASC
// LIMIT 10
//
// Для Microsoft SQL Server, это будет преобразовано в:
//
// SELECT TOP 10 foo.*
// FROM [foo] foo
// ORDER BY [id] ASC

// Теперь более сложный "LIMIT :количество_строк OFFSET :смещение"
$select = $db->select();
$select->from('foo', '*');
$select->order('id');
$select->limit(10, 20);

// Для MySQL/PostgreSQL/SQLite, это будет преобразовано в:
//
// SELECT foo.*
// FROM "foo" foo
// ORDER BY "id" ASC
// LIMIT 10 OFFSET 20
//
// Microsoft SQL Server не поддерживает смещение, поэтому это будет
// преобразовано в:
//
// SELECT * FROM (
//     SELECT TOP 10 * FROM (
//         SELECT TOP 30 *
//         FROM [foo] foo
//         ORDER BY [id] DESC
//     ) ORDER BY id ASC
// )
//
// Zend_Db_Adapter Выполняет эти преобразования автоматически

?>

9.3.9. Ограничение по странице и количеству строк

Zend_Db_Select также предлагает ограничение, основанное на страницах. Если вы хотите получить определенную "страницу" результатов, то используйте метод limitPage(); сначала передается номер требуемой страницы, затем количество строк, которое показывается на каждой странице.

<?php
	
// построение основы запроса SELECT...
$select = $db->select();
$select->from('foo', '*');
$select->order('id');

// ... и ограничение до страницы 3, где каждая страница имеет 10 строк
$select->limitPage(3, 10);

// в MySQL/PostgreSQL/SQLite это будет преобразовано в:
//
// SELECT foo.*
// FROM `foo` foo
// ORDER BY `id` ASC
// LIMIT 10 OFFSET 20

?>

9.3.10. Другие методы

Метод distinct() дает возможность добавлять ключевое слово DISTINCT в SQL-запрос.

<?php

// SELECT DISTINCT foo.`non_unique_column`
// FROM `foo` foo

$select = $db->select();
$select->distinct();
$select->from('foo', 'non_unique_column');

?>

Метод forUpdate() дает возможность добавлять ключевые слова FOR UPDATE в SQL-запрос.

<?php

// SELECT FOR UPDATE foo.*
// FROM `foo` foo

$select = $db->select();
$select->forUpdate();
$select->from('foo');

?>

Метод query() подобен методу query() в классе Zend_Db_Adapter. Он возвращает объект класса Zend_Db_Statement или PDOStatement, в зависимости от типа адаптера.

<?php

$select = $db->select();
$select->from('foo');
$stmt = $select->query();
$result = $stmt->fetchAll();

// Это эквивалентно следующему:
$select = $db->select();
$select->from('foo');
$stmt = $db->query($select);
$result = $stmt->fetchAll();

?>

Метод getPart() возвращает даннные, переданные для частей SQL-запроса. Класс Zend_Db_Select включает в себя определения констант, которые можно использовать для частей SQL-запроса.

<?php

// SELECT foo.*
// FROM `foo` foo
// ORDER `keyColumn`

$select = $db->select();
$select->from('foo');
$select->order('keyColumn');

print_r( $select->getPart( Zend_Db_Select::ORDER ) );

?>

Метод reset() дает возможность очистить определенную часть SQL-запроса или все его части, если аргумент не указан.

<?php

// SELECT foo.*
// FROM `foo` foo
// ORDER BY `column1`

$select = $db->select();
$select->from('foo');
$select->order('column1');

// Теперь мы хотим установить другой критерий сортировки
//
// SELECT foo.*
// FROM `foo` foo
// ORDER BY `column2`

// Очищение одной части запроса, теперь мы можем переопределеить ее
$select->reset( Zend_Db_Select::ORDER );
$select->order('column2');

// Очищение всех частей запроса
$select->reset();

?>