# Moose / Olap / Read Data Documentation – Python
## Included Files
1. moose/olap/read-data/read-data.mdx
## Querying Data
Source: moose/olap/read-data/read-data.mdx
Query OLAP tables using SQL with type safety
# Querying Data
Moose provides type-safe SQL querying for your `OlapTable` and `MaterializedView` instances. Use cases include:
- Building APIs to expose your data to client/frontend applications
- Building transformation pipelines inside your database with materialized views
## Querying with MooseClient
Use `MooseClient` to query data from existing tables and materialized views.
### Basic Querying
You can use a formatted string with `execute`:
```py filename="BasicQuerying.py"
from moose_lib import MooseClient
from app.UserTable import UserTable
client = MooseClient()
status = "active"
limit = 10
query = """
SELECT id, name, email
FROM {table}
WHERE status = {status}
LIMIT {limit}
"""
rows = client.query.execute(query, {"table": UserTable, "status": status, "limit": limit})
rows = client.query.execute(query)
```
This allows you to safely interpolate the table and column names while still using your Moose OlapTables and columns.
If you'd rather just use the raw ClickHouse python driver with server-side parameter binding, you can use `execute_raw`:
```py filename="BasicQuerying.py"
from moose_lib import MooseClient
client = MooseClient()
# Query existing table using execute_raw with explicit ClickHouse types
query = """
SELECT id, name, email
FROM users
WHERE status = {status:String}
LIMIT {limit:UInt32}
"""
rows = client.query.execute_raw(query, {
"status": "active",
"limit": 10
})
```
### Querying Materialized Views
You can use a formatted string with `execute`:
```py filename="QueryMaterializedView.py"
from moose_lib import MooseClient
client = MooseClient()
min_orders = 10
query = """
SELECT user_id, total_orders, average_order_value
FROM user_stats_view
WHERE total_orders > {min_orders}
ORDER BY average_order_value DESC
"""
rows = client.query.execute(query, {"min_orders": min_orders})
```
Use `execute_raw` with parameter binding:
```py filename="QueryMaterializedView.py"
from moose_lib import MooseClient
client = MooseClient()
min_orders = 10
# Query existing materialized view
query = """
SELECT user_id, total_orders, average_order_value
FROM user_stats_view
WHERE total_orders > {min_orders:UInt32}
ORDER BY average_order_value DESC
"""
rows = client.query.execute_raw(query, {"min_orders": min_orders})
```
## Select With Column and Table References
```py filename="TypedReferences.py"
from moose_lib import MooseClient
from app.UserTable import UserTable
client = MooseClient()
status = "active"
query = """
SELECT
{column}
FROM {table}
WHERE status = {status}
"""
rows = client.query.execute(query, {"column": UserTable.cols.id, "table": UserTable, "status": status})
```
```python copy
from moose_lib import MooseClient
client = MooseClient()
# Use parameter binding with explicit identifiers
query = """
SELECT
id,
name,
email
FROM {table: Identifier}
WHERE status = {status:String}
"""
rows = client.query.execute_raw(query, {"table": UserTable.name, "status": "active"})
```
## Filtering with WHERE Clauses
```py copy
from moose_lib import MooseClient
client = MooseClient()
status = "active"
start_date = "2024-01-01"
search_pattern = "%example%"
min_age = 18
max_age = 65
user_ids = [1, 2, 3, 4, 5]
# Multiple WHERE conditions
filter_query = """
SELECT id, name
FROM {table}
WHERE status = {status}
AND created_at > {start_date}
AND email ILIKE {search_pattern}
"""
# Using BETWEEN
range_query = """
SELECT * FROM {table}
WHERE age BETWEEN {min_age} AND {max_age}
"""
# Using IN
in_query = """
SELECT * FROM {table}
WHERE id IN {user_ids}
"""
# Execute examples
filter_rows = client.query.execute(filter_query, {"table": UserTable, "status": status, "startDate": start_date, "searchPattern": search_pattern})
range_rows = client.query.execute(range_query, {"table": UserTable, "minAge": min_age, "maxAge": max_age})
in_rows = client.query.execute(in_query, {"table": UserTable, "userIds": user_ids})
```
```py filename="WhereClauses.py"
from moose_lib import MooseClient
client = MooseClient()
# Multiple WHERE conditions
filter_query = """
SELECT id, name
FROM users
WHERE status = {status:String}
AND created_at > {startDate:DateTime}
AND email ILIKE {searchPattern:String}
"""
# Using BETWEEN
range_query = """
SELECT * FROM users
WHERE age BETWEEN {minAge:UInt32} AND {maxAge:UInt32}
"""
# Using IN with typed arrays
in_query = """
SELECT * FROM users
WHERE id IN {userIds:Array(UInt32)}
"""
# Execute examples
filter_rows = client.query.execute_raw(filter_query, {
"status": "active",
"startDate": "2024-01-01",
"searchPattern": "%example%"
})
range_rows = client.query.execute_raw(range_query, {
"minAge": 18,
"maxAge": 65
})
in_rows = client.query.execute_raw(in_query, {
"userIds": [1, 2, 3, 4, 5]
})
```
## Dynamic Query Building
Moose provides two distinct approaches for executing queries in Python. Choose the right one for your use case:
- Option 1: Use formatted strings with `execute`
- Option 2: Use `execute_raw` with parameter binding (lowest level of abstraction)
```py filename="execute.py"
from moose_lib import MooseClient
from pydantic import BaseModel, Field, validator
from typing import Optional
client = MooseClient()
# Example: Static query with validated parameters
def get_active_users(status: str, limit: int):
# Static table/column names, validated parameters
query = """
SELECT id, name, email
FROM {table}
WHERE status = {status}
LIMIT {limit}
"""
return client.query.execute(query, {"table": UserTable, "status": status, "limit": limit})
# Usage with validated input
active_users = get_active_users("active", 10)
class UserQueryParams(BaseModel):
status: str = Field(..., pattern=r"^(active|inactive|pending)$")
limit: int = Field(default=10, ge=1, le=1000)
def build_validated_query(params: UserQueryParams):
# All parameters are validated by Pydantic
query = """
SELECT id, name, email
FROM {table}
WHERE status = {status}
LIMIT {limit}
"""
return client.query.execute(query, {"table": UserTable, "status": params.status, "limit": params.limit})
```
```py filename="ParameterBinding.py"
from moose_lib import MooseClient
client = MooseClient()
# Example: Dynamic table and column selection with server-side parameter binding
def query_user_data(table_name: str, status_filter: str, limit: int):
# Dynamic identifiers in query structure, bound parameters for values
query = """
SELECT id, name, email
FROM {table_name:Identifier}
WHERE status = {status:String}
AND created_at > {startDate:DateTime}
LIMIT {limit:UInt32}
"""
return client.query.execute_raw(query, {
"table_name": table_name, # Bound parameter
"status": status_filter, # Bound parameter
"startDate": "2024-01-01T00:00:00", # Bound parameter
"limit": limit # Bound parameter
})
# Usage with different tables
users_data = query_user_data("users", "active", 10)
admins_data = query_user_data("admin_users", "pending", 5)
# Conditional WHERE clauses
def build_conditional_query(client: MooseClient, params: FilterParams):
conditions: list[str] = []
parameters: dict = {}
if params.min_age is not None:
conditions.append("age >= {minAge:UInt32}")
parameters["minAge"] = params.min_age
if params.status:
conditions.append("status = {status:String}")
parameters["status"] = params.status
if params.search_text:
conditions.append("(name ILIKE {searchPattern:String} OR email ILIKE {searchPattern:String})")
parameters["searchPattern"] = f"%{params.search_text}%"
query = "SELECT * FROM users"
if conditions:
query += " WHERE " + " AND ".join(conditions)
query += " ORDER BY created_at DESC"
return client.query.execute_raw(query, parameters)
```
## Building APIs
To build REST APIs that expose your data, see the [Bring Your Own API Framework documentation](/moose/app-api-frameworks) for comprehensive examples and patterns using Express, Koa, Fastify, or FastAPI.
## Common Pitfalls
## Performance Optimization
If your query is slower than expected, there are a few things you can check:
- If using filters, try to filter on a column that is defined in the `orderByFields` of the table
- For common queries, consider [creating a materialized view](/stack/olap/create-materialized-view) to pre-compute the result set
## Further Reading