9.3. Zend_Db_Select

9.3.1. Wprowadzenie

Zend_Db_Select jest narzędziem pomagającym w budowaniu zapytań SQL SELECT w sposób niezależny od rodzaju bazy danych. Oczywiście nie może to być perfekcyjne, ale pomaga w tym, aby zapytania były przenośne pomiędzy różnymi systemami bazodanowymi. Dodatkowo pomaga to w uodpornieniu zapytań na ataki SQL injection.

Najprostszy sposób utworzenia instancji Zend_Db_Select to użycie metody 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 jest teraz obiektem Zend_Db_Select skonfigurowanym do użycia z adapterem PDO_MYSQL.

?>

Wtedy konstruujesz zapytanie SELECT używając tego obiektu i jego metod, a następnie generujesz łańcuch znaków który przekazujesz spowrotem do obiektu Zend_Db_Adapter w celu wykonania zapytania.

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

// możesz użyć stylu iteracyjnego...
$select->from('round_table');
$select->where('noble_title = ?', 'Sir');
$select->order('first_name');
$select->limit(10,20);

// ...lub stylu łańcuchowego:
$select->from('round_table')
       ->where('noble_title = ?', 'Sir')
       ->order('first_name')
       ->limit(10,20);

// pobieramy dane:
$sql = $select->__toString();
$result = $db->fetchAll($sql);

// druga alternatywa: możesz przekazać sam obiekt $select;
$result = $db->fetchAll($select);

// trzecia alternatywa: tworzysz obiekt Zend_Db_Statement
// lub PDOStatement bezpośrednio z obiektu $select:
$stmt = $select->query();
$result = $stmt->fetchAll();

?>

Możesz także użyć parametrów wstawianych w miejsce nazwanych znaczników, zamiast cytowania parametrów po kolei.

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

// pobieramy wyniki używająć parametrów wstawianych w miejsce znaczników
$params = array('title' => 'Sir');
$result = $db->fetchAll($select, $params);

?>

9.3.2. Kolumny z tabel (FROM)

Aby wybrać kolumny z określonej tabeli, użyj metody from(), określając tabelę oraz kolumny których potrzebujesz. Możesz użyć aliasów dla tabel oraz kolumn, i możesz używać metody from() tyle razy ile potrzebujesz.

<?php
	
// tworzymy obiekt $db, zakładając, że adapter to Mysql
$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');

?>

Drugi argument do metody from() jest wartością skalarną dla pojedynczej nazwy kolumny, lub tablicą dla wielu nazw kolumn. Wartość skalarna lub element tablicy może być łańcuchem znaków, który jest interpretowany jako prosta nazwa kolmny i jest ona cytowana, a na początek nazwy dołączany jest alias tabeli. Jeśli pominiesz drugi argument, przyjęta zostanie jego domyślna wartość '*', co nie jest cytowane, ale do tej wartości też zostanie dołączony alias tabeli. Jeśli nie chcesz by kolumny z tej tabeli znajdowały się w zestawie wyników, użyj pustej tablicy array().

Nie określaj wielu kolumn jako pojedynczego łańcucha znaków z nazwami kolumn oddzielonymi przecinkami. Taka składnia była używana we wcześniejszych wydaniach Zend_Db i nie jest ona już obsługiwana. Użyj zamiast tego tablicy.

Alias tabeli jest dołączany do każdego elementu będącego łańcuchem znaków znajdującym się w drugim argumencie, ale jeśli element jest obiektem typu Zend_Db_Expr, to jego wartość w postaci łańcucha znaków jest używana bez cytowania oraz dodawania alisu tabeli.

<?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'));

?>

