# Zend_Db_Select

## Wprowadzenie

Obiekt `Zend_Db_Select` reprezentuje pojedyncze polecenie SQL SELECT. Klasa posiada metody służące do umieszczenia poszczególnych części zapytania. Za jej pomocą można zapisać elementy zapytania poprzez metody i struktur danych PHP a klasa sama tworzy poprawne polecenie SQL. Po zbudowaniu zapytania można go użyć tak jakby było napisane w postaci zwykłego łańcucha znaków.

Wartość `Zend_Db_Select` zawiera się w poniższych cechach:

- Metody obiektowe służące tworzeniu zapytań SQL krok po kroku;

- Poziom abstrakcji umożliwiający używanie określonych części zapytania SQL są w sposób niezależny od rodzaju bazy danych;

- Automatyczne umieszczanie identyfikatorów metadanych w cudzysłowach ułatwia używanie identyfikatorów zawierających zarezerwowane słowa i specjalne znaki SQL;

- Umieszczanie identyfikatorów i wartości w cudzysłowach pomaga ograniczyć ryzyko ataków wstrzykiwania kodu SQL (SQL injection);

Używanie `Zend_Db_Select` nie jest obowiązkowe. Dla najprostrzych zapytań SELECT z reguły łatwiej jest zapisać całe polecenie SQL w postaci łańcucha znaków i wywołać je za pomocą metod Adaptera takich jak `query()` lub `fetchAll()`. Użycie `Zend_Db_Select` jest przydatne jeśli zajdzie potrzeba połączenia części złożonego zapytania w kodzie np. w zależności od wystąpienia dodatkowych warunków logicznych.

## Tworzenie objektu Select

Instancję klasy `Zend_Db_Select` można utworzyć poprzez metodę `select()` obiektu `Zend_Db_Adapter_Abstract`.

### Przykład metody select() adaptera bazy danych

    $db = Zend_Db::factory( ...options... );
    $select = $db->select();

Innym sposobem utworzenia objektu `Zend_Db_Select` jest użycie konstruktora, podając adapter bazy danych w argumencie.

### Przykład tworzenia nowego obiektu Select

    $db = Zend_Db::factory( ...options... );
    $select = new Zend_Db_Select($db);

## Tworzenie poleceń Select

Budując zapytanie można dodawać jego części jedna po drugiej. Obiekt `Zend_Db_Select` posiada odrębne metody dla każdej klauzuli SQL.

### Przykład użycia metod służących dodawaniu części zapytania

    // Utworzenie objektu Zend_Db_Select
    $select = $db->select();

    // Dodanie klauzuli FROM
    $select->from( ...podanie tabel i kolumn... )

    // Dodanie klauzuli WHERE
    $select->where( ...podanie kryteriów ograniczenia... )

    // Dodanie klauzuli ORDER BY
    $select->order( ...podanie kryteriów sortowania... );

Większości metod obiektu `Zend_Db_Select` można używać za pomocą przyjaznego płynego interfejsu (fluent interface). Interfejs płynny oznacza, że każda z metod zwraca referencję do obiektu wywołującego więc można od razu użyć następnej metody.

### Przykład użycia płynego interfejsu

    $select = $db->select()
        ->from( ...podanie tabel i kolumn... )
        ->where( ...podanie kryteriów ograniczenia... )
        ->order( ...podanie kryteriów sortowania... );

Przykłady w tym rozdziale używają płynnego interfejsu ale zawsze można z niego zrezygnować. Często może się to okazać niezbędne w przypadku gdy należy wykonać operacje zgodne z logiką biznesową aplikacji przed umieszczeniem dodatkowej klauzuli w zapytaniu.

### Dodawanie klauzuli FROM

Można wybrać tabelę dla zapytania używając metody `from()`. Aby tego dokonać należy podać nazwę tabeli jako łańcuch znaków. `Zend_Db_Select` umieszcza cudzysłowy wokół podanej nazwy, więc można używać znaków specjalnych.

#### Przykład używacia metody from()

    // Utworzenie zapytania:
    //   SELECT *
    //   FROM "products"

    $select = $db->select()
                 ->from( 'products' );

Można podać również nazwę korelacyjną (nazywaną również "aliasem tabeli") danej tabeli. Aby to zrobić w argumencie należy podać tablicę asocjacyjną, która będzie zawierała mapowanie nazwy aliasu na nazwę tabeli. W pozostałych częściach zapytania SQL będzie można używać tej nazwy zamiast tabeli. Jeśli dane zapytanie łączy wiele tabel `Zend_Db_Select` utworzy unikalne aliasy na podstawie prawdziwych nazw dla każdej tabeli dla której nie zrobi tego użytkownik.

