UNPKG

@jakub.knejzlik/ts-query

Version:

TypeScript implementation of SQL builder

492 lines (375 loc) 10.2 kB
import { Meta } from "@storybook/addon-docs"; import { QueryPreview } from "./QueryPreview"; <Meta title="1. Query" /> # Query Builder The Query Builder is a powerful tool that allows developers to construct SQL queries without having to write raw SQL. By offering a fluent interface, it simplifies the process of creating complex queries and ensures that the syntax is correct, reducing the risk of errors. ## Basic Usage The foundation of any query starts with the `Q.select` method. This method initializes the query building process: <QueryPreview code={`Q.select().from('tableName')`} /> ### Selecting Fields Instead of selecting all fields, you can specify which columns you want to retrieve: <QueryPreview code={`Q.select().from('table').addField('fieldName')`} /> If you need to rename a column in the result set, you can provide an alias: <QueryPreview code={` Q.select().from('table').addField('fieldName', 'aliasName'); `} /> You can also add multiple fields at once: <QueryPreview code={` Q.select() .from('table') .addFields([ { name: 'fieldName1', alias: 'aliasName1' }, { name: 'fieldName2', alias: 'aliasName2' }, ]); `} /> Or you can reset all specified fields: <QueryPreview code={` Q.select() .from('table') .addField('fieldNameA') .fields([{ name: 'fieldNameB' }]); `} /> ### Table Aliasing: Sometimes, for the sake of clarity or to avoid column name conflicts, you might want to alias a table: <QueryPreview code={` Q.select().from('table', 't'); `} /> ### Union <QueryPreview code={` Q.select() .from('users') .union(Q.select().from('users2')) `} /> ### Joining Tables Join operations are essential in SQL for combining rows from two or more tables based on a related column. The Query Builder supports various types of joins: INNER, LEFT, RIGHT, and FULL joins. ### Basic Join To perform a basic join (INNER JOIN by default): <QueryPreview code={` Q.select() .from('users') .join( Q.table('orders', 'o'), Cond.columnEqual('users.id', 'o.user_id') ); `} /> ### Specific Join Types You can specify the type of join you need: **Inner Join**: Combines rows from different tables when the join condition is met. <QueryPreview code={` Q.select() .from('users') .innerJoin( Q.table('orders', 'o'), Cond.columnEqual('users.id', 'o.user_id') ); `} /> **Left Join**: Returns all rows from the left table, and the matched rows from the right table. If no match, NULLs are returned for columns of the right table. <QueryPreview code={` Q.select() .from('users') .leftJoin( Q.table('orders', 'o'), Cond.columnEqual('users.id', 'o.user_id') ); `} /> **Right Join**: Similar to the left join, but returns all rows from the right table and matched rows from the left table. <QueryPreview code={` Q.select() .from('users') .rightJoin( Q.table('orders', 'o'), Cond.columnEqual('users.id', 'o.user_id') ); `} /> **Full Join**: Combines the results of both left and right joins. It returns rows when there is a match in either left or right table. <QueryPreview code={` Q.select() .from('users') .fullJoin( Q.table('orders', 'o'), Cond.columnEqual('users.id', 'o.user_id') ); `} /> ### Cross Join A cross join returns the Cartesian product of the two tables, combining each row from the first table with every row from the second table: <QueryPreview code={` Q.select() .from('users') .crossJoin(Q.table('orders', 'o')); `} /> ### Join Conditions The join condition is specified using the `Conditions` object. This condition determines how the tables are joined. For example, joining users and orders on user ID: <QueryPreview code={` Cond.columnEqual('users.id', 'o.user_id'); `} /> ### Multiple Joins You can chain multiple joins to combine more than two tables: <QueryPreview code={` Q.select() .from('users') .innerJoin( Q.table('orders', 'o'), Cond.columnEqual('users.id', 'o.user_id') ) .leftJoin( Q.table('products', 'p'), Cond.columnEqual('o.product_id', 'p.id') ); `} /> ### Aliasing in Joins When joining tables, especially with multiple joins, it's often useful to alias tables for clarity and to resolve column name conflicts: <QueryPreview code={` Q.select() .from('users', 'u') .leftJoin( Q.table('orders', 'o'), Cond.columnEqual('u.id', 'o.user_id') ); `} /> ### Complex Join Conditions Join conditions can be more complex, involving logical operators like AND, OR: <QueryPreview code={` Q.select() .from('users') .innerJoin(Q.table('orders', 'o'), Cond.and([ Cond.columnEqual('users.id', 'o.user_id'), Cond.greaterThan('orders.amount', 100), ])); `} /> ### Join with subquery You can also join with a subquery as a table source. This is useful for aggregating or filtering data before joining: <QueryPreview code={` Q.select() .from('users', 'u') .leftJoin( Q.table(Q.select() .from('orders') .addField(Fn.sum('price','totalPrice')) .limit(10) , 'o'), Cond.columnEqual('u.id', 'o.user_id') ); `} /> ### WHERE Conditions Filtering the data you retrieve is a common operation. The `where` method allows you to specify conditions to limit the rows returned: <QueryPreview code={` Q.select().from('table').where(Cond.equal('fieldName', 'foo')).where(Cond.lessThan('fieldName2', 123)); `} /> ### Optional WHERE Conditions You can also add where conditions optionaly by providing empty values. For example all of these conditions are not applied <QueryPreview code={` Q.select() .from('table') .where(Cond.and([])) .where(Cond.or([])) .where(Cond.in('foo', 1>2 ? ['abc'] : [])); `} /> Also nested conditions are filtered <QueryPreview code={` Q.select() .from('table') .where(Cond.and([ Cond.equal('foo','blah'), Cond.or([ Cond.in('foo',[]) ]) ])); `} /> ### LIMIT and OFFSET In scenarios where you don't want to retrieve all rows, you can limit the result set: <QueryPreview code={` Q.select().from('table').limit(10); `} /> If you want to skip a certain number of rows before retrieving the result set, use the offset method: <QueryPreview code={` Q.select().from('table').offset(5); `} /> ### ORDER BY Sorting your result set can be achieved using the `orderBy` method: <QueryPreview code={` Q.select().from('table').orderBy('fieldName'); `} /> If you need the sorting to be in descending order: <QueryPreview code={` Q.select().from('table').orderBy('fieldName').orderBy('fieldName2', 'DESC'); `} /> You can also reset order by configuration: <QueryPreview code={` Q.select().from('table').orderBy('fieldName').removeOrderBy().orderBy('fieldName2', 'DESC'); `} /> ### GROUP BY When you want to group rows that have the same values in specified columns into summary rows: <QueryPreview code={` Q.select().from('table').groupBy('fieldName').groupBy('fieldName2'); `} /> You can also reset group by configuration: <QueryPreview code={` Q.select().from('table').groupBy('fieldName').removeGroupBy().groupBy('fieldName2','DESC'); `} /> ### Complex Table Sources: For more advanced queries, you might need to use subqueries as a table source: <QueryPreview code={` Q.select().from(Q.select().from('table').where(Cond.equal('field', 'blah')),'subt'); `} /> ## Mutations You can also generate insert/update/delete statements ### Insert <QueryPreview code={` Q.insert('table').values([{foo:'blah'}]); `} /> ### Insert select <QueryPreview code={` Q.insert('table').select(Q.select().from('table2')); `} /> You can also specify column names: <QueryPreview code={` Q.insert('user_stats').select(Q.select().from('users').addField('region').addField(Fn.count('*')).groupBy('region'),['region','count']); `} /> ### Update <QueryPreview code={` Q.update('table') .set({ foo: 'blah', total: Fn.divide(Fn.multiply('amount','price'),Q.raw(2)) }).where(Cond.equal('name','john')); `} /> ### Delete <QueryPreview code={` Q.delete('table').where(Cond.equal('id',123)); `} /> ## Examples ### Basic Select Query A simple example to retrieve all columns from a table: <QueryPreview code={` Q.select().from('foo'); `} /> ### Query with Multiple Conditions A more complex example showcasing multiple conditions: <QueryPreview code={` Q.select() .from('users') .addField('id') .addField('name') .where( Cond.and([ Cond.equal('age', 25), Cond.or([ Cond.greaterThan('salary', 50000), Cond.like('position', 'manager%'), ]), ]) ) .orderBy('salary', 'DESC') .limit(10) .offset(5); `} /> ### Query with Nested Conditions Queries can also have nested conditions for more intricate filtering: <QueryPreview code={` Q.select() .from('products') .where( Cond.and([ Cond.between('price', [10, 50]), Cond.or([ Cond.like('name', '%apple%'), Cond.notLike('description', '%refurbished%'), ]), ]) ); `} /> ## Immutability One of the key features of the Query Builder is its immutability. This ensures that once a query is constructed, it cannot be changed, preventing unintended side-effects: ```tsx const originalQuery = Q.select().from("table").orderBy("bar", "DESC"); const newQuery = originalQuery.orderBy("foo"); ``` This design ensures that queries can be safely reused and extended without affecting the original query. ## Advanced Conditions: Advanced conditions provide more flexibility in filtering data: - `Cond.in(...)`: Checks if a column's value is within a set of values. - `Cond.null(...)`: Checks if a column's value is NULL. - `Cond.notNull(...)`: Checks if a column's value is NOT NULL. - `Cond.columnEqual(...)`: Compares two columns for equality. ## Cloning: If you need to create a copy of a query to make variations without affecting the original, you can use the `clone` method: ```tsx originalQuery.clone(); ``` ## Serialization: For scenarios where you might need to store the query structure or send it across a network, the Query Builder provides serialization methods: ```tsx const serialized = query.serialize(); SelectQuery.deserialize(serialized); ```