Użyj tablicy asocjacyjnej aby zadeklarować aliasy dla tabel. Klucz tablicy jest nazwą tabeli, a wartośc jest aliasem. Jeśli zamiast tego określisz nazwę tabeli jako zwykły łańcuch znaków, a nie jako asocjacyjną tablicę, to Zend_Db_Select wygeneruje alias dla tabeli. Domyślnie generowany jest alias o tej samej nazwie jak tabela, czyli tak jak na przykład w zapytaniu SQL "SELECT foo.* FROM `foo` foo". Jeśli dodasz tę samą tabelę więcej niż raz, na przykład w złączeniu SELF JOIN, Zend_Db_Select zadeklaruje unikalny alias dla każdej instancji tabeli, jak np. "foo_1", "foo_2", itd.

9.3.3. Kolumny ze złączonych tabel (JOIN)

Aby wybrać kolumny używając złączonych tabel, użyj metody join(). Wpierw określ nazwę złączanej tabeli, następnie wyrażenie łączące, a na końcu kolumny które potrzebujesz dołączyć. Możesz użyć metody join() tyle razy ile potrzebujesz.

<?php
	
// tworzymy obiekt $db, zakładając, że adapter to Mysql
$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');

?>

Pierwszy argument dla metody join() określa tabelę. Działa to analogicznie jak pierwszy argument metody from(), w którym nazwa tabeli może być łańcuchem znaków lub asocjacyjną tablicą mapującą nazwę tabeli do aliasu.

Drugi argument dla metody join() jest wyrażeniem tworzącym warunek złączenia. Możesz tu użyć aliasów tabel, ale pamiętaj, że jeśli nie określiłeś aliasów tabel, to Zend_Db_Select sam je wygeneruje. Jeśli pominiesz warunek złączenia, będzie to odpowiadało użyciu złączenia krzyżowego CROSS JOIN lub iloczynu kartezjańskiego.

Trzeci argument dla metody join()jest listą kolumn ze złączonej tabeli, które mają być dołączone do listy wyników. Działa to analogicznie jak drugi argument dla metody from(), w którym może być wartość skalarna z nazwą tabeli, lub tablica wartości skalarnych w celu wybrania wielu kolumn. Każda wartość skalarna może być łańcuchem znakow lub obiektem typu Zend_Db_Expr. Jeśli pominiesz ten argument, jako wartość domyślna zostanie przyjęty znak '*'. Jeśli nie chcesz by kolumny z dołączonej tabeli znajdowały się w zestawie wyników, użyj pustej tablicy array().

Nie określaj wielu kolumn jako pojedynczego łańcucha znaków z nazwami kolumn oddzielonymi przecinkami. Taka składnia była używana we wcześniejszych wydaniach Zend_Db i nie jest ona już obsługiwana. Użyj zamiast tego tablicy.

Obsługiwane są następujące typy złączeń:

  • INNER JOIN za pomocą metody join() lub joinInner(). Wszystkie rodzaje RDBMS obsługują ten typ złączeń.

  • LEFT JOIN za pomocą metody joinLeft(). Wszystkie rodzaje RDBMS obsługują ten typ złączeń.

  • RIGHT JOIN za pomocą metody joinRight(). Niektóre rodzaje RDBMS nie obsługują tego typu złączeń.

  • FULL JOIN za pomocą metody joinFull(). Niektóre rodzaje RDBMS nie obsługują tego typu złączeń.

  • CROSS JOIN za pomocą metody joinCross(). Nie ma żadnego parametru dla tej metody do określania warunku złączenia. Niektóre rodzaje RDBMS nie obsługują tego typu złączeń.

  • NATURAL JOIN za pomocą metody joinNatural(). Nie ma żadnego parametru dla tej metody do określania warunku złączenia; przy złączeniu naturalnym zakładane jest, że jest tworzone równościowe złączenie kolumn o tej samej nazwie w obu tabelach. Obsługiwane jest jedynie złączenie NATURAL INNER JOIN.

9.3.4. Warunki WHERE

Aby dodać warunki WHERE, użyj metody where(). Możesz przekazać zwykły łańcuch znaków lub możesz przekazać łańcuch znaków ze znacznikiem w postaci znaku zapytania oraz wartość która ma być zacytowana. (wartość będzie zacytowana za pomocą metody Zend_Db_Adapter::quoteInto).

