Master SELECT Queries
Complete guide to querying data in Cassandra - WHERE clauses, filtering, ordering, LIMIT, aggregations, and performance optimization!
๐ฏ SELECT in Cassandra
CRITICAL: SELECT is NOT Like SQL!
Cassandra's SELECT has SEVERE restrictions compared to SQL. You MUST query by partition key, and most WHERE clauses won't work without proper indexes or ALLOW FILTERING!
โ ๏ธ The Golden Rule:
SELECT queries MUST include the partition key (or use secondary indexes/materialized views)
Without partition key = FULL CLUSTER SCAN = Performance disaster!
โ What Works Well
- Query by partition key
- Query with clustering columns (in order)
- LIMIT and pagination
- ORDER BY (if matches clustering)
- Simple aggregations (COUNT, SUM)
โ What Doesn't Work
- JOINs (not supported!)
- Complex WHERE without index
- GROUP BY (limited)
- Subqueries
- OR conditions
๐ Basic SELECT Syntax
Complete SELECT Syntax
FROM table_name
[WHERE condition]
[ORDER BY clustering_column [ASC|DESC]]
[LIMIT number]
[ALLOW FILTERING];
-- Aggregations
SELECT COUNT(*), SUM(column), AVG(column), MIN(column), MAX(column)
FROM table_name
[WHERE partition_key = ?];
Simple Examples
SELECT * FROM users WHERE user_id = uuid('...');
-- Select specific columns
SELECT username, email FROM users WHERE user_id = ?;
-- Count rows
SELECT COUNT(*) FROM users WHERE user_id = ?;
-- With LIMIT
SELECT * FROM posts WHERE user_id = ? LIMIT 10;
๐ How SELECT Query Works
๐ WHERE Clause Rules
WHERE Clause Restrictions
Cassandra's WHERE clause is MUCH more limited than SQL. You must follow specific rules or queries will fail!
โ Valid WHERE Clauses
WHERE user_id = ?
-- โ Partition + clustering (in order)
WHERE user_id = ?
AND post_date = '2025-01-15'
-- โ With range on last clustering
WHERE user_id = ?
AND post_date > '2025-01-01'
โ Invalid WHERE Clauses
WHERE username = 'alice'
ERROR!
-- โ Skipping clustering column
WHERE user_id = ?
AND post_content LIKE '%hello%'
ERROR!
WHERE Clause Rules Explained
| Rule | Explanation | Example |
|---|---|---|
| Rule 1 | MUST include partition key | WHERE user_id = ? |
| Rule 2 | Clustering columns in LEFT-TO-RIGHT order | Can't use month without year |
| Rule 3 | Range queries ONLY on last clustering column | WHERE user_id=? AND date > ? |
| Rule 4 | Equality (=) before ranges (>, <) | Can't have year > X AND month = Y |
| Rule 5 | No OR conditions | Must use IN or multiple queries |
Complete WHERE Examples
CREATE TABLE user_posts (
user_id UUID,
post_year INT,
post_month INT,
post_id UUID,
content TEXT,
PRIMARY KEY (user_id, post_year, post_month, post_id)
);
-- โ VALID: Partition key only
SELECT * FROM user_posts
WHERE user_id = ?;
-- โ VALID: Partition + first clustering
SELECT * FROM user_posts
WHERE user_id = ? AND post_year = 2025;
-- โ VALID: All in order with range on last
SELECT * FROM user_posts
WHERE user_id = ?
AND post_year = 2025
AND post_month = 1
AND post_id > ?;
-- โ INVALID: Skipped post_year
SELECT * FROM user_posts
WHERE user_id = ? AND post_month = 1;
// ERROR: Cannot skip clustering columns!
-- โ INVALID: Range on non-last clustering
SELECT * FROM user_posts
WHERE user_id = ?
AND post_year > 2020
AND post_month = 1;
// ERROR: Range must be on LAST clustering column!
โก ALLOW FILTERING - Dangerous!
ALLOW FILTERING Can Kill Your Cluster!
ALLOW FILTERING bypasses normal query restrictions by scanning ALL data. This causes FULL TABLE SCANS which can:
- ๐ Timeout queries
- ๐ Crash nodes (out of memory)
- ๐ Slow down entire cluster
- ๐ Block other queries
What is ALLOW FILTERING?
SELECT * FROM users
WHERE username = 'alice';
ERROR: Cannot execute this query as it might involve data filtering
and thus may have unpredictable performance. Use ALLOW FILTERING.
-- With ALLOW FILTERING (works but DANGEROUS)
SELECT * FROM users
WHERE username = 'alice'
ALLOW FILTERING;
// โ ๏ธ Scans EVERY row in EVERY partition!
// If table has 10M rows = reads 10M rows!
Better Alternatives to ALLOW FILTERING
- Redesign table: Make the queried column part of primary key
- Secondary Index: Create index on the column
- Materialized View: Create denormalized view
- Application-side filtering: Read by partition key, filter in code
๐ ORDER BY - Clustering Order Only!
ORDER BY is Extremely Limited
You can ONLY ORDER BY clustering columns, and it must match the table's defined clustering order (or be reversed)!
ORDER BY Rules
CREATE TABLE user_posts (
user_id UUID,
post_date TIMESTAMP,
content TEXT,
PRIMARY KEY (user_id, post_date)
) WITH CLUSTERING ORDER BY (post_date DESC);
-- โ Default order (matches clustering order DESC)
SELECT * FROM user_posts
WHERE user_id = ?;
// Returns newest posts first automatically
-- โ Explicit DESC (same as default)
SELECT * FROM user_posts
WHERE user_id = ?
ORDER BY post_date DESC;
-- โ Reverse order (ASC when table is DESC)
SELECT * FROM user_posts
WHERE user_id = ?
ORDER BY post_date ASC;
// Returns oldest posts first
-- โ INVALID: Can't order by non-clustering column
SELECT * FROM user_posts
WHERE user_id = ?
ORDER BY content;
ERROR! Cannot order by non-clustering column
ORDER BY Best Practices
- Choose clustering order carefully: Pick DESC if you'll usually want newest first
- Don't specify ORDER BY if using default: Saves typing, same result
- Reversing is cheap: Reading backwards is just as fast as forwards
- Can't order by regular columns: Redesign table or use materialized view
๐ข LIMIT & Pagination
LIMIT Basics
SELECT * FROM user_posts
WHERE user_id = ?
LIMIT 10;
-- Get first 100 rows
SELECT * FROM user_posts
WHERE user_id = ?
LIMIT 100;
-- LIMIT is applied AFTER filtering
SELECT * FROM user_posts
WHERE user_id = ?
AND post_date > '2025-01-01'
LIMIT 20;
Pagination with Paging State
SELECT * FROM user_posts
WHERE user_id = ?
LIMIT 10;
// Driver returns: results + paging_state token
// paging_state = "eyJjb2x1bW4...encrypted token..."
-- Page 2: Pass paging_state to get next 10
// Application uses driver method:
// session.execute(query, paging_state=previous_paging_state)
-- โ ๏ธ CQL doesn't have OFFSET!
-- Must use paging_state from driver
Important LIMIT Notes
- No OFFSET: Cassandra doesn't support OFFSET - must use paging state
- Paging state is opaque: Don't parse or manipulate it - treat as black box
- Can't jump to page N: Must page through sequentially (1โ2โ3โ...)
- Stateless pagination: Store paging_state in UI/cookie to continue
๐ Aggregation Functions
Available Aggregation Functions
SELECT COUNT(*) FROM user_posts
WHERE user_id = ?;
-- SUM: Total of numeric column
SELECT SUM(views) FROM user_posts
WHERE user_id = ?;
-- AVG: Average value
SELECT AVG(likes) FROM user_posts
WHERE user_id = ?;
-- MIN & MAX: Minimum and maximum
SELECT MIN(created_at), MAX(created_at)
FROM user_posts
WHERE user_id = ?;
-- Multiple aggregations together
SELECT COUNT(*), SUM(views), AVG(likes)
FROM user_posts
WHERE user_id = ?;
Aggregation Limitations
- No GROUP BY (usually): Aggregations work on entire result set, not groups
- Must include partition key: Can't aggregate across all partitions efficiently
- COUNT(*) can be expensive: Counts every row in partition
- Per-partition only: Can't easily get global aggregations
| Function | Description | Example | Performance |
|---|---|---|---|
COUNT(*) |
Count all rows | How many posts does user have? | โ ๏ธ Can be slow on large partitions |
SUM(col) |
Sum numeric values | Total views for user's posts | โ ๏ธ Reads all rows |
AVG(col) |
Average value | Average likes per post | โ ๏ธ Reads all rows |
MIN(col) |
Minimum value | Earliest post date | โ Fast if clustering column |
MAX(col) |
Maximum value | Latest post date | โ Fast if clustering column |
๐ก Real-World SELECT Examples
Example 1: Social Media - User Feed
Requirement: Show user's latest 20 posts with pagination
CREATE TABLE user_timeline (
user_id UUID,
post_id TIMEUUID,
content TEXT,
likes INT,
PRIMARY KEY (user_id, post_id)
) WITH CLUSTERING ORDER BY (post_id DESC);
-- Query: Get latest 20 posts
SELECT post_id, content, likes
FROM user_timeline
WHERE user_id = ?
LIMIT 20;
-- Query: Get posts older than specific post_id (pagination)
SELECT post_id, content, likes
FROM user_timeline
WHERE user_id = ?
AND post_id < ? -- "Load more" pagination
LIMIT 20;
-- Query: Count total posts
SELECT COUNT(*) FROM user_timeline
WHERE user_id = ?;
Example 2: E-commerce - Order History
Requirement: Find user's orders from last 30 days
CREATE TABLE orders_by_user (
user_id UUID,
order_date DATE,
order_id UUID,
total DECIMAL,
status TEXT,
PRIMARY KEY (user_id, order_date, order_id)
) WITH CLUSTERING ORDER BY (order_date DESC);
-- Query: Last 30 days of orders
SELECT * FROM orders_by_user
WHERE user_id = ?
AND order_date >= '2024-12-01'
AND order_date <= '2024-12-31';
-- Query: Total spent in December
SELECT SUM(total) AS total_spent
FROM orders_by_user
WHERE user_id = ?
AND order_date >= '2024-12-01'
AND order_date <= '2024-12-31';
-- Query: Orders with specific status (requires filtering)
SELECT * FROM orders_by_user
WHERE user_id = ?
AND order_date >= '2024-12-01'
AND status = 'shipped'
ALLOW FILTERING; -- โ ๏ธ Only if date range is small
Example 3: IoT - Sensor Data Query
Requirement: Query sensor readings for specific time range
CREATE TABLE sensor_data (
sensor_id UUID,
reading_date DATE,
reading_time TIMESTAMP,
temperature FLOAT,
humidity FLOAT,
PRIMARY KEY ((sensor_id, reading_date), reading_time)
);
-- Query: Today's readings
SELECT * FROM sensor_data
WHERE sensor_id = ?
AND reading_date = '2025-01-15';
-- Query: Readings between specific times
SELECT * FROM sensor_data
WHERE sensor_id = ?
AND reading_date = '2025-01-15'
AND reading_time >= '2025-01-15 08:00:00'
AND reading_time <= '2025-01-15 12:00:00';
-- Query: Average temperature today
SELECT AVG(temperature) AS avg_temp
FROM sensor_data
WHERE sensor_id = ?
AND reading_date = '2025-01-15';
Example 4: Gaming - Leaderboard Query
Requirement: Get top 100 players by score
CREATE TABLE leaderboard (
game_id UUID,
season INT,
score BIGINT,
player_id UUID,
player_name TEXT,
PRIMARY KEY ((game_id, season), score, player_id)
) WITH CLUSTERING ORDER BY (score DESC);
-- Query: Top 100 players
SELECT player_name, score
FROM leaderboard
WHERE game_id = ?
AND season = 2025
LIMIT 100;
-- Query: Players with score > 1000
SELECT player_name, score
FROM leaderboard
WHERE game_id = ?
AND season = 2025
AND score >= 1000;
-- Query: Player count above 1000
SELECT COUNT(*) FROM leaderboard
WHERE game_id = ?
AND season = 2025
AND score >= 1000;
โก Performance Optimization Tips
โ Fast Queries
- โ Always include partition key
- โ Use clustering columns in order
- โ Use LIMIT to reduce data transfer
- โ Select only needed columns
- โ Use prepared statements
- โ Query single partition
โ Slow Queries
- โ Missing partition key
- โ ALLOW FILTERING on large tables
- โ SELECT * without LIMIT
- โ COUNT(*) on huge partitions
- โ Querying non-indexed columns
- โ Multiple partition reads
Performance Best Practices
- Design for queries: Create tables based on how you'll query, not data relationships
- Use prepared statements: 10-100x faster than regular statements
- Specify columns: Don't use SELECT * in production
- Limit results: Always use LIMIT to prevent huge result sets
- Avoid ALLOW FILTERING: Redesign table or use secondary index instead
- Monitor partition sizes: Keep partitions under 100MB
- Use IN sparingly: IN queries can hit multiple nodes
- Batch smartly: Only batch writes to same partition
Query Performance Comparison
| Query Type | Performance | Reason |
|---|---|---|
| WHERE partition_key = ? | โกโกโก FAST (~1-5ms) | Single node, single partition |
| + clustering columns | โกโกโก FAST (~1-10ms) | Filters within partition |
| + LIMIT 10 | โกโกโก FAST (~1-5ms) | Stops after 10 rows |
| IN (pk1, pk2, pk3) | โ ๏ธ MEDIUM (~10-50ms) | Multiple partitions/nodes |
| ALLOW FILTERING (small) | โ ๏ธ SLOW (100ms-1s) | Scans partition |
| ALLOW FILTERING (large) | ๐ VERY SLOW (seconds+) | Full table scan |
| No partition key | ๐ DISASTER (timeout) | Scans entire cluster |
Troubleshooting Slow Queries
If your SELECT query is slow:
- Check logs: Look for "Cannot execute this query" warnings
- Enable tracing: Use TRACING ON to see execution details
- Check partition size: Large partitions = slow reads
- Review data model: Might need denormalized table
- Add secondary index: If querying by non-PK column frequently
- Use materialized view: For complex query patterns
Responsive Ad