#### Przykład użycia aliasu

    // Utworzenie zapytania:
    //   SELECT p.*
    //   FROM "products" AS p

    $select = $db->select()
                 ->from( array('p' => 'products') );

Niektóre silniki bazy danych (RDBMS) wspierają podawanie nazw schematu przed nazwą tabeli. W takim przypadku należy podać nazwę tabeli jako "`nazwaSchematu.nazwaTabeli`" a `Zend_Db_Select` umieści w cudzysłowach każdą z części takiej nazwy indywidualnie. Można też podać nazwę schematu oddzielnie. Nazwa schematu podana przy nazwie tabeli ma pierwszeństwo wobec nazwy schematu podanej osobno (jeśli obie występują).

#### Przykład podawania nazwy schematu

    // Utworzenie zapytania:
    //   SELECT *
    //   FROM "myschema"."products"

    $select = $db->select()
                 ->from( 'myschema.products' );

    // lub

    $select = $db->select()
                 ->from('products', '*', 'myschema');

### Dodawanie kolumn

Drugi argument metody `from()` może zawierać kolumny, które mają zostać pobrane z odpowiedniej tabeli. Jeśli nie poda się tego argumentu domyślną wartością jest "`*`" czyli znak specjalny SQL odpowiadający wszystkim kolumnom.

Kolumny można podawać w prostej tablicy łańcuchów tekstowych lub jako asocjacyjnej tablicy aliasów kolumn do nazw kolumn. Jeśli potrzebna jest tylko jedna kolumna to można ją podać w prostym stringu - nie trzeba używać tablicy.

Jeśli w tym argumencie zostanie podana pusta tablica to żadna kolumna z odpowiedniej tabeli nie zostanie dołączona do wyniku zapytania. Zobacz przykład kodu znajdujący się pod rozdziałem dotyczącym metody `join()`.

Nazwę kolumny można podać w formie "`nazwaAliasu.nazwaKolumny`". `Zend_Db_Select` umieści każdą z części nazwy oddzielnie w cudzysłowach, używając aliasu wcześniej ustalonego w metodzie `from()` (jeśli nie został podany bezpośrednio).

#### Przykład dodawania kolumn

    // Tworzenie zapytania:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id', 'product_name'));

    // Tworzenie podobnego zapytania z użyciem aliasów tabeli:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('p.product_id', 'p.product_name'));

    // Tworzenie podobnego zapytania z aliasem dla jednej kolumny:
    //   SELECT p."product_id" AS prodno, p."product_name"
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('prodno' => 'product_id', 'product_name'));

### Dodawanie kolumn z wyrażeniami

W zapytaniach SQL często zachodzi potrzeba użycia wyrażeń zamiast zwykłych kolumn tabeli. Wyrażenia nie powinny być otoczone cudzysłowami ani zawierać aliasów tabel. Jeśli podana kolumna zawiera nawiasy `Zend_Db_Select` rozpoznaje ją jako wyrażenie.

Można również samemu utworzyć obiekt klasy `Zend_Db_Expr` aby łańcuch znaków nie został potraktowany jak zwykła nazwa kolumny. `Zend_Db_Expr` jest małą klasą zawierającą jeden string. `Zend_Db_Select` rozpoznaje instancje klasy `Zend_Db_Expr` i zamienia je na łańcuchy znaków ale nie wprowadza zmian takich jak cudzysłowy czy aliasy tabel.

Używanie `Zend_Db_Expr` dla wyrażeń nie jest obowiązkowe jeśli zawiera ono nawiasy. `Zend_Db_Select` rozpoznaje nawiasy i traktuje dany łańcuch jak wyrażenie (nie umieszcza w cudzysłowach ani nie dodanie nazw alias tabel).

#### Przykłady podawania kolumn zawierających wyrażenia

    // Tworzenie zapytania:
    //   SELECT p."product_id", LOWER(product_name)
    //   FROM "products" AS p
    // Wyrażenie z nawiasami staje się obiektem klasy Zend_Db_Expr.

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id', 'LOWER(product_name)'));

    // Tworzenie zapytania:
    //   SELECT p."product_id", (p.cost * 1.08) AS cost_plus_tax
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id',
                              'cost_plus_tax' => '(p.cost * 1.08)')
                       );

    // Tworzenie zapytania używając Zend_Db_Expr:
    //   SELECT p."product_id", p.cost * 1.08 AS cost_plus_tax
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id',
                              'cost_plus_tax' =>
                                  new Zend_Db_Expr('p.cost * 1.08'))
                        );

W powyższych przykładach `Zend_Db_Select` nie zmienia stringa i nie dodaje aliasów tabel ani nie używa cudzysłowów. Jeśli takie zmiany są niezbędne (np. z powodu dwuznaczności nazw) należy je wprowadzić ręcznie.