Kolejne wywołania where() będą łączyć warunki za pomocą AND; jeśli chcesz je złączyć za pomocą OR, użyj metody orWhere().

<?php
	
// tworzymy obiekt $db, a następie odbieramy narzędzie 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'"); // osadzona wartość
$select->where('favorite_color = ?', 'yellow'); // cytowana wartość

// 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 nie stosuje cytowania oraz aliasów tabeli dla nazwanych kolumn w wyrażeniach WHERE. Jeśli zmiennych PHP używasz do tworzenia łańcucha znaków dla wyyrażenia WHERE, powinienes użyć metody Zend_Db_Adapter quoteIdentifier() aby ochronić twoje zapytania przed niedozwoloną składnią.

<?php
...
$columnName = 'bar'; // lub ustawione przez niezaufane źródło
$whereExpr = $db->quoteIdentifier($columnName) . ' = ?';
$select->where($whereExpr, 'baz');
?>

9.3.5. Warunek GROUP BY

Aby grupować wiersze użyj metody group() tyle razy ile potrzebujesz.

<?php
	
// tworzymy obiekt $db, a następie odbieramy narzędzie 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');

// wywołanie metody group():
$select->group(array('bar', 'baz'));

?>

Nie określaj wielu kolumn jako pojedynczego łańcucha znaków z nazwami kolumn oddzielonymi przecinkami. Taka składnia była używana we wcześniejszych wydaniach Zend_Db i nie jest ona już obsługiwana. Użyj zamiast tego tablicy.

Cytowanie jest stosowane do każdego elementu będącego łańcuchem znakow, znajdującym się argumencie dla group(), ale jeśli element ten jest obiektem typu Zend_Db_Expr, jego wartość jest używana bez cytowania.

9.3.6. Warunki HAVING

Aby dodać warunki HAVING dla wybranych wyników użyj metody having(). Ta metoda jest identyczna w użyciu jak metoda where().

Kolejne wywołania having() będą łączyć warunki za pomocą AND; jeśli chcesz je złączyć za pomocą OR, użyj metody orHaving().

<?php
	
// tworzymy obiekt $db, a następie odbieramy narzędzie 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 does not apply quoting or table aliases to columns named in HAVING clauses. If you combine PHP variables into the string for a HAVING expression, you should use the Zend_Db_Adapter quoteIdentifier() method to protect against illegal syntax.

<?php
...
$columnName = 'count_id'; // lub ustawione przez niezaufane źródło
$havingExpr = $db->quoteIdentifier($columnName) . ' > ?';
$select->having($havingExpr, 1);
?>

9.3.7. Warunek ORDER BY

Aby sortować dane użyj metody order() tyle razy ile potrzebujesz.

<?php
	
// tworzymy obiekt $db, a następie odbieramy narzędzie 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');

// wywołanie metody order():
$select->order(array('noble_title DESC', 'first_name'));

?>

Nie określaj wielu kolumn jako pojedynczego łańcucha znaków z nazwami kolumn oddzielonymi przecinkami. Taka składnia była używana we wcześniejszych wydaniach Zend_Db i nie jest ona już obsługiwana. Użyj zamiast tego tablicy.

Cytowanie jest stosowane do każdego elementu będącego łańcuchem znakow, znajdującym się argumencie dla order(), ale jeśli element ten jest obiektem typu Zend_Db_Expr, jego wartość jest używana bez cytowania.

9.3.8. LIMIT w oparciu o ilość wyników i offset

Zend_Db_Select oferuje obsługę bazodanowej składni LIMIT. Dla wielu baz danych, np. MySQL czy PostgreSQL, jest to relatywnie proste, ponieważ obsługują one składnię "LIMIT :count [OFFSET :offset]".

Dla niektórych innych baz danych, nie jets to takie proste, ponieważ nie obsługują one składni LIMIT. Microsoft SQL Server posiada składnię TOP, która daje taki sam rezultat. Oracle oraz DB2 wymagają zapytań napisanych w specjalny sposób aby emulować składnię LIMIT. Zend_Db_Select może przepisać zapytanie SELECT odpowiednio dla każdego ze sterowników bazy danych aby umożliwiać obsługę funkcjonalności LIMIT.

