Complex Queries
CQL provides a powerful, type-safe query builder that allows you to compose sophisticated SQL queries using idiomatic Crystal code. This guide demonstrates how to build complex queries using Model.query and the CQL::Query interface.
Join Types and Strategies
Implicit vs Explicit Joins
CQL supports both implicit and explicit join syntax for different use cases:
# Implicit JOIN - CQL automatically detects relationships based on foreign keys
users_with_posts = User.query.joins(:posts).all(User)
# Generates: SELECT * FROM users INNER JOIN posts ON users.id = posts.user_id
# Explicit JOIN - Full control over join conditions
users_with_posts = User.query.join(Post) { users.id == posts.user_id }
.all(User)Foreign Key Requirements for Implicit Joins
Important: Implicit joins in CQL require foreign key relationships to be explicitly defined in your schema. CQL uses these foreign key definitions to automatically infer the correct JOIN conditions.
Schema Definition with Foreign Keys
For implicit joins to work, you must define foreign key constraints in your schema:
How CQL Uses Foreign Keys for Implicit Joins
When you use implicit joins, CQL:
Looks up the foreign key definition in the schema
Automatically determines the join condition based on the foreign key relationship
Generates the appropriate SQL JOIN clause
Multi-Level Implicit Joins
With proper foreign key definitions, CQL can handle complex multi-level joins:
Without Foreign Keys - Explicit Joins Required
If foreign keys are not defined in the schema, you must use explicit joins:
Composite Foreign Keys
CQL supports composite foreign keys for complex relationships:
Foreign Key vs Column Reference
There's an important distinction between having a column that references another table and having a proper foreign key constraint:
Checking Schema Foreign Keys
You can inspect your schema's foreign key definitions:
Best Practices for Foreign Keys and Joins
Always define foreign keys in your schema for relationships you'll query
Use consistent naming for foreign key columns (e.g.,
user_id,post_id)Define foreign keys before creating tables in your schema
Use explicit joins when you need custom join conditions beyond foreign key relationships
Document complex relationships in your schema comments
This foreign key requirement ensures that CQL can provide type-safe, automatic join generation while maintaining clear relationships in your database schema.
Inner Joins (Default)
Inner joins return only records that have matching records in both tables:
Left Joins
Left joins return all records from the left table and matching records from the right table:
Right Joins
Right joins return all records from the right table and matching records from the left table:
Self Joins
Join a table to itself for hierarchical or comparative queries:
Cross Joins
Generate Cartesian product of two tables (use with caution):
Advanced Filtering
Combine multiple conditions, logical operators, and expressions:
Ordering and Sorting
Basic Ordering
Advanced Ordering
Pagination and Limiting
Basic Pagination
Cursor-Based Pagination
Grouping, Aggregation, and Having
Use grouping and aggregate functions for reporting and analytics:
DISTINCT Queries
Remove duplicate results from your queries:
Subqueries
Use subqueries for advanced filtering and data retrieval:
Raw SQL Integration
Inspecting Generated SQL
Executing Raw SQL
N+1 Query Prevention
One of the most common performance issues in ORMs is the N+1 query problem, where loading a collection of records results in 1 query for the main records plus N additional queries for each associated record. CQL provides several mechanisms to prevent this issue.
Understanding the N+1 Problem
CQL's JOIN-Based Approach
CQL automatically uses efficient JOIN-based queries instead of separate queries for each association:
Has Many Associations
Many-to-Many Associations
Lazy Loading with Caching
CQL implements intelligent lazy loading that prevents duplicate queries:
Efficient Association Operations
CQL provides methods to perform common operations without loading full records:
Count Without Loading
Existence Checks Without Loading
ID-Only Operations
Query Optimization Strategies
Batch Operations
Strategic Use of Joins
Performance Best Practices
Advanced N+1 Prevention Patterns
Manual Preloading
Caching Association Counts
Monitoring and Debugging
Query Analysis
Performance Testing
Query Optimization Tips
Query Analysis
Advanced Query Patterns
Conditional Queries
Complex Aggregations
Window Functions (if supported by your database)
Best Practices Summary
Query Construction
Use table aliases for clarity in multi-join queries
Use block syntax for complex conditions to improve readability
Leverage automatic relationship detection for simpler joins
Use explicit joins when you need custom conditions
Define foreign keys in your schema to enable implicit joins and automatic relationship detection
Performance
Always use
.selectto limit columns and improve performanceUse
.limitand.offsetfor paginationPrefer EXISTS over IN for subqueries with large result sets
Use proper indexing strategy for WHERE clause columns
Leverage association count and existence methods to avoid N+1 queries
Use JOIN-based aggregation instead of iterating over associations
Monitor query patterns and optimize based on actual usage patterns
Maintainability
Use meaningful variable names and method extraction for complex queries
Use
.to_sqlto inspect and verify generated SQLTest query performance with realistic data volumes
Document complex business logic in query methods
Security
Always use parameterized queries (CQL handles this automatically)
Validate user input before using in dynamic query construction
Use proper escaping for LIKE patterns
Related Guides
For more information on related query topics:
CRUD Operations - Basic create, read, update, delete operations
Queryable - Core querying interface and methods
Relations - Understanding model relationships and associations
Scopes - Reusable query methods and named scopes
Transactions - Managing database transactions
Validations - Data validation and integrity
Last updated
Was this helpful?