@@ -314,6 +314,122 @@ $stmt->execute($result['bindings']);
314314$rows = $stmt->fetchAll();
315315```
316316
317+ ** Aggregations** — count, sum, avg, min, max with optional aliases:
318+
319+ ``` php
320+ $result = (new Builder())
321+ ->from('orders')
322+ ->count('*', 'total')
323+ ->sum('price', 'total_price')
324+ ->select(['status'])
325+ ->groupBy(['status'])
326+ ->having([Query::greaterThan('total', 5)])
327+ ->build();
328+
329+ // SELECT COUNT(*) AS `total`, SUM(`price`) AS `total_price`, `status`
330+ // FROM `orders` GROUP BY `status` HAVING `total` > ?
331+ ```
332+
333+ ** Distinct:**
334+
335+ ``` php
336+ $result = (new Builder())
337+ ->from('users')
338+ ->distinct()
339+ ->select(['country'])
340+ ->build();
341+
342+ // SELECT DISTINCT `country` FROM `users`
343+ ```
344+
345+ ** Joins** — inner, left, right, and cross joins:
346+
347+ ``` php
348+ $result = (new Builder())
349+ ->from('users')
350+ ->join('orders', 'users.id', 'orders.user_id')
351+ ->leftJoin('profiles', 'users.id', 'profiles.user_id')
352+ ->crossJoin('colors')
353+ ->build();
354+
355+ // SELECT * FROM `users`
356+ // JOIN `orders` ON `users.id` = `orders.user_id`
357+ // LEFT JOIN `profiles` ON `users.id` = `profiles.user_id`
358+ // CROSS JOIN `colors`
359+ ```
360+
361+ ** Raw expressions:**
362+
363+ ``` php
364+ $result = (new Builder())
365+ ->from('t')
366+ ->filter([Query::raw('score > ? AND score < ?', [10, 100])])
367+ ->build();
368+
369+ // SELECT * FROM `t` WHERE score > ? AND score < ?
370+ // bindings: [10, 100]
371+ ```
372+
373+ ** Union:**
374+
375+ ``` php
376+ $admins = (new Builder())->from('admins')->filter([Query::equal('role', ['admin'])]);
377+ $result = (new Builder())
378+ ->from('users')
379+ ->filter([Query::equal('status', ['active'])])
380+ ->union($admins)
381+ ->build();
382+
383+ // SELECT * FROM `users` WHERE `status` IN (?)
384+ // UNION SELECT * FROM `admins` WHERE `role` IN (?)
385+ ```
386+
387+ ** Conditional building** — ` when() ` applies a callback only when the condition is true:
388+
389+ ``` php
390+ $result = (new Builder())
391+ ->from('users')
392+ ->when($filterActive, fn(Builder $b) => $b->filter([Query::equal('status', ['active'])]))
393+ ->build();
394+ ```
395+
396+ ** Page helper** — page-based pagination:
397+
398+ ``` php
399+ $result = (new Builder())
400+ ->from('users')
401+ ->page(3, 10) // page 3, 10 per page → LIMIT 10 OFFSET 20
402+ ->build();
403+ ```
404+
405+ ** Debug** — ` toRawSql() ` inlines bindings for inspection (not for execution):
406+
407+ ``` php
408+ $sql = (new Builder())
409+ ->from('users')
410+ ->filter([Query::equal('status', ['active'])])
411+ ->limit(10)
412+ ->toRawSql();
413+
414+ // SELECT * FROM `users` WHERE `status` IN ('active') LIMIT 10
415+ ```
416+
417+ ** Query helpers** — merge, diff, and validate:
418+
419+ ``` php
420+ // Merge queries (later limit/offset/cursor overrides earlier)
421+ $merged = Query::merge($defaultQueries, $userQueries);
422+
423+ // Diff — queries in A not in B
424+ $unique = Query::diff($queriesA, $queriesB);
425+
426+ // Validate attributes against an allow-list
427+ $errors = Query::validate($queries, ['name', 'age', 'status']);
428+
429+ // Page helper — returns [limit, offset] queries
430+ [$limit, $offset] = Query::page(3, 10);
431+ ```
432+
317433** Pluggable extensions** — customize attribute mapping, identifier wrapping, and inject extra conditions:
318434
319435``` php
0 commit comments