Aby limitować zwracane wyniki na podstawie ilości i offsetu użyj metody limit() podając ilość oraz opcjonalny offset.

<?php
	
// na początek prosty "LIMIT :count"
$select = $db->select();
$select->from('foo');
$select->order('id');
$select->limit(10);

// W MySQL/PostgreSQL/SQLite odpowiada to zapytaniu:
//
// SELECT foo.*
// FROM "foo" foo
// ORDER BY "id" ASC
// LIMIT 10
//
// A w Microsoft SQL Server odpowiada to zapytaniu:
//
// SELECT TOP 10 foo.*
// FROM [foo] foo
// ORDER BY [id] ASC

// a teraz bardziej złożony "LIMIT :count OFFSET :offset"
$select = $db->select();
$select->from('foo', '*');
$select->order('id');
$select->limit(10, 20);

// W MySQL/PostgreSQL/SQLite odpowiada to zapytaniu:
//
// SELECT foo.*
// FROM "foo" foo
// ORDER BY "id" ASC
// LIMIT 10 OFFSET 20
//
// Microsoft SQL Server nie obsługuje offset, więc odpowiada to zapytaniu:
//
// SELECT * FROM (
//     SELECT TOP 10 * FROM (
//         SELECT TOP 30 *
//         FROM [foo] foo
//         ORDER BY [id] DESC
//     ) ORDER BY id ASC
// )
//
// Zend_Db_Adapter automatycznie tłumaczy zapytanie.

?>

9.3.9. LIMIT w oparciu o ilość wyników i numer strony

Zend_Db_Select oferuje limitowanie wyników oparte na stronach. Jeśli chcesz pobrać pewną stronę wyników, użyj metody limitPage(); wpierw przekaż numer strony którą potrzebujesz, a nąstępnie ilość wierszy jaka ma się pojawiać na każdej ze stron.

<?php
	
// budujemy podstawowe zapytanie select...
$select = $db->select();
$select->from('foo');
$select->order('id');

// ... i limitujemy do strony 3 gdzie każda strona ma 10 wierszy
$select->limitPage(3, 10);

// W MySQL/PostgreSQL/SQLite, odpowiada to zapytaniu:
//
// SELECT foo.*
// FROM `foo` foo
// ORDER BY `id` ASC
// LIMIT 10 OFFSET 20

?>

9.3.10. Inne metody

Metoda distinct() pozwala dodać słowo kluczowe DISTINCT do twojego zapytania SQL.

<?php

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

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

?>

Metoda forUpdate() pozwala dodać ci słowa kluczowe FOR UPDATE do twojego zapytania SQL.

<?php

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

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

?>

Metoda query() działa podobnie jak metoda query() obiektu klasy Zend_Db_Adapter. Zwraca ona obiekt typu Zend_Db_Statement lub PDOStatement, zależnie od typu używanego sterownika.

<?php

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

// Jest to równoznaczne z poniższym:
$select = $db->select();
$select->from('foo');
$stmt = $db->query($select);
$result = $stmt->fetchAll();

?>

Metoda getPart() zwraca dane, ktore przypisałeś do części zapytania SQL. Klasa Zend_Db_Select zawiera definicje stałych, których możesz użyć dla części zapytania 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 ) );

?>

Metoda reset() pozwala ci na wyczyszczenie jednej określonej części zapytania SQL, lub na wyczyszczenie wszystkich części zapytania SQL, jeśli pominiesz argument podczas wywołania.

<?php

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

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

// Chcemy zmienić kryteria sortowania
//
// SELECT foo.*
// FROM `foo` foo
// ORDER BY `column2`

// Czyścimy jedną część aby zdefiniować ją ponownie
$select->reset( Zend_Db_Select::ORDER );
$select->order('column2');

// Czyścimy wszystkie części zapytania
$select->reset();

?>