Explorer: Filtrování a řazení

Třída Selection poskytuje metody pro filtrování a řazení výběru dat.

where($condition, ...$params) Přidá podmínku WHERE. Více podmínek je spojeno operátorem AND
whereOr(array $conditions) Přidá skupinu podmínek WHERE spojených operátorem OR
wherePrimary($value) Přidá podmínku WHERE podle primárního klíče
order($columns, ...$params) Nastaví řazení ORDER BY
select($columns, ...$params) Specifikuje sloupce, které se mají načíst
limit($limit, $offset = null) Omezí počet řádků (LIMIT) a volitelně nastaví OFFSET
page($page, $itemsPerPage, &$total = null) Nastaví stránkování
group($columns, ...$params) Seskupí řádky (GROUP BY)
having($condition, ...$params) Přidá podmínku HAVING pro filtrování seskupených řádků

Metody lze řetězit (tzv. fluent interface): $table->where(...)->order(...)->limit(...).

V těchto metodách můžete také používat speciální notaci pro přístup k datům ze souvisejících tabulek.

Escapování a identifikátory

Metody automaticky escapují parametry a uvozují identifikátory (názvy tabulek a sloupců), čímž zabraňuje SQL injection. Pro správné fungování je nutné dodržovat několik pravidel:

  • Klíčová slova, názvy funkcí, procedur apod. pište velkými písmeny.
  • Názvy sloupců a tabulek pište malými písmeny.
  • Řetězce vždy dosazujte přes parametry.
where('name = ' . $name);         // KRITICKÁ ZRANITELNOST: SQL injection
where('name LIKE "%search%"');    // ŠPATNĚ: komplikuje automatické uvozování
where('name LIKE ?', '%search%'); // SPRÁVNĚ: hodnota dosazená přes parametr

where('name like ?', $name);     // ŠPATNĚ: vygeneruje: `name` `like` ?
where('name LIKE ?', $name);     // SPRÁVNĚ: vygeneruje: `name` LIKE ?
where('LOWER(name) = ?', $value);// SPRÁVNĚ: LOWER(`name`) = ?

where (string|array $condition, …$parameters)static

Filtruje výsledky pomocí podmínek WHERE. Její silnou stránkou je inteligentní práce s různými typy hodnot a automatická volba SQL operátorů.

Základní použití:

$table->where('id', $value);     // WHERE `id` = 123
$table->where('id > ?', $value); // WHERE `id` > 123
$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow'

Díky automatické detekci vhodných operátorů nemusíme řešit různé speciální případy. Nette je vyřeší za nás:

$table->where('id', 1);          // WHERE `id` = 1
$table->where('id', null);       // WHERE `id` IS NULL
$table->where('id', [1, 2, 3]);  // WHERE `id` IN (1, 2, 3)
// lze použít i zástupný otazník bez operátoru:
$table->where('id ?', 1);        // WHERE `id` = 1

Metoda správně zpracovává i záporné podmínky a prázdné pole:

$table->where('id', []);         // WHERE `id` IS NULL AND FALSE -- nic nenalezne
$table->where('id NOT', []);     // WHERE `id` IS NULL OR TRUE -- nalezene vše
$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- nalezene vše
// $table->where('NOT id ?', $ids);  Pozor - tato syntaxe není podporovaná

Jako parametr můžeme předat také výsledek z jiné tabulky – vytvoří se poddotaz:

// WHERE `id` IN (SELECT `id` FROM `tableName`)
$table->where('id', $explorer->table($tableName));

// WHERE `id` IN (SELECT `col` FROM `tableName`)
$table->where('id', $explorer->table($tableName)->select('col'));

Podmínky můžeme předat také jako pole, jehož položky se spojí pomocí AND:

// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`)
$table->where([
	'price_final < price_original',
	'stock_count > min_stock',
]);

V poli můžeme použít dvojice klíč ⇒ hodnota a Nette opět automaticky zvolí správné operátory:

// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3))
$table->where([
	'status' => 'active',
	'id' => [1, 2, 3],
]);

V poli můžeme kombinovat SQL výrazy se zástupnými otazníky a více parametry. To je vhodné pro komplexní podmínky s přesně definovanými operátory:

// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5)
$table->where([
	'age > ?' => 18,
	'ROUND(score, ?) > ?' => [2, 75.5], // dva parametry předáme jako pole
]);

Vícenásobné volání where() podmínky automaticky spojuje pomocí AND.

whereOr (array $parameters)static

Podobně jako where() přidává podmínky, ale s tím rozdílem, že je spojuje pomocí OR:

// WHERE (`status` = 'active') OR (`deleted` = 1)
$table->whereOr([
	'status' => 'active',
	'deleted' => true,
]);

I zde můžeme použít komplexnější výrazy:

// WHERE (`price` > 1000) OR (`price_with_tax` > 1500)
$table->whereOr([
	'price > ?' => 1000,
	'price_with_tax > ?' => 1500,
]);

wherePrimary (mixed $key)static

Přidá podmínku pro primární klíč tabulky:

// WHERE `id` = 123
$table->wherePrimary(123);

// WHERE `id` IN (1, 2, 3)
$table->wherePrimary([1, 2, 3]);

Pokud má tabulka kompozitní primární klíč (např. foo_id, bar_id), předáme jej jako pole:

// WHERE `foo_id` = 1 AND `bar_id` = 5
$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch();

// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3))
$table->wherePrimary([
	['foo_id' => 1, 'bar_id' => 5],
	['foo_id' => 2, 'bar_id' => 3],
])->fetchAll();

order (string $columns, …$parameters)static

Určuje pořadí, v jakém budou řádky vráceny. Můžeme řadit podle jednoho či více sloupců, v sestupném či vzestupném pořadí, nebo podle vlastního výrazu:

$table->order('created');                   // ORDER BY `created`
$table->order('created DESC');              // ORDER BY `created` DESC
$table->order('priority DESC, created');    // ORDER BY `priority` DESC, `created`
$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC

select (string $columns, …$parameters)static

Specifikuje sloupce, které se mají vrátit z databáze. Ve výchozím stavu Nette Database Explorer vrací pouze ty sloupce, které se reálně použijí v kódu. Metodu select() tak používáme v případech, kdy potřebujeme vrátit specifické výrazy:

// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date`
$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y');

Aliasy definované pomocí AS jsou pak dostupné jako vlastnosti objektu ActiveRow:

foreach ($table as $row) {
	echo $row->formatted_date;   // přístup k aliasu
}

limit (?int $limit, ?int $offset = null)static

Omezuje počet vrácených řádků (LIMIT) a volitelně umožňuje nastavit offset:

$table->limit(10);        // LIMIT 10 (vrátí prvních 10 řádků)
$table->limit(10, 20);    // LIMIT 10 OFFSET 20

Pro stránkování je vhodnější použít metodu page().

page (int $page, int $itemsPerPage, &$numOfPages = null)static

Usnadňuje stránkování výsledků. Přijímá číslo stránky (počítané od 1) a počet položek na stránku. Volitelně lze předat referenci na proměnnou, do které se uloží celkový počet stránek:

$numOfPages = null;
$table->page(page: 3, itemsPerPage: 10, $numOfPages);
echo "Celkem stránek: $numOfPages";

group (string $columns, …$parameters)static

Seskupuje řádky podle zadaných sloupců (GROUP BY). Používá se obvykle ve spojení s agregačními funkcemi:

// Spočítá počet produktů v každé kategorii
$table->select('category_id, COUNT(*) AS count')
	->group('category_id');

having (string $having, …$parameters)static

Nastavuje podmínku pro filtrování seskupených řádků (HAVING). Lze ji použít ve spojení s metodou group() a agregačními funkcemi:

// Nalezne kategorie, které mají více než 100 produktů
$table->select('category_id, COUNT(*) AS count')
	->group('category_id')
	->having('count > ?', 100);

Dotazování přes související tabulky

V metodách where(), select(), order() a group() můžeme používat speciální notace pro přístup k sloupcům z jiných tabulek. Explorer automaticky vytvoří potřebné JOINy.

Tečková notace (nadřazená_tabulka.sloupec) se používá pro vztah 1:N z pohledu podřízené tabulky:

$books = $explorer->table('book');

// Najde knihy, jejichž autor má jméno začínající na 'Jon'
$books->where('author.name LIKE ?', 'Jon%');

// Seřadí knihy podle jména autora sestupně
$books->order('author.name DESC');

// Vypíše název knihy a jméno autora
$books->select('book.title, author.name');

Dvojtečková notace (:podřízená_tabulka.sloupec) se používá pro vztah 1:N z pohledu nadřazené tabulky:

$authors = $explorer->table('author');

// Najde autory, kteří napsali knihu s 'PHP' v názvu
$authors->where(':book.title LIKE ?', '%PHP%');

// Spočítá počet knih pro každého autora
$authors->select('*, COUNT(:book.id) AS book_count')
	->group('author.id');

Ve výše uvedeném příkladu s dvojtečkovou notací (:book.title) není specifikován sloupec s cizím klíčem. Explorer automaticky detekuje správný sloupec na základě názvu nadřazené tabulky. V tomto případě se spojuje přes sloupec book.author_id, protože název zdrojové tabulky je author. Pokud by existovalo více možných spojení, Explorer vyhodí výjimku AmbiguousReferenceKeyException.

Spojovací sloupec lze explicitně uvést v závorce:

// Najde autory, kteří přeložili knihu s 'PHP' v názvu
$authors->where(':book(translator).title LIKE ?', '%PHP%');

Notace lze řetězit pro přístup přes více tabulek:

// Najde autory knih označených tagem 'PHP'
$authors->where(':book:book_tag.tag.name', 'PHP')
	->group('author.id');

Rozšíření podmínek pro JOIN

Metoda joinWhere() rozšiřuje podmínky, které se uvádějí při propojování tabulek v SQL za klíčovým slovem ON.

Dejme tomu, že chceme najít knihy přeložené konkrétním překladatelem:

// Najde knihy přeložené překladatelem jménem 'David'
$books = $explorer->table('book')
	->joinWhere('translator', 'translator.name', 'David');
// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David')

V podmínce joinWhere() můžeme používat stejné konstrukce jako v metodě where() – operátory, zástupné otazníky, pole hodnot či SQL výrazy.

Pro složitější dotazy s více JOINy můžeme definovat aliasy tabulek:

$tags = $explorer->table('tag')
	->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950)
	->alias(':book_tag.book.author', 'book_author');
// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id`
// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id`
// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id`
//    AND (`book_author`.`born` < 1950)

Všimněte si, že zatímco metoda where() přidává podmínky do klauzule WHERE, metoda joinWhere() rozšiřuje podmínky v klauzuli ON při spojování tabulek.