Jeśli podane nazwy kolumn zawierają słowa kluczowe SQL lub znaki specjalne należy użyć metody adaptera połączenia o nazwie `quoteIdentifier()` i rezultat umieścić w stringu. Metoda `quoteIdentifier()` używa cudzysłowów dzięki czemu można być pewnym, że podany łańcuch znaków jest identyfikatorem tabeli lub kolumny a nie częścią składni polecenia SQL.

Dzięki użyciu metody `quoteIdentifier()` zamiast ręcznego wpisywania cudzysłowów kod staje się niezależny od rodzaju bazy danych. Niektóre systemy zarządzania bazą danych (RDBMS) używają niestandardowych znaków do ograniczania identyfikatorów. Metoda `quoteIdentifier()` jest przystosowana do używania odpowiednich symboli ograniczających w zależności od typu używanego adaptera. Metoda `quoteIdentifier()` dokonuje również unikania znaków cudzysłowia, które pojawią się w argumencie wejściowym.

#### Przykłady umieszczania wyrażeń w cudzysłowach

    // Tworzenie zapytania,
    // umieszczając kolumnę o nazwie "from" w cudzysłowach:
    //   SELECT p."from" + 10 AS origin
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('origin' =>
                                  '(p.' . $db->quoteIdentifier('from') . ' + 10)')
                       );

### Dodawanie kolumn do wcześniej utworzonej klauzuli FROM lub JOIN

Może powstać sytuacja w której niezbędne okazuje się dodanie kolumn do klauzuli FROM lub JOIN, która została utworzona wcześniej (za pomocą odpowiedniej metody). Metoda `columns()` pozwala na dodanie kolumn w dowolnym momencie przed wykonaniem zapytania. Kolumny można podać jako łańcuchy znaków, obiekty `Zend_Db_Expr` lub jako tablice tych elementów. Drugi argument tej metody może zostać pominięty co oznacza, że kolumny powinny zostać dodane do tabeli z klauzuli FROM. W przeciwnym razie należy podać alias lub nazwę tabeli.

#### Przykłady dodawania kolumn metodą columns()

    // Tworzenie zapytania:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'), 'product_id')
                 ->columns('product_name');

    // Tworzenie zapytania używając nazwy alias:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p

    $select = $db->select()
                 ->from(array('p' => 'products'), 'p.product_id')
                 ->columns('product_name', 'p');
                 // Alternatywnie można użyć columns('p.product_name')

### Dodawanie tabeli do zapytania za pomocą JOIN

Wiele użytecznych zapytań zawiera klauzulę JOIN służącą do łączenia wierszy z wielu tabel. Aby dodać tabele do obiektu `Zend_Db_Select` należy użyć metody `join()`. Używanie jej jest podobne do użycia metody `from()` z tym, że tu można również użyć warunek łączenia tabel.

#### Przykład użycia metody join()

    // Tworzenie zapytania:
    //   SELECT p."product_id", p."product_name", l.*
    //   FROM "products" AS p JOIN "line_items" AS l
    //     ON p.product_id = l.product_id

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id', 'product_name'))
                 ->join(array('l' => 'line_items'),
                        'p.product_id = l.product_id');

Drugi argument metody `join()` to string stanowiący warunek połączenia. Jest to wyrażenie określające kryteria wg. których wiersze z jednej tabeli łączą się z wierszami drugiej tabeli. W tym miejscu można użyć aliasów tabel.

Do warunku połączenia nie są stosowane cudzysłowy; Jeśli występuje konieczność umieszczenia nazwy kolumny w cudzysłowach, należy użyć metody adaptera `quoteIdentifier()` przy formowaniu wyrażenia warunku połączenia.

Trzeci argument metody `join()` to tablica nazw kolumn (tak jak przy metodzie `from()`). Domyślną wartością jest "`*`". Można w nim podawać aliasy kolumn, wyrażenia lub obiekty `Zend_Db_Expr` w taki sam sposób jak w metodzie `from()`.

Aby nie wybierać żadnej kolumny należy podać pustą tablicę zamiast nazw kolumn. Ten sposób działa również w metodzie `from()` ale z podstawowych tabel przeważnie kolumny są potrzebne, co nie zawsze jest prawdą dla kolumn tabeli połączonej.

#### Przykład nie podawania kolumn

    // Tworzenie zapytania:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p JOIN "line_items" AS l
    //     ON p.product_id = l.product_id

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id', 'product_name'))
                 ->join(array('l' => 'line_items'),
                        'p.product_id = l.product_id',
                        array() ); // pusta lista kolumn

W miejscu listy kolum tabeli połączonej występuje pusta tablica (`array()`).

SQL dysponuje wieloma rodzajami klauzul JOIN. Poniżej znajduje się lista metod klasy `Zend_Db_Select` obsługująca je.

