SelectoKino Subselects Demo
Mix.install([
{:selecto_kino, path: "../vendor/selecto_kino"},
{:selecto, path: "../vendor/selecto"},
{:kino, "~> 0.12.0"},
{:postgrex, "~> 0.17.0"}
])
Introduction
This notebook demonstrates the new Subselect (Subquery) support in SelectoKino, providing interactive builders for creating nested queries for complex filtering and data retrieval.
Subselects allow you to:
- Use query results as values in WHERE clauses
- Check for existence of related data
- Create correlated queries that reference outer query values
- Build complex filtering logic with nested conditions
Connect to Database
# Connect to your database
connection_form = SelectoKino.connect()
Subselect Builder
The Subselect Builder provides different types of subqueries:
# Define a sample domain for the demo
domain_config = %{
source: %{
source_table: "films",
columns: %{
id: %{type: :integer},
title: %{type: :string},
release_year: %{type: :integer},
rating: %{type: :string},
rental_rate: %{type: :decimal},
replacement_cost: %{type: :decimal}
}
},
schemas: %{
inventory: %{
source_table: "inventory",
columns: %{
id: %{type: :integer},
film_id: %{type: :integer},
store_id: %{type: :integer}
}
},
rental: %{
source_table: "rental",
columns: %{
id: %{type: :integer},
inventory_id: %{type: :integer},
customer_id: %{type: :integer},
rental_date: %{type: :datetime}
}
}
},
name: "Films with Inventory"
}
# Create the subselect builder
subselect_builder = SelectoKino.SubselectBuilder.new(
domain: domain_config,
on_complete: fn config ->
IO.puts("Subselect configured!")
IO.inspect(config, label: "Configuration", pretty: true)
end
)
1. Scalar Subselects
Scalar subselects return a single value that can be used in comparisons:
# Find films with rental rate above average
above_avg_subselect = SelectoKino.SubselectBuilder.scalar(%{
select: "AVG(rental_rate)",
from: "films",
where: %{rating: "PG-13"}
})
IO.puts("SQL: Find films more expensive than average PG-13 films")
IO.inspect(above_avg_subselect, pretty: true)
# Find films released in the same year as a specific film
same_year_subselect = SelectoKino.SubselectBuilder.scalar(%{
select: "release_year",
from: "films",
where: %{title: "ACADEMY DINOSAUR"}
})
IO.inspect(same_year_subselect, label: "Same Year as Specific Film", pretty: true)
Maximum/Minimum Comparisons
# Find the most expensive film(s)
max_cost_subselect = SelectoKino.SubselectBuilder.scalar(%{
select: "MAX(replacement_cost)",
from: "films"
})
# Find films from the earliest year
min_year_subselect = SelectoKino.SubselectBuilder.scalar(%{
select: "MIN(release_year)",
from: "films",
where: %{rating: "G"}
})
IO.inspect([
max_cost: max_cost_subselect,
earliest_g_rated: min_year_subselect
], pretty: true)
2. List Subselects (IN/NOT IN)
List subselects return multiple values for set membership testing:
# Find films that have inventory
films_with_inventory = SelectoKino.SubselectBuilder.list(%{
select: "film_id",
from: "inventory",
distinct: true
})
IO.puts("Films with inventory (for IN clause):")
IO.inspect(films_with_inventory, pretty: true)
# Find films rented in the last 30 days
recent_rentals = SelectoKino.SubselectBuilder.list(%{
select: "inventory.film_id",
from: "rental JOIN inventory ON rental.inventory_id = inventory.id",
where: %{rental_date: {:gte, "CURRENT_DATE - INTERVAL '30 days'"}},
distinct: true
})
IO.inspect(recent_rentals, label: "Recently Rented Films", pretty: true)
NOT IN Examples
# Films never rented (NOT IN)
never_rented = %{
type: :not_in,
subselect: SelectoKino.SubselectBuilder.list(%{
select: "DISTINCT inventory.film_id",
from: "rental JOIN inventory ON rental.inventory_id = inventory.id"
})
}
# Films not in specific stores
not_in_store = SelectoKino.SubselectBuilder.list(%{
select: "film_id",
from: "inventory",
where: %{store_id: 1},
distinct: true
})
IO.inspect([
never_rented: never_rented,
not_in_store_1: not_in_store
], pretty: true)
3. EXISTS Subselects
EXISTS checks whether any rows match the criteria:
# Films that have been rented
rented_exists = SelectoKino.SubselectBuilder.exists(%{
from: "rental JOIN inventory ON rental.inventory_id = inventory.id",
where: %{
film_id: {:parent, "id"}
}
})
IO.puts("EXISTS: Films that have been rented")
IO.inspect(rented_exists, pretty: true)
# Films with inventory in multiple stores
multi_store_exists = SelectoKino.SubselectBuilder.exists(%{
from: "inventory i1 JOIN inventory i2 ON i1.film_id = i2.film_id",
where: %{
"i1.film_id": {:parent, "id"},
"i1.store_id": {:ne, "i2.store_id"}
}
})
IO.inspect(multi_store_exists, label: "Films in Multiple Stores", pretty: true)
NOT EXISTS Examples
# Films with no rentals in the last year
no_recent_rentals = %{
type: :not_exists,
subselect: SelectoKino.SubselectBuilder.exists(%{
from: "rental JOIN inventory ON rental.inventory_id = inventory.id",
where: %{
film_id: {:parent, "id"},
rental_date: {:gte, "CURRENT_DATE - INTERVAL '1 year'"}
}
})
}
IO.inspect(no_recent_rentals, label: "No Recent Rentals", pretty: true)
4. Correlated Subselects
Correlated subselects reference values from the outer query:
# Films with above-average rental rate for their rating
above_avg_for_rating = SelectoKino.SubselectBuilder.correlated(%{
select: "AVG(rental_rate)",
from: "films f2",
where: %{
rating: {:parent, "rating"}
}
})
IO.puts("Correlated: Films above average for their rating category")
IO.inspect(above_avg_for_rating, pretty: true)
# Count of other films with same rating
same_rating_count = SelectoKino.SubselectBuilder.correlated(%{
select: "COUNT(*)",
from: "films f2",
where: %{
rating: {:parent, "rating"},
id: {:ne, {:parent, "id"}}
}
})
IO.inspect(same_rating_count, label: "Count of Similar Films", pretty: true)
Advanced Correlations
# Rank within rating category (before window functions)
rank_in_category = SelectoKino.SubselectBuilder.correlated(%{
select: "COUNT(*) + 1",
from: "films f2",
where: %{
rating: {:parent, "rating"},
rental_rate: {:gt, {:parent, "rental_rate"}}
}
})
# Percentage of films in same category with lower price
percentile_in_category = SelectoKino.SubselectBuilder.correlated(%{
select: "ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM films f3 WHERE f3.rating = f1.rating), 2)",
from: "films f2",
where: %{
rating: {:parent, "rating"},
rental_rate: {:lt, {:parent, "rental_rate"}}
}
})
IO.inspect([
rank: rank_in_category,
percentile: percentile_in_category
], pretty: true)
5. ANY and ALL Operators
Using subselects with ANY and ALL for complex comparisons:
# Films cheaper than ANY film from 2006
cheaper_than_any_2006 = %{
operator: :any,
comparison: "<",
subselect: SelectoKino.SubselectBuilder.list(%{
select: "rental_rate",
from: "films",
where: %{release_year: 2006}
})
}
# Films more expensive than ALL PG films
more_than_all_pg = %{
operator: :all,
comparison: ">",
subselect: SelectoKino.SubselectBuilder.list(%{
select: "rental_rate",
from: "films",
where: %{rating: "PG"}
})
}
IO.inspect([
any_example: cheaper_than_any_2006,
all_example: more_than_all_pg
], pretty: true)
6. Nested Subselects
Subselects within subselects for complex logic:
# Films in the top 10% by rental rate within their rating
top_10_percent = %{
type: :scalar,
outer_query: "rental_rate >",
subselect: SelectoKino.SubselectBuilder.scalar(%{
select: "percentile_cont(0.9) WITHIN GROUP (ORDER BY rental_rate)",
from: "films f2",
where: %{
rating: {:parent, "rating"}
}
})
}
# Films with more inventory than average for popular films
above_avg_inventory = %{
type: :scalar,
comparison: ">",
subselect: SelectoKino.SubselectBuilder.scalar(%{
select: "AVG(inv_count)",
from: "(SELECT film_id, COUNT(*) as inv_count FROM inventory GROUP BY film_id) inv_summary",
where: %{
film_id: {:in,
SelectoKino.SubselectBuilder.list(%{
select: "film_id",
from: "rental JOIN inventory ON rental.inventory_id = inventory.id",
where: %{rental_date: {:gte, "CURRENT_DATE - INTERVAL '90 days'"}},
distinct: true
})
}
}
})
}
IO.inspect([
top_10_percent: top_10_percent,
above_avg_inventory: above_avg_inventory
], pretty: true)
7. Validation and Error Handling
The builder includes validation:
# Test validation with invalid configurations
test_configs = [
# Valid scalar subselect
%{valid: SelectoKino.SubselectBuilder.validate(%{
type: :scalar,
select: "MAX(rental_rate)",
from: "films"
})},
# Missing FROM clause
%{missing_from: SelectoKino.SubselectBuilder.validate(%{
type: :scalar,
select: "COUNT(*)",
from: nil
})},
# Missing correlation for correlated type
%{missing_correlation: SelectoKino.SubselectBuilder.validate(%{
type: :correlated,
select: "AVG(price)",
from: "products",
correlation: nil
})},
# EXISTS doesn't need SELECT
%{exists_valid: SelectoKino.SubselectBuilder.validate(%{
type: :exists,
from: "orders",
where: %{customer_id: {:parent, "id"}}
})}
]
Enum.each(test_configs, fn {label, result} ->
case result do
{:ok, _} -> IO.puts("✓ #{label}: Valid")
{:error, errors} -> IO.puts("✗ #{label}: #{inspect(errors)}")
end
end)
8. Integration with Selecto
Convert subselect configurations to Selecto specifications:
# Example configurations ready for Selecto
configs = [
# Scalar for WHERE clause
SelectoKino.SubselectBuilder.scalar(%{
select: "AVG(rental_rate)",
from: "films"
}),
# List for IN clause
SelectoKino.SubselectBuilder.list(%{
select: "id",
from: "inventory",
where: %{store_id: 1},
distinct: true
}),
# EXISTS for filtering
SelectoKino.SubselectBuilder.exists(%{
from: "rental r JOIN inventory i ON r.inventory_id = i.id",
where: %{
"i.film_id": {:parent, "id"},
"r.rental_date": {:gte, "CURRENT_DATE - INTERVAL '30 days'"}
}
})
]
# Convert to Selecto specs
specs = Enum.map(configs, fn config ->
SelectoKino.SubselectBuilder.to_selecto_spec(config)
end)
IO.puts("Selecto Specifications:")
Enum.each(specs, fn spec ->
IO.inspect(spec, pretty: true)
end)
# Usage with Selecto:
# selecto
# |> Selecto.where({:rental_rate, :>, {:subselect, specs |> Enum.at(0)}})
# |> Selecto.where({:id, :in, {:subselect, specs |> Enum.at(1)}})
# |> Selecto.where({:exists, specs |> Enum.at(2)})
# |> Selecto.execute()
Summary
SelectoKino’s Subselect support provides:
-
Four Subselect Types:
- Scalar (single value)
- List (multiple values for IN/NOT IN)
- EXISTS (row existence check)
- Correlated (references outer query)
-
Interactive Builder - Visual configuration without SQL knowledge
-
Correlation Support - Link inner and outer queries
-
Complex Operators - ANY, ALL, IN, NOT IN, EXISTS, NOT EXISTS
-
Validation - Catch errors before execution
-
SQL Preview - See generated SQL before running
-
Selecto Integration - Seamless use in Selecto queries
This makes it easy to build sophisticated nested queries for complex business logic without manually writing SQL.