Query Builder
Build type-safe SQL queries programmatically with the Query Builder API.
Overview
The Query Builder provides a fluent, type-safe API for constructing SQL queries. It helps prevent SQL injection, provides IDE autocomplete, and makes complex queries more maintainable.
Basic Usage
SELECT Queries
javascript
import { select } from '@northprint/duckdb-wasm-adapter-core';
// Simple select
const query = select('id', 'name', 'email')
.from('users')
.build();
// SELECT id, name, email FROM users
// With conditions
const query = select('*')
.from('users')
.where('active', '=', true)
.where('age', '>', 18)
.build();
// SELECT * FROM users WHERE active = true AND age > 18
// With ordering and limit
const query = select('name', 'salary')
.from('employees')
.where('department', '=', 'Engineering')
.orderBy('salary', 'DESC')
.limit(10)
.build();
// SELECT name, salary FROM employees
// WHERE department = 'Engineering'
// ORDER BY salary DESC
// LIMIT 10INSERT Queries
javascript
import { insert } from '@northprint/duckdb-wasm-adapter-core';
// Single insert
const query = insert('users')
.values({
name: 'John Doe',
email: 'john@example.com',
age: 30
})
.build();
// INSERT INTO users (name, email, age)
// VALUES ('John Doe', 'john@example.com', 30)
// Multiple inserts
const query = insert('users')
.values([
{ name: 'Alice', email: 'alice@example.com' },
{ name: 'Bob', email: 'bob@example.com' }
])
.build();
// Insert with returning
const query = insert('users')
.values({ name: 'Charlie', email: 'charlie@example.com' })
.returning('id')
.build();
// INSERT INTO users (name, email)
// VALUES ('Charlie', 'charlie@example.com')
// RETURNING idUPDATE Queries
javascript
import { update } from '@northprint/duckdb-wasm-adapter-core';
// Simple update
const query = update('users')
.set({ email: 'newemail@example.com' })
.where('id', '=', 123)
.build();
// UPDATE users SET email = 'newemail@example.com' WHERE id = 123
// Multiple updates
const query = update('users')
.set({
name: 'Updated Name',
email: 'updated@example.com',
modified_at: 'NOW()'
})
.where('id', '=', 123)
.build();
// Conditional update
const query = update('employees')
.set({ salary: raw('salary * 1.1') })
.where('performance_rating', '>', 4)
.where('department', '=', 'Sales')
.build();DELETE Queries
javascript
import { deleteFrom } from '@northprint/duckdb-wasm-adapter-core';
// Simple delete
const query = deleteFrom('users')
.where('id', '=', 123)
.build();
// DELETE FROM users WHERE id = 123
// Delete with multiple conditions
const query = deleteFrom('logs')
.where('created_at', '<', '2023-01-01')
.where('level', '=', 'debug')
.build();
// DELETE FROM logs
// WHERE created_at < '2023-01-01' AND level = 'debug'Advanced Features
JOINs
javascript
// Inner join
const query = select('u.name', 'd.name as department')
.from('users', 'u')
.innerJoin('departments', 'd', 'u.department_id', '=', 'd.id')
.build();
// Left join
const query = select('u.name', 'COUNT(o.id) as order_count')
.from('users', 'u')
.leftJoin('orders', 'o', 'u.id', '=', 'o.user_id')
.groupBy('u.id', 'u.name')
.build();
// Multiple joins
const query = select('u.name', 'd.name as department', 'p.title as project')
.from('users', 'u')
.innerJoin('departments', 'd', 'u.department_id', '=', 'd.id')
.leftJoin('user_projects', 'up', 'u.id', '=', 'up.user_id')
.leftJoin('projects', 'p', 'up.project_id', '=', 'p.id')
.where('u.active', '=', true)
.build();Aggregations
javascript
// Group by with aggregations
const query = select('department')
.count('*', 'employee_count')
.avg('salary', 'avg_salary')
.max('salary', 'max_salary')
.min('salary', 'min_salary')
.from('employees')
.groupBy('department')
.having('COUNT(*)', '>', 5)
.orderBy('avg_salary', 'DESC')
.build();
// Window functions
const query = select('name', 'salary', 'department')
.raw('RANK() OVER (PARTITION BY department ORDER BY salary DESC)', 'salary_rank')
.from('employees')
.build();Subqueries
javascript
// Subquery in WHERE
const subquery = select('id')
.from('departments')
.where('name', '=', 'Engineering');
const query = select('*')
.from('users')
.whereIn('department_id', subquery)
.build();
// Subquery in FROM
const subquery = select('department_id')
.count('*', 'emp_count')
.from('employees')
.groupBy('department_id')
.as('dept_counts');
const query = select('d.name', 'dc.emp_count')
.from('departments', 'd')
.innerJoin(subquery, 'dc', 'd.id', '=', 'dc.department_id')
.build();Complex Conditions
javascript
// OR conditions
const query = select('*')
.from('users')
.where('department', '=', 'Engineering')
.orWhere('department', '=', 'Sales')
.build();
// Grouped conditions
const query = select('*')
.from('products')
.where(q => q
.where('category', '=', 'Electronics')
.where('price', '<', 1000)
)
.orWhere(q => q
.where('category', '=', 'Books')
.where('price', '<', 50)
)
.build();
// IN clause
const query = select('*')
.from('users')
.whereIn('department', ['Engineering', 'Sales', 'Marketing'])
.build();
// BETWEEN
const query = select('*')
.from('orders')
.whereBetween('created_at', '2024-01-01', '2024-12-31')
.build();
// NULL checks
const query = select('*')
.from('users')
.whereNull('deleted_at')
.whereNotNull('email')
.build();TypeScript Support
Type-Safe Queries
typescript
interface User {
id: number;
name: string;
email: string;
department: string;
}
// Type-safe select
const query = select<User>('id', 'name', 'email')
.from('users')
.where('department', '=', 'Engineering')
.build();
// Type-safe insert
const query = insert<User>('users')
.values({
id: 1,
name: 'John Doe',
email: 'john@example.com',
department: 'Engineering'
})
.build();
// Type checking prevents errors
const query = select<User>('id', 'name', 'invalid_column') // TypeScript error!
.from('users')
.build();Custom Types
typescript
// Define table schemas
type UserTable = {
id: number;
name: string;
email: string;
created_at: Date;
};
type OrderTable = {
id: number;
user_id: number;
amount: number;
status: 'pending' | 'completed' | 'cancelled';
};
// Use in queries
const userQuery = select<UserTable>('id', 'name')
.from('users')
.where('created_at', '>', new Date('2024-01-01'))
.build();
const orderQuery = select<OrderTable>('*')
.from('orders')
.where('status', '=', 'completed')
.build();Query Execution
With React
jsx
import { useQueryBuilder } from '@northprint/duckdb-wasm-adapter-react';
import { select } from '@northprint/duckdb-wasm-adapter-core';
function UserList() {
const queryBuilder = useQueryBuilder();
const query = select('id', 'name', 'email')
.from('users')
.where('active', '=', true)
.orderBy('name')
.limit(50);
const { data, loading, error } = queryBuilder.execute(query);
return (
<div>
{loading && <div>Loading...</div>}
{error && <div>Error: {error.message}</div>}
{data && (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}With Vue
vue
<template>
<div>
<ul v-if="users">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
<script setup>
import { useQueryBuilder } from '@northprint/duckdb-wasm-adapter-vue';
import { select } from '@northprint/duckdb-wasm-adapter-core';
const queryBuilder = useQueryBuilder();
const query = select('id', 'name', 'email')
.from('users')
.where('active', '=', true)
.orderBy('name')
.limit(50);
const { data: users } = queryBuilder.execute(query);
</script>Raw SQL
Sometimes you need to use raw SQL expressions:
javascript
import { raw } from '@northprint/duckdb-wasm-adapter-core';
// Raw expressions in select
const query = select('name')
.raw('EXTRACT(YEAR FROM AGE(birth_date))', 'age')
.from('users')
.build();
// Raw WHERE conditions
const query = select('*')
.from('events')
.whereRaw('DATE(created_at) = CURRENT_DATE')
.build();
// Raw in SET clause
const query = update('products')
.set({
price: raw('price * 1.1'),
updated_at: raw('NOW()')
})
.where('category', '=', 'Electronics')
.build();Query Composition
Build complex queries by composing smaller parts:
javascript
// Base query
const baseQuery = select('id', 'name', 'email')
.from('users')
.where('active', '=', true);
// Add department filter
function addDepartmentFilter(query, department) {
return query.where('department', '=', department);
}
// Add sorting
function addSorting(query, field, direction = 'ASC') {
return query.orderBy(field, direction);
}
// Add pagination
function addPagination(query, page, pageSize = 20) {
const offset = (page - 1) * pageSize;
return query.limit(pageSize).offset(offset);
}
// Compose final query
let finalQuery = baseQuery;
finalQuery = addDepartmentFilter(finalQuery, 'Engineering');
finalQuery = addSorting(finalQuery, 'name');
finalQuery = addPagination(finalQuery, 1, 10);
const sql = finalQuery.build();Query Templates
Create reusable query templates:
javascript
// Query template factory
function createUserQuery(filters = {}) {
let query = select('id', 'name', 'email', 'department')
.from('users');
if (filters.active !== undefined) {
query = query.where('active', '=', filters.active);
}
if (filters.department) {
query = query.where('department', '=', filters.department);
}
if (filters.search) {
query = query.where('name', 'ILIKE', `%${filters.search}%`);
}
if (filters.minAge) {
query = query.where('age', '>=', filters.minAge);
}
return query;
}
// Usage
const activeEngineers = createUserQuery({
active: true,
department: 'Engineering'
}).build();
const searchResults = createUserQuery({
search: 'john',
minAge: 25
}).orderBy('name').build();Performance Tips
- Use specific columns instead of SELECT *
- Add indexes for frequently queried columns
- Use LIMIT for large result sets
- Optimize JOIN order - smaller tables first
- Use EXISTS instead of IN for subqueries
- Avoid function calls in WHERE clauses
Best Practices
- Always use parameterized queries to prevent SQL injection
- Use TypeScript for type safety
- Create reusable query functions for common patterns
- Test complex queries before deployment
- Document query intentions with comments
- Use meaningful aliases for readability
Next Steps
- Caching - Optimize with query caching
- Performance - Advanced optimization
- TypeScript - Full TypeScript guide