- `INNER JOIN` za pomocą `join(tabela, warunek, [kolumny])` lub `joinInner(tabela, warunek, [kolumny])`

  To jest najbardziej popularny rodzaj połączenia JOIN. Wiersze z każdej tabeli są porównywane za pomocą podanego warunku. Zbiór wyjściowy zawiera jedynie wiersze, które spełniają warunek połączenia. Jeśli żadna para wierszy nie spełnia warunku to zbiór pozostanie pusty.

  Wszystkie systemy zarządzania bazą danych (RDBMS) posiadają implementację tego rodzaju polecenia JOIN.

- `LEFT JOIN` za pomocą metody `joinLeft(tabela, warunek, [kolumny])`.

  Wszystkie wiersze z tabeli znajdującej się po lewej stronie "wchodzą" do wyniku. Te, które nie mają odpowiadającego wiersza w tabeli znajdującej się po stronie prawej - zostają wypełnione wartościami `NULL` (w kolumnach z prawej tabeli).

  Wszystkie systemy zarządzania bazą danych (RDBMS) posiadają implementację tego rodzaju polecenia JOIN.

- `RIGHT JOIN` za pomocą metody `joinRight(tabela, warunek, [kolumny])`

  RIGHT JOIN to przeciwieństwo LEFT JOIN. Wszystkie wiersze z tabeli znajdującej się po prawej stronie są umieszczone w wyniku. Te, które nie posiadają odpowiednika w tabeli lewej otrzymują wartości `NULL` w kolumnach z lewej tabeli.

  Niektóre systemy zarządzania bazą danych (RDBMS) nie wspierają tego typu polecenia JOIN ale generalnie każdy `RIGHT JOIN` może zostać zaprezentowany jako `LEFT JOIN` poprzez odwrócenie kolejności dodawania tabel.

- `FULL JOIN` za pomocą metody `joinFull(tabela, warunek, [kolumny])`

  To polecenie jest jak połączenie `LEFT JOIN` oraz `RIGHT JOIN`. Wszystkie wiersze z obu tabel są włączane do wyniku. Jeśli dany wiersz nie posiada odpowiednika spełniającego warunek połączenia w drugiej tabeli to w kolumnach z tej tabeli umieszczony jest `NULL`.

  Niektóre systemy zarządzania bazą danych (RDBMS) nie wspierają tego typu polecenia JOIN.

- `CROSS JOIN` za pomocą metody `joinCross(tabela, [kolumny])`.

  Cross join to iloczyn kartezjański tabel. Każdy wiersz z pierwszej tabeli zostaje połączony z każdym wierszem z tabeli drugiej. Ilość wierszy w zbiorze wynikowym jest równa iloczynowi ilości wierszy w obu tabelach. Poprzez użycie warunku WHERE można ograniczyć wiersze wynikowe przez co cross join może być podobny do składni polecenia join ze standardu SQL-89.

  Metoda `joinCross()` nie ma parametru odnoszącego się do warunku połączenia. Niektóre systemy zarządzania bazą danych (RDBMS) nie wspierają tego typu polecenia JOIN.

- `NATURAL JOIN` za pomocą metody `joinNatural(tabela, [kolumny])`.

  Polecenie natural join łączy wiersze pod względem wszystkich kolumn, które mają taką samą nazwę w obydwu tabelach. Warunkiem połączenia jest zgodność wartości wszystkich tak samo nazwanych kolumn tabel. Porównywanie wartości na zasadzie niezgodności (różnicy) nie stanowi polecenia natural join. Jedynie polecenia typu natural inner join są zaimplementowane w tym API pomimo tego że standard SQL definiuje też polecenia natural outer join.

  Metoda `joinCross()` nie ma parametru odnoszącego się do warunku połączenia.

Oprócz powyższych metod można uprościć zapytania używając metod JoinUsing. Zamiast podawania pełnego warunku można wybrać nazwę kolumny, na podstawie której połączenie będzie przeprowadzone a obiekt `Zend_Db_Select` dopisze niezbędną część polecenia warunku.

#### Przykład użycia metody joinUsing()

    // Tworzenie zapytania:
    //   SELECT *
    //   FROM "table1"
    //   JOIN "table2"
    //   ON "table1".column1 = "table2".column1
    //   WHERE column2 = 'foo'

    $select = $db->select()
                 ->from('table1')
                 ->joinUsing('table2', 'column1')
                 ->where('column2 = ?', 'foo');

Każda z metod połączenia klasy `Zend_Db_Select` ma odpowiednią metodę 'using'.

- `joinUsing(tabela, [kolumny])` and `joinInnerUsing(tabela, [kolumny])`

- `joinLeftUsing(tabela, [kolumny])`

- `joinRightUsing(tabela, [kolumny])`

- `joinFullUsing(tabela, [kolumny])`

