@@ -717,3 +717,327 @@ const rawSingleUser = await qb.raw<User>({
717717
718718console .log (' Raw single user:' , rawSingleUser .results );
719719```
720+
721+ ## DISTINCT Selection
722+
723+ Use the ` distinct() ` method to remove duplicate rows from your query results.
724+
725+ ### Simple DISTINCT
726+
727+ ``` typescript
728+ import { D1QB } from ' workers-qb' ;
729+
730+ const qb = new D1QB (env .DB );
731+
732+ // SELECT DISTINCT * FROM users
733+ const uniqueUsers = await qb .select (' users' )
734+ .distinct ()
735+ .execute ();
736+
737+ // SELECT DISTINCT name, email FROM users
738+ const uniqueUserInfo = await qb .select (' users' )
739+ .distinct ()
740+ .fields ([' name' , ' email' ])
741+ .execute ();
742+ ```
743+
744+ ### DISTINCT ON (PostgreSQL)
745+
746+ PostgreSQL supports ` DISTINCT ON ` to select distinct rows based on specific columns:
747+
748+ ``` typescript
749+ import { PGQB } from ' workers-qb' ;
750+
751+ const qb = new PGQB (client );
752+
753+ // SELECT DISTINCT ON (department) department, name FROM users ORDER BY department
754+ const firstUserPerDept = await qb .select (' users' )
755+ .distinct ([' department' ])
756+ .fields ([' department' , ' name' ])
757+ .orderBy (' department' )
758+ .execute ();
759+ ```
760+
761+ ## Additional JOIN Types
762+
763+ In addition to INNER, LEFT, and CROSS joins, ` workers-qb ` supports RIGHT, FULL, and NATURAL joins.
764+
765+ ### RIGHT JOIN
766+
767+ A RIGHT JOIN returns all rows from the right table and matching rows from the left table:
768+
769+ ``` typescript
770+ const result = await qb .select (' orders' )
771+ .rightJoin ({ table: ' users' , on: ' orders.user_id = users.id' })
772+ .execute ();
773+ ```
774+
775+ ### FULL OUTER JOIN
776+
777+ A FULL JOIN returns all rows when there's a match in either table:
778+
779+ ``` typescript
780+ const result = await qb .select (' users' )
781+ .fullJoin ({ table: ' profiles' , on: ' users.id = profiles.user_id' })
782+ .execute ();
783+ ```
784+
785+ ### NATURAL JOIN
786+
787+ A NATURAL JOIN automatically joins tables based on columns with the same name:
788+
789+ ``` typescript
790+ const result = await qb .select (' users' )
791+ .naturalJoin (' profiles' )
792+ .execute ();
793+ ```
794+
795+ ## Set Operations (UNION, INTERSECT, EXCEPT)
796+
797+ Combine results from multiple queries using set operations.
798+
799+ ### UNION
800+
801+ Combine results from two queries, removing duplicates:
802+
803+ ``` typescript
804+ const result = await qb .select (' active_users' )
805+ .fields ([' id' , ' name' ])
806+ .union (qb .select (' archived_users' ).fields ([' id' , ' name' ]))
807+ .execute ();
808+ ```
809+
810+ ### UNION ALL
811+
812+ Combine results while keeping duplicates:
813+
814+ ``` typescript
815+ const result = await qb .select (' table1' )
816+ .fields ([' id' ])
817+ .unionAll (qb .select (' table2' ).fields ([' id' ]))
818+ .execute ();
819+ ```
820+
821+ ### INTERSECT
822+
823+ Return only rows that appear in both queries:
824+
825+ ``` typescript
826+ const result = await qb .select (' users' )
827+ .fields ([' id' ])
828+ .intersect (qb .select (' admins' ).fields ([' user_id' ]))
829+ .execute ();
830+ ```
831+
832+ ### EXCEPT
833+
834+ Return rows from the first query that don't appear in the second:
835+
836+ ``` typescript
837+ const result = await qb .select (' all_users' )
838+ .fields ([' id' ])
839+ .except (qb .select (' blocked_users' ).fields ([' user_id' ]))
840+ .execute ();
841+ ```
842+
843+ ### Chaining Set Operations
844+
845+ You can chain multiple set operations and add ORDER BY to the final result:
846+
847+ ``` typescript
848+ const result = await qb .select (' table1' )
849+ .fields ([' id' , ' name' ])
850+ .union (qb .select (' table2' ).fields ([' id' , ' name' ]))
851+ .union (qb .select (' table3' ).fields ([' id' , ' name' ]))
852+ .orderBy ({ name: ' ASC' })
853+ .execute ();
854+ ```
855+
856+ ## Common Table Expressions (CTEs)
857+
858+ CTEs allow you to define named temporary result sets using the ` WITH ` clause.
859+
860+ ### Simple CTE
861+
862+ ``` typescript
863+ const result = await qb .select (' orders' )
864+ .with (' active_users' , qb .select (' users' ).where (' status = ?' , ' active' ))
865+ .join ({ table: ' active_users' , on: ' orders.user_id = active_users.id' })
866+ .execute ();
867+
868+ // SQL: WITH active_users AS (SELECT * FROM users WHERE status = 'active')
869+ // SELECT * FROM orders JOIN active_users ON orders.user_id = active_users.id
870+ ```
871+
872+ ### Multiple CTEs
873+
874+ ``` typescript
875+ const result = await qb .select (' combined' )
876+ .with (' recent_orders' , qb .select (' orders' ).where (' created_at > ?' , lastWeek ))
877+ .with (' active_users' , qb .select (' users' ).where (' status = ?' , ' active' ))
878+ .execute ();
879+ ```
880+
881+ ### CTE with Column Names
882+
883+ ``` typescript
884+ const result = await qb .select (' results' )
885+ .with (
886+ ' user_stats' ,
887+ qb .select (' users' ).fields ([' id' , ' COUNT(*) as cnt' ]).groupBy (' id' ),
888+ [' user_id' , ' count' ] // Column names for the CTE
889+ )
890+ .execute ();
891+
892+ // SQL: WITH user_stats(user_id, count) AS (SELECT id, COUNT(*) as cnt FROM users GROUP BY id)
893+ ```
894+
895+ ## Query Inspection (toSQL / toDebugSQL)
896+
897+ Inspect generated SQL without executing the query.
898+
899+ ### toSQL()
900+
901+ Get the SQL query and parameters without executing:
902+
903+ ``` typescript
904+ const { sql, params } = qb .select (' users' )
905+ .where (' status = ?' , ' active' )
906+ .where (' age > ?' , 18 )
907+ .toSQL ();
908+
909+ console .log (sql ); // "SELECT * FROM users WHERE (status = ?) AND (age > ?)"
910+ console .log (params ); // ['active', 18]
911+ ```
912+
913+ ### toDebugSQL()
914+
915+ Get the SQL with parameters interpolated (for debugging only):
916+
917+ ``` typescript
918+ const debugSql = qb .select (' users' )
919+ .where (' id = ?' , 1 )
920+ .where (' name = ?' , " O'Brien" )
921+ .toDebugSQL ();
922+
923+ console .log (debugSql );
924+ // "SELECT * FROM users WHERE (id = 1) AND (name = 'O''Brien')"
925+ ```
926+
927+ ** Warning:** Never use ` toDebugSQL() ` output to execute queries as it bypasses parameterization.
928+
929+ ## Pagination
930+
931+ The ` paginate() ` method provides convenient pagination with metadata.
932+
933+ ``` typescript
934+ const result = await qb .select (' users' )
935+ .where (' active = ?' , true )
936+ .orderBy ({ created_at: ' DESC' })
937+ .paginate ({ page: 2 , perPage: 20 });
938+
939+ console .log (result .results ); // Array of users for page 2
940+ console .log (result .pagination ); // Pagination metadata
941+
942+ // result.pagination = {
943+ // page: 2,
944+ // perPage: 20,
945+ // total: 150,
946+ // totalPages: 8,
947+ // hasNext: true,
948+ // hasPrev: true
949+ // }
950+ ```
951+
952+ ## Query Plan Analysis (EXPLAIN)
953+
954+ Use ` explain() ` to get the query execution plan for debugging performance:
955+
956+ ``` typescript
957+ const plan = await qb .select (' users' )
958+ .where (' email = ?' , ' test@example.com' )
959+ .explain ();
960+
961+ console .log (plan .results );
962+ // Shows SQLite's EXPLAIN QUERY PLAN output
963+ ```
964+
965+ ## Transactions
966+
967+ Execute multiple queries atomically.
968+
969+ ### D1 Transactions
970+
971+ D1 uses batching for atomic operations:
972+
973+ ``` typescript
974+ import { D1QB } from ' workers-qb' ;
975+
976+ const qb = new D1QB (env .DB );
977+
978+ const results = await qb .transaction (async (tx ) => {
979+ return [
980+ tx .insert ({ tableName: ' orders' , data: { user_id: 1 , total: 100 } }),
981+ tx .update ({
982+ tableName: ' inventory' ,
983+ data: { stock: 9 },
984+ where: { conditions: ' product_id = ?' , params: [1 ] }
985+ }),
986+ ];
987+ });
988+ ```
989+
990+ ### Durable Objects Transactions
991+
992+ DOQB uses SQLite transactions synchronously:
993+
994+ ``` typescript
995+ import { DOQB } from ' workers-qb' ;
996+
997+ // Inside a Durable Object, use within blockConcurrencyWhile
998+ this .ctx .blockConcurrencyWhile (() => {
999+ qb .transaction ((tx ) => {
1000+ tx .insert ({ tableName: ' orders' , data: { user_id: 1 , total: 100 } }).execute ();
1001+ tx .update ({
1002+ tableName: ' inventory' ,
1003+ data: { stock: 9 },
1004+ where: { conditions: ' product_id = ?' , params: [1 ] }
1005+ }).execute ();
1006+ });
1007+ });
1008+ ```
1009+
1010+ ## Query Hooks
1011+
1012+ Register hooks to intercept queries before or after execution.
1013+
1014+ ### beforeQuery Hook
1015+
1016+ Modify queries before execution (e.g., add tenant filters):
1017+
1018+ ``` typescript
1019+ const tenantId = ' tenant-123' ;
1020+
1021+ qb .beforeQuery ((query , type ) => {
1022+ if (type === ' SELECT' || type === ' UPDATE' || type === ' DELETE' ) {
1023+ // Add tenant filter to all queries
1024+ query .query = query .query .replace (
1025+ ' WHERE' ,
1026+ ` WHERE tenant_id = '${tenantId }' AND `
1027+ );
1028+ }
1029+ return query ;
1030+ });
1031+ ```
1032+
1033+ ### afterQuery Hook
1034+
1035+ Process results or record metrics after execution:
1036+
1037+ ``` typescript
1038+ qb .afterQuery ((result , query , duration ) => {
1039+ console .log (` Query took ${duration }ms: ` , query .query );
1040+ metrics .record (' query_duration' , duration );
1041+ return result ;
1042+ });
1043+ ```
0 commit comments