# `Selecto.Builder.Sql.Tagging`

Many-to-many tagging SQL pattern generation for join table relationships.

Supports tagging patterns through intermediate join tables with aggregation
and faceted filtering capabilities. Handles the complexity of many-to-many
relationships while maintaining proper parameterization.

Phase 3: Full many-to-many implementation with tag aggregation and faceted filtering

## Supported Patterns

- **Basic many-to-many joins**: LEFT JOIN through intermediate table
- **Tag aggregation**: string_agg for comma-separated tag lists  
- **Faceted filtering**: EXISTS subqueries for tag filtering
- **Tag counting**: COUNT-based filtering for minimum tag requirements

## Examples

    # Basic tagging join: posts ↔ post_tags ↔ tags
    config = %{
      source: "tags",
      join_table: "post_tags",  
      tag_field: "name",
      main_foreign_key: "post_id",
      tag_foreign_key: "tag_id"
    }
    
    # Generates SQL like:
    # LEFT JOIN post_tags pt ON main.id = pt.post_id  
    # LEFT JOIN tags t ON pt.tag_id = t.id
    # With string_agg(t.name, ', ') for aggregation

# `build_faceted_tag_filter`

Build faceted tag filter using EXISTS subquery.

Generates EXISTS subqueries for filtering records that have specific tags.
Supports both single tag and array-based tag filtering.

## Examples

    # Single tag filter
    build_faceted_tag_filter(config, "programming", :single)
    
    # Multiple tags filter (ANY match)
    build_faceted_tag_filter(config, ["elixir", "phoenix"], :any)
    
    # Multiple tags filter (ALL required)  
    build_faceted_tag_filter(config, ["web", "backend"], :all)

Returns: `{where_clause_iodata, params}`

# `build_tag_aggregation_column`

Build tag aggregation column SQL.

Generates string_agg expressions for displaying comma-separated tag lists.
Handles NULL values and provides proper GROUP BY compatibility.

## Examples

    build_tag_aggregation_column("tags", "name", "tag_list")
    #=> "string_agg(tags.name, ', ') as tag_list"
    
    build_tag_aggregation_column("categories", "title", "category_names")  
    #=> "string_agg(categories.title, ', ') as category_names"

Returns: iodata for SELECT clause

# `build_tag_count_column`

Build tag count column SQL.

Generates COUNT expressions for counting distinct tags per record.
Useful for filtering by minimum tag requirements.

## Examples

    build_tag_count_column("tags", "tag_count")
    #=> "COUNT(DISTINCT tags.id) as tag_count"

Returns: iodata for SELECT clause  

# `build_tag_count_filter`

Build tag count filter for minimum tag requirements.

Generates WHERE conditions that filter records based on the number of tags
they have. Useful for finding "well-tagged" content.

## Examples

    build_tag_count_filter(config, {:gte, 3})  # At least 3 tags
    build_tag_count_filter(config, {:eq, 1})   # Exactly 1 tag  
    build_tag_count_filter(config, {:between, 2, 5})  # Between 2-5 tags

Returns: `{where_clause_iodata, params}`

# `build_tagging_join_with_aggregation`

Build many-to-many tagging join with intermediate table.

Generates the double-JOIN pattern required for many-to-many relationships:
1. Main table → intermediate join table  
2. Join table → tag table

Includes support for tag aggregation and proper GROUP BY handling.

## Parameters
- `selecto`: Main selecto struct
- `join`: Join identifier (atom)
- `config`: Join configuration with tagging options
- `fc`: Current from clause iodata  
- `p`: Current parameters list
- `ctes`: Current CTEs list

Returns: `{updated_from_clause, updated_params, updated_ctes}`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