### Dodawanie klauzuli WHERE

Za pomocą metody `where()` można określić kryteria ograniczające ilość wierszy zwracanych przez zapytanie. Pierwszy argument tej metody to wyrażenie SQL które zostanie użyte w klauzuli WHERE zapytania SQL.

#### Przykład użycia metody where()

    // Tworzenie zapytania:
    //   SELECT product_id, product_name, price
    //   FROM "products"
    //   WHERE price > 100.00

    $select = $db->select()
                 ->from('products',
                        array('product_id', 'product_name', 'price'))
                 ->where('price > 100.00');

Wyrażenia w metodach `where()` lub `orWhere()` nie zostają umieszczone w cudzysłowach. Jeśli nazwa kolumny tego wymaga należy użyć metody `quoteIdentifier()` podczas tworzenia parametru warunku.

Drugi argument metody `where()` jest opcjonalny. Stanowi on wartość umieszczaną w warunku. `Zend_Db_Select` ogranicza tą wartość cudzysłowami i za jej pomocą podmienia symbol znaku zapytania ("`?`") w warunku.

#### Przykład użycia parametru w metodzie where()

    // Tworzenie zapytania:
    //   SELECT product_id, product_name, price
    //   FROM "products"
    //   WHERE (price > 100.00)

    $minimumPrice = 100;

    $select = $db->select()
                 ->from('products',
                        array('product_id', 'product_name', 'price'))
                 ->where('price > ?', $minimumPrice);

Drugi parametr metody `where()` przyjmuje również tablicę w przypadku gdy używa się operatora IN.

#### Przykład użycia tablicy w metodzie where()

    // Tworzenie zapytania:
    //   SELECT product_id, product_name, price
    //   FROM "products"
    //   WHERE (product_id IN (1, 2, 3))

    $productIds = array(1, 2, 3);

    $select = $db->select()
                 ->from('products',
                        array('product_id', 'product_name', 'price'))
                 ->where('product_id IN (?)', $productIds);

Metoda `where()` może być wywoływana wiele razy dla jednego obiektu `Zend_Db_Select`. Zapytanie wynikowe łączy wszystkie warunki używając wyrażenia AND.

#### Przykład wywołania metody where() wiele razy

    // Tworzenie zapytania:
    //   SELECT product_id, product_name, price
    //   FROM "products"
    //   WHERE (price > 100.00)
    //     AND (price < 500.00)

    $minimumPrice = 100;
    $maximumPrice = 500;

    $select = $db->select()
                 ->from('products',
                        array('product_id', 'product_name', 'price'))
                 ->where('price > ?', $minimumPrice)
                 ->where('price < ?', $maximumPrice);

Jeśli istnieje potrzeba połączenia warunków za pomocą wyrażenia OR należy użyć metody `orWhere()`. Można jej używać w taki sam sposób jak metody `where()`. W wynikowym poleceniu warunki zostaną połączone wyrażeniem OR zamiast AND.

#### Przykład użycia metody orWhere()

    // Tworzenie zapytania:
    //   SELECT product_id, product_name, price
    //   FROM "products"
    //   WHERE (price < 100.00)
    //     OR (price > 500.00)

    $minimumPrice = 100;
    $maximumPrice = 500;

    $select = $db->select()
                 ->from('products',
                        array('product_id', 'product_name', 'price'))
                 ->where('price < ?', $minimumPrice)
                 ->orWhere('price > ?', $maximumPrice);

`Zend_Db_Select` automatycznie umieszcza wyrażenia podane do metod `where()` lub `orWhere()` w nawiasach. Dzięki temu kolejność wykonywania działań logicznych nie spowoduje nieoczekiwanych rezultatów.

#### Przykład umieszczania wyrażeń w nawiasach

    // Tworzenie zapytania:
    //   SELECT product_id, product_name, price
    //   FROM "products"
    //   WHERE (price < 100.00 OR price > 500.00)
    //     AND (product_name = 'Apple')

    $minimumPrice = 100;
    $maximumPrice = 500;
    $prod = 'Apple';

    $select = $db->select()
                 ->from('products',
                        array('product_id', 'product_name', 'price'))
                 ->where("price < $minimumPrice OR price > $maximumPrice")
                 ->where('product_name = ?', $prod);

W powyższym przykładzie zapytanie bez nawiasów przyniosłoby inny rezultat ponieważ AND ma wyższy priorytet niż OR. Dzięki nawiasom `Zend_Db_Select` sprawia, że każde wywołanie metody `where()` łączy zawarte w niej warunki z wyższym priorytetem niż AND który łączy poszczególne warunki.

### Adding a GROUP BY Clause

W SQL, klauzula `GROUP BY` pozwala na ograniczenie wierszy wyników zapytania do jednego wiersza na każdą unikalną wartość znalezioną w kolumnie podanej przy klauzuli `GROUP BY`.

