|
763 | 763 |
|
764 | 764 | --- |
765 | 765 |
|
| 766 | +## Eager Loading with Preload |
| 767 | + |
| 768 | +Eager loading allows you to load associated records in batch queries, avoiding the N+1 query problem. CQL provides the `preload` method for efficient association loading. |
| 769 | + |
| 770 | +### Basic Preloading |
| 771 | + |
| 772 | +```crystal |
| 773 | +# Preload a single association |
| 774 | +users = User.preload(:posts).all |
| 775 | +users.each do |user| |
| 776 | + # posts are already loaded - no additional query |
| 777 | + puts "#{user.name} has #{user.posts.size} posts" |
| 778 | +end |
| 779 | +
|
| 780 | +# Preload multiple associations |
| 781 | +users = User.preload(:posts, :comments).all |
| 782 | +users.each do |user| |
| 783 | + puts "#{user.name}: #{user.posts.size} posts, #{user.comments.size} comments" |
| 784 | +end |
| 785 | +``` |
| 786 | + |
| 787 | +### Chaining with Where |
| 788 | + |
| 789 | +Preload works seamlessly with other query methods: |
| 790 | + |
| 791 | +```crystal |
| 792 | +# Filter users then preload their posts |
| 793 | +active_users = User.where(active: true) |
| 794 | + .preload(:posts) |
| 795 | + .order(:name) |
| 796 | + .all |
| 797 | +
|
| 798 | +# Only 2 queries total: |
| 799 | +# 1. SELECT * FROM users WHERE active = true ORDER BY name |
| 800 | +# 2. SELECT * FROM posts WHERE user_id IN (...) |
| 801 | +``` |
| 802 | + |
| 803 | +### How Preload Works |
| 804 | + |
| 805 | +Unlike JOIN-based eager loading, `preload` uses separate queries: |
| 806 | + |
| 807 | +1. First query fetches the primary records |
| 808 | +2. Subsequent queries fetch associated records using `IN` clauses |
| 809 | +3. Records are matched in memory and associations are populated |
| 810 | + |
| 811 | +This approach: |
| 812 | + |
| 813 | +- Avoids cartesian product issues with multiple has_many associations |
| 814 | +- Keeps queries simple and indexable |
| 815 | +- Works well with pagination |
| 816 | + |
| 817 | +### Preload vs Join |
| 818 | + |
| 819 | +| Aspect | `preload` | `join` | |
| 820 | +|--------------------------|------------------------------|---------------------------------| |
| 821 | +| Query count | N+1 reduced to 2 queries | 1 query | |
| 822 | +| Cartesian product | No | Yes (with multiple has_many) | |
| 823 | +| Filtering on association | No | Yes | |
| 824 | +| Pagination | Works correctly | May return duplicates | |
| 825 | +| Best for | Loading data to display | Filtering by association | |
| 826 | + |
| 827 | +```crystal |
| 828 | +# Use preload when you need the associated data |
| 829 | +users = User.preload(:posts).all |
| 830 | +
|
| 831 | +# Use join when filtering by association |
| 832 | +users_with_recent_posts = User.join(:posts) |
| 833 | + .where("posts.created_at > ?", 1.week.ago) |
| 834 | + .distinct |
| 835 | + .all |
| 836 | +``` |
| 837 | + |
| 838 | +### Performance Considerations |
| 839 | + |
| 840 | +```crystal |
| 841 | +# Preload only what you need |
| 842 | +users = User.preload(:posts).all # Good - loads posts in one query |
| 843 | +
|
| 844 | +# Avoid preloading unused associations |
| 845 | +users = User.preload(:posts, :comments, :profile).all # Wasteful if you only use posts |
| 846 | +
|
| 847 | +# Combine with select for optimal performance |
| 848 | +users = User.select(:id, :name) |
| 849 | + .preload(:posts) |
| 850 | + .where(active: true) |
| 851 | + .all |
| 852 | +``` |
| 853 | + |
| 854 | +--- |
| 855 | + |
766 | 856 | ## Advanced Query Composition |
767 | 857 |
|
768 | 858 | CQL's query interface allows you to build complex queries by combining multiple query methods. This section demonstrates common patterns and best practices for composing sophisticated queries. |
|
0 commit comments