Aby określić kolumny używane do podzielenia wyników na grupy w `Zend_Db_Select` należy użyć metody `group()`. Jako argument podaje się kolumnę lub tablicę kolumn, które mają trafić do klauzuli `GROUP BY`.

#### Przykład użycia metody group()

    // Tworzenie zapytania:
    //   SELECT p."product_id", COUNT(*) AS line_items_per_product
    //   FROM "products" AS p JOIN "line_items" AS l
    //     ON p.product_id = l.product_id
    //   GROUP BY p.product_id

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id'))
                 ->join(array('l' => 'line_items'),
                        'p.product_id = l.product_id',
                        array('line_items_per_product' => 'COUNT(*)'))
                 ->group('p.product_id');

Podobnie jak w przypadku metody `from()` w argumencie można używać aliasów tabel a nazwy są umieszczane w cudzysłowach jako identyfikatory chyba że łańcuch znaków zawiera nawiasy lub jest obiektem `Zend_Db_Expr`.

### Dodawanie klauzuli HAVING

W SQL, klauzula `HAVING` wprowadza ograniczenie w stosunku do grup wierszy. Jest to podobne do sposobu w jaki klauzula `WHERE` ogranicza wiersze ogólnie. Te klauzule są różne ponieważ warunki `WHERE` są oceniane prze definiowaniem grup, podczas gdy warunki `HAVING` nakładane są po uformowaniu grup.

W `Zend_Db_Select` można określić warunki dotyczące grup wierszy za pomocą metody `having()`. Użycie jej jest podobne do metody `where()`. Pierwszy argument to string zawierający wyrażenie SQL. Opcjonalny drugi argument to wartość używana do zamienienia pozycyjnych parametrów w wyrażeniu SQL. Wyrażenia umieszczone w wielu wywołaniach metody `having()` są łączone za pomocą operatora `AND` lub `OR` - jeśli zostanie użyta metoda `orHaving()`.

#### Przykład użycia metody having()

    // Tworzenie zapytania:
    //   SELECT p."product_id", COUNT(*) AS line_items_per_product
    //   FROM "products" AS p JOIN "line_items" AS l
    //     ON p.product_id = l.product_id
    //   GROUP BY p.product_id
    //   HAVING line_items_per_product > 10

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id'))
                 ->join(array('l' => 'line_items'),
                        'p.product_id = l.product_id',
                        array('line_items_per_product' => 'COUNT(*)'))
                 ->group('p.product_id')
                 ->having('line_items_per_product > 10');

W metodach `having()` oraz `orHaving()` nie jest stosowane umieszczanie identyfikatorów w cudzysłowach. Jeśli nazwa kolumny tego wymaga należy użyć metody `quoteIdentifier()` podczas tworzenia parametru warunku.

### Dodawanie klauzuli ORDER BY

W SQL, klauzula `ORDER BY` określa jedną bądź więcej kolumn lub wyrażeń według których zbiór wynikowy jest posortowany. Jeśli poda się wiele kolumn to sortowanie odbywa się w pierwszej kolejności na podstawie wcześniej podanej kolumny. Jeśli istnieją wiersze o takiej samej wartośći w danej kolumnie to do sortowania używana jest kolejna klumna klauzuli `ORDER BY`. Domyślny kierunek sortowania to od najmniejszej wartości do największej. Można sortować w przeciwnym kierunku przez użycie słowa kluczowego `DESC` po nazwie kolumny sortowania.

W `Zend_Db_Select` można użyć metody `order()` i podać kolumnę lub tablicę kolumn według których sortowanie ma przebiegać. Każdy z elementów tablicy powinien być łańcuchem znaków określającym kolumnę. Opcjonalnie można dodać słowa kluczowe `ASC` lub `DESC` oddzielone od kolumny spacją.

Podobnie jak przy metodach `from()` oraz `group()` nazwy kolumn są otaczane cudzysłowami, chyba że zawierają nawiasy lub są obiektami klasy `Zend_Db_Expr`.

#### Przykład użycia metody order()

    // Tworzenie zapytania:
    //   SELECT p."product_id", COUNT(*) AS line_items_per_product
    //   FROM "products" AS p JOIN "line_items" AS l
    //     ON p.product_id = l.product_id
    //   GROUP BY p.product_id
    //   ORDER BY "line_items_per_product" DESC, "product_id"

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id'))
                 ->join(array('l' => 'line_items'),
                        'p.product_id = l.product_id',
                        array('line_items_per_product' => 'COUNT(*)'))
                 ->group('p.product_id')
                 ->order(array('line_items_per_product DESC',
                               'product_id'));

### Dodawanie klauzuli LIMIT

Niektóre systemy zarządzania bazą danych (RDBMS) rozszerzają SQL za pomocą klauzuli `LIMIT`. Za jej pomocą można ograniczyć ilość wierszy zwracanych w zapytaniu do podanej ilości. Można również określić ilość wierszy, która ma zostać opuszczona przed rozpoczęciem zwracania wyników zapytania. Dzięki temu można w łatwy sposób uzyskać podzbiór ze zbioru wynikowego. Może to być przydatne np. przy wyświetlaniu rezultatów zapytania z podziałem na strony.

W `Zend_Db_Select` można użyć metody `limit()` aby określić ilość wierszy do zwrócenia oraz do opuszczenia. Pierswszy argumement metody to ilość wierszy jaka maksymalnie ma zostać zwrócona. Drugi argument to ilość wierszy do opuszczenia.

#### Przykład użycia metody limit()

    // Tworzenie zapytania:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p
    //   LIMIT 10, 20
    // Equivalent to:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p
    //   LIMIT 20 OFFSET 10

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id', 'product_name'))
                 ->limit(20, 10);

Polecenie `LIMIT` nie jest wspierane przez wszystkie rodzaje baz danych. Niektóre z nich wymagają innej składni dla uzyskania podobnego efektu. Każda z klas `Zend_Db_Adapter_Abstract` zawiera metodę tworzącą polecenie SQL odpowiednie dla danego RDBMS.

Można użyć metody `limitPage()` jako alternatywy do określania ilości wierszy do zwrotu i do pominięcia. Ta metoda pozwala na podzielenie zbioru wynikowego na wiele podzbiorów o stałej wielkości i zwrócenie jednego z nich. Innymi słowy należy określić długość jednej "strony" z wynikami zapytania oraz liczbę porządkową określającą stronę, która ma zostać zwrócona. Numer strony stanowi pierwszy argument metody `limitPage()` a długość strony to drugi argument. Obydwa argumenty są wymagane - nie mają wartości domyślnych.

#### Przykład użycia metody limitPage()

    // Tworzenie zapytania:
    //   SELECT p."product_id", p."product_name"
    //   FROM "products" AS p
    //   LIMIT 10, 20

    $select = $db->select()
                 ->from(array('p' => 'products'),
                        array('product_id', 'product_name'))
                 ->limitPage(2, 10);

### Dodawanie słowa kluczowego DISTINCT do zapytania

Metoda `distinct()` pozwala na dodanie słowa kluczowego `DISTINCT` do zapytania SQL.

#### Przykład użycia metody distinct()

    // Tworzenie zapytania:
    //   SELECT DISTINCT p."product_name"
    //   FROM "products" AS p

    $select = $db->select()
                 ->distinct()
                 ->from(array('p' => 'products'), 'product_name');

### Dodawanie słowa kluczowego FOR UPDATE do zapytania

Metoda `forUpdate()` pozwala na dodanie słowa kluczowego `FOR UPDATE` do zapytania SQL.

#### Przykład użycia metody forUpdate()

    // Tworzenie zapytania:
    //   SELECT FOR UPDATE p.*
    //   FROM "products" AS p

    $select = $db->select()
                 ->forUpdate()
                 ->from(array('p' => 'products'));

### Tworzenie zapytania z UNION

Z `Zend_Db_Select` można łączyć zapytania poprzez przekazanie tablicy obiektów `Zend_Db_Select` lub łańcuchów zapytań do metody `union()`. Jako drugi parametr można podać stałe `Zend_Db_Select::SQL_UNION` lub `Zend_Db_Select::SQL_UNION_ALL` aby określić rodzaj połączenia jaki chce się uzyskać.

#### Przykład użycia metody union()

    $sql1 = $db->select();
    $sql2 = "SELECT ...";

    $select = $db->select()
        ->union(array($sql1, $sql2))
        ->order("id");

## Wykonywanie zapytań Select

Poniższa część opisuje jak wywołać zapytanie zawarte w obiekcie `Zend_Db_Select`.

### Wykonywanie zapytań Select z poziomu adaptera bazy danych

Zapytanie zawarte w obiekcie `Zend_Db_Select` można wywołać poprzez podanie obiektu jako pierwszego argumentu metody `query()` obiektu `Zend_Db_Adapter_Abstract`. Zalecane jest używanie obiektów `Zend_Db_Select` zamiast łańcuchów znaków z zapytaniem.

Metoda `query()` w zależności od typu adaptera bazy danych zwraca obiekt klasy `Zend_Db_Statement` lub PDOStatement.

#### Przykład użycia metody query() adaptera bazy danych

    $select = $db->select()
                 ->from('products');

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

### Wykonywanie zapytań Select z samego obiektu

Jako alternatywny sposób w stosunku do użycia metody `query()` adaptera bazy danych, można użyć metody o takiej samej nazwie obiektu `Zend_Db_Select`. Obydwie metody zwracają obiekt klasy `Zend_Db_Statement` lub PDOStatement w zależności od typu użytego adaptera.

#### Przykład użycia metody obiektu Zend_Db_Select

    $select = $db->select()
                 ->from('products');

    $stmt = $select->query();
    $result = $stmt->fetchAll();

### Zamiana obiektu Select w łańcuch polecenia SQL

Jeśli niezbędny jest dostęp do polecenia SQL w postaci łańcucha znaków zawartego w obiekcie `Zend_Db_Select`, należy użyć metody `__toString()`.

#### Przykład użycia metody __toString()

    $select = $db->select()
                 ->from('products');

    $sql = $select->__toString();
    echo "$sql\n";

    // Wyjściowy string:
    //   SELECT * FROM "products"

## Inne metody

Ta część opisuje inne metody klasy `Zend_Db_Select`, które nie zostały wymienione wcześniej: `getPart()` oraz `reset()`.

### Uzyskanie części obiektu Select

Metoda `getPart()` zwraca postać łąńcucha znaków odpowiadającą jednej części polecenia SQL. Można użyć tej metody aby uzyskać tablicę warunków klauzuli `WHERE`, tablicę kolumn (lub wyrażeń) zawartych w liście `SELECT` albo wartości ilości wierszy klauzuli `LIMIT`.

Wartością zwracaną nie jest string zawierający składnię SQL. Zamiast tego zwracana jest wewnętrzna postać danych, co przeważnie oznacza tablicę zawierającą wartości i wyrażenia. Każda część zapytania ma inną strukturę.

Jedynym argumentem metody `getPart()` jest łańcuch znaków identyfikujący żądaną część zapytania. String `'from'` odpowiada części obiektu `Zend_Db_Select`, która przechowuje informacje o tabelach (włączając w to tabele połączone) w klauzuli `FROM`.

Klasa `Zend_Db_Select` definiuje stałe, których można użyć jako oznaczeń zapytania SQL. Dozwolone jest stosowanie tych stałych bądź nazw dosłownych.

#### Stałe używane przez metody getPart() oraz reset()

Stała | Wartość dosłowna
--- | ---
`Zend_Db_Select::DISTINCT` | `'distinct'`
`Zend_Db_Select::FOR_UPDATE` | `'forupdate'`
`Zend_Db_Select::COLUMNS` | `'columns'`
`Zend_Db_Select::FROM` | `'from'`
`Zend_Db_Select::WHERE` | `'where'`
`Zend_Db_Select::GROUP` | `'group'`
`Zend_Db_Select::HAVING` | `'having'`
`Zend_Db_Select::ORDER` | `'order'`
`Zend_Db_Select::LIMIT_COUNT` | `'limitcount'`
`Zend_Db_Select::LIMIT_OFFSET` | `'limitoffset'`

#### Przykład użycia metody getPart()

    $select = $db->select()
                 ->from('products')
                 ->order('product_id');

    // Można użyć dosłownej nazwy żądanej części
    $orderData = $select->getPart( 'order' );

    // Alternatywnie można posłużyć się stałą
    $orderData = $select->getPart( Zend_Db_Select::ORDER );

    // Wartość zwrotna może nie być stringiem a tablicą.
    // Każda część zapytania może mieć inną strukturę.
    print_r( $orderData );

### Czyszczenie części obiektu Select

Metoda `reset()` umożliwia wyczyszczenie podanej części lub całości (jeśli nie poda się argumentu) zapytania SQL.

Jedyny argument jest opcjonalny. Można podać w nim część zapytania przeznaczoną do wyczyszczenia używając tych samych łąńcuchów co w przypadku metody `getPart()`. Podana część zapytania jest ustawiana w stan domyślny.

Jeśli nie poda się parametru, metoda `reset()` ustawia wszystkie części zapytania w ich stan domyślny. Przez to używany obiekt `Zend_Db_Select` odpowiada nowemu obiektowi, tak jakby został on dopiero utworzony.

#### Przykład użycia metody reset()

    // Tworzenie zapytania:
    //   SELECT p.*
    //   FROM "products" AS p
    //   ORDER BY "product_name"

    $select = $db->select()
                 ->from(array('p' => 'products')
                 ->order('product_name');

    // Zmienione wymagania, sortowanie wg. innej kolumny:
    //   SELECT p.*
    //   FROM "products" AS p
    //   ORDER BY "product_id"

    // Wyczyszczenie jednej części aby można było ją ponownie zdefiniować
    $select->reset( Zend_Db_Select::ORDER );

    // Podanie nowej kolumny sortowania
    $select->order('product_id');

    // Wyczyszczenie wszystkich części zapytania
    $select->reset();