Query Patterns
This document catalogs the actual queries the Android app uses and explains how the database schema is designed to support them efficiently. Every query shown here is extracted from the live DAO files.
Overview
The database schema is optimized for these primary access patterns:
- Chronological Browse - Show all shows by date (newest first)
- Date Filtering - Shows by year, month, or date range
- Location Browse - Shows by venue, city, or state
- Full-Text Search - Search any show attribute via FTS4
- Navigation - Next/previous show for playback continuity
- Recording Queries - Get best recording, filter by quality
- Library Management - User's saved shows with pins
- Recent History - Recently played shows
1. Browse Patterns
1.1 Chronological Browse (All Shows)
Use Case: Home screen default view, browse all shows
Query:
SELECT * FROM shows ORDER BY date DESC
Code Location: ShowDao.kt:20-21
Indexes Used:
- idx_shows_date - Fast sorting by date
Performance: O(log N) with index
Variations:
-- With limit for pagination
SELECT * FROM shows ORDER BY date DESC LIMIT 20
Code Location: ShowDao.kt:71-72
UI Context: Main show list, "Latest Shows" section
1.2 Shows by Year
Use Case: "1977 Shows" - user browsing by year
Query:
SELECT * FROM shows WHERE year = :year ORDER BY date
Code Location: ShowDao.kt:36-37
Indexes Used:
- idx_shows_year - Fast filter by year
- idx_shows_date - Sort filtered results chronologically
Performance: O(log N + k) where k = shows in year
Example: getShowsByYear(1977) returns all ~100 shows from 1977
UI Context: Year filter button, decade browser
1.3 Shows by Year-Month
Use Case: "May 1977" - more granular browsing
Query:
SELECT * FROM shows WHERE yearMonth = :yearMonth ORDER BY date
Code Location: ShowDao.kt:39-40
Indexes Used:
- idx_shows_yearMonth - Composite index for month-level filtering
Performance: O(log N + k) where k = shows in month
Example: getShowsByYearMonth("1977-05") returns ~8 shows from May 1977
UI Context: Month drill-down, calendar view
1.4 Shows by Exact Date
Use Case: Multiple shows on same day (common for festivals/runs)
Query:
SELECT * FROM shows WHERE date = :date ORDER BY showSequence
Code Location: ShowDao.kt:42-43
Indexes Used:
- idx_shows_date - Exact date lookup
Performance: O(log N + k) where k = shows on date (typically 1-3)
Example: getShowsByDate("1977-05-08") returns Cornell '77
Note: showSequence handles multiple shows per day (first show = 1, second = 2)
UI Context: Date picker, "on this day" feature
1.5 Date Range Browse
Use Case: "Spring 1977 Tour" - tour date ranges
Query:
SELECT * FROM shows
WHERE date >= :startDate AND date <= :endDate
ORDER BY date
Code Location: ShowDao.kt:45-46
Indexes Used:
- idx_shows_date - Range scan
Performance: O(log N + k) where k = shows in range
Example: getShowsInDateRange("1977-04-22", "1977-06-09") returns entire Spring '77 tour
UI Context: Tour collections, date range picker
1.6 "On This Day" Browse
Use Case: "All shows played on May 8th" (across all years)
Query:
SELECT * FROM shows
WHERE month = :month
AND SUBSTR(date, 9, 2) = PRINTF('%02d', :day)
AND recordingCount > 0
ORDER BY year
Code Location: ShowDao.kt:74-75
Indexes Used:
- idx_shows_month - Filter by month first
- Manual SUBSTR for day matching (no index, but small result set)
Performance: O(N) for SUBSTR, but acceptable (only 30-40 days per month to scan)
Example: getShowsForDate(5, 8) returns all May 8th shows (Cornell '77, others)
UI Context: "Today in Dead history" feature
Why No Day Index: Date part extraction doesn't benefit from index; month filter reduces scan significantly.
2. Location Browse Patterns
2.1 Shows by Venue Name
Use Case: "All Fillmore East shows"
Query:
SELECT * FROM shows
WHERE venueName LIKE '%' || :venueName || '%'
ORDER BY date
Code Location: ShowDao.kt:50-51
Indexes Used: - None (LIKE with wildcard prefix can't use index)
Performance: O(N) - full table scan
Why No Index: %pattern% requires scanning all rows; prefix-only LIKE 'pattern%' could use index but defeats flexible search
Example: getShowsByVenue("Fillmore") matches "Fillmore East", "Fillmore West"
UI Context: Venue browser, venue detail page
Alternative: Use FTS4 search for better performance on venue queries
2.2 Shows by City
Use Case: "All San Francisco shows"
Query:
SELECT * FROM shows WHERE city = :city ORDER BY date DESC
Code Location: ShowDao.kt:53-54
Indexes Used:
- idx_shows_city - Exact city match
Performance: O(log N + k) where k = shows in city
Example: getShowsByCity("San Francisco") returns ~200 shows
UI Context: Location browser, map view
2.3 Shows by State
Use Case: "All California shows"
Query:
SELECT * FROM shows WHERE state = :state ORDER BY date DESC
Code Location: ShowDao.kt:56-57
Indexes Used:
- idx_shows_state - Exact state match
Performance: O(log N + k) where k = shows in state
Example: getShowsByState("CA") returns ~500 shows
UI Context: State filter, regional browser
3. Full-Text Search Pattern
3.1 FTS4 Text Search
Use Case: User types "Cornell 1977" or "5-8-77" in search box
Query:
SELECT showId FROM show_search
WHERE show_search MATCH :query
Code Location: ShowSearchDao.kt:36-37
Indexes Used: - FTS4 inverted index (automatic)
Performance: O(log T) where T = token count, not row count
BM25 Ranking: Results ordered by relevance automatically
Example Queries:
- searchShows("Cornell") → ["1977-05-08-barton-hall-cornell-u-ithaca-ny-usa", ...]
- searchShows("5-8-77") → Same result (date variation indexed)
- searchShows("Ithaca 1977") → Cornell '77 + other Ithaca shows
Then Fetch Full Data:
val showIds = showSearchDao.searchShows(query)
val shows = showDao.getShowsByIds(showIds)
Code Location: ShowDao.kt:29-30
UI Context: Search bar, global show search
Why Two Queries: FTS4 returns IDs with ranking; must fetch full show data separately
3.2 Reactive Search (Live Updates)
Query:
SELECT showId FROM show_search
WHERE show_search MATCH :query
Code Location: ShowSearchDao.kt:43-44 (Flow variant)
Returns: Flow<List<String>> that updates automatically
Usage: ViewModel observes Flow, UI updates reactively as user types
3.3 Song Search (Fallback)
Use Case: Find shows by song name (when FTS4 not sufficient)
Query:
SELECT * FROM shows
WHERE songList LIKE '%' || :songName || '%'
ORDER BY date DESC
Code Location: ShowDao.kt:60-64
Indexes Used: - None (wildcard LIKE requires scan)
Performance: O(N) - full table scan
Why Exists: Fallback for detailed song queries; FTS4 handles most cases
Example: getShowsBySong("Scarlet Begonias") returns all shows with that song
Note: songList is comma-separated: "Scarlet Begonias,Fire on the Mountain,..."
4. Navigation Patterns
4.1 Next Show (Chronological)
Use Case: Playback ends, user taps "Next Show" button
Query:
SELECT * FROM shows
WHERE date > :currentDate
ORDER BY date ASC
LIMIT 1
Code Location: ShowDao.kt:78-79
Indexes Used:
- idx_shows_date - Range scan + sort
Performance: O(log N) - single row lookup
Example: After playing 1977-05-08, getNextShowByDate("1977-05-08") returns 1977-05-09
UI Context: "Next Show" button, autoplay
4.2 Previous Show (Chronological)
Use Case: User taps "Previous Show" button
Query:
SELECT * FROM shows
WHERE date < :currentDate
ORDER BY date DESC
LIMIT 1
Code Location: ShowDao.kt:81-82
Indexes Used:
- idx_shows_date - Range scan + reverse sort
Performance: O(log N) - single row lookup
Example: From 1977-05-08, getPreviousShowByDate("1977-05-08") returns 1977-05-07
UI Context: "Previous Show" button
5. Recording Queries
5.1 All Recordings for Show
Use Case: Show detail page - list all available recordings
Query:
SELECT * FROM recordings
WHERE show_id = :showId
ORDER BY rating DESC
Code Location: RecordingDao.kt:22-23
Indexes Used:
- idx_recordings_show_id_rating - Composite index (perfect for this query)
Performance: O(log N + k) where k = recordings for show (typically 6-8)
Example: getRecordingsForShow("1977-05-08-...") returns ~8 recordings sorted by quality
UI Context: Recording selector dropdown
5.2 Best Recording for Show
Use Case: Auto-select best quality recording on show load
Query:
SELECT * FROM recordings
WHERE show_id = :showId
ORDER BY rating DESC
LIMIT 1
Code Location: RecordingDao.kt:25-26
Indexes Used:
- idx_recordings_show_id_rating - Composite index (optimized)
Performance: O(log N) - single best record
Example: getBestRecordingForShow("1977-05-08-...") returns Miller SBD (highest rated)
UI Context: Default recording selection
5.3 Filter by Source Type
Use Case: "Show me only soundboards"
Query:
SELECT * FROM recordings
WHERE source_type = :sourceType
ORDER BY rating DESC
Code Location: RecordingDao.kt:28-29
Indexes Used:
- idx_recordings_source_type - Filter by type
- Sort by rating (may require temp table)
Performance: O(log N + k) where k = recordings of type
Example: getRecordingsBySourceType("SBD") returns all soundboards
UI Context: Source type filter button
5.4 Top Rated Recordings (Global)
Use Case: "Best recordings ever" discovery feature
Query:
SELECT * FROM recordings
WHERE rating > :minRating AND review_count >= :minReviews
ORDER BY rating DESC, review_count DESC
LIMIT :limit
Code Location: RecordingDao.kt:47-57
Indexes Used:
- idx_recordings_rating - Primary sort
- Secondary sort by review_count (in-memory)
Performance: O(N) for filters + O(k log k) for sort where k = qualifying recordings
Example: getTopRatedRecordings(minRating=4.0, minReviews=10, limit=50) returns top 50 legendary recordings
UI Context: "Top Recordings" discovery page
5.5 Recording Statistics by Source Type
Use Case: Analytics, debugging
Query:
SELECT
source_type,
COUNT(*) as count,
AVG(rating) as avg_rating,
AVG(review_count) as avg_reviews
FROM recordings
WHERE source_type IS NOT NULL
GROUP BY source_type
ORDER BY count DESC
Code Location: RecordingDao.kt:62-73
Indexes Used:
- idx_recordings_source_type - GROUP BY optimization
Performance: O(N) - full table scan for aggregation
Example Output:
AUD: 12000 recordings, avg_rating=3.5, avg_reviews=15
SBD: 5000 recordings, avg_rating=4.2, avg_reviews=25
MATRIX: 2000 recordings, avg_rating=4.0, avg_reviews=20
UI Context: Admin/debug screen
6. Library Management Patterns
6.1 Get All Library Shows
Use Case: User's library page
Query:
SELECT * FROM library_shows
ORDER BY isPinned DESC, addedToLibraryAt DESC
Code Location: LibraryDao.kt:38-39
Indexes Used:
- idx_library_shows_is_pinned - Sort pinned first
- idx_library_shows_added_at - Sort by added date
Performance: O(log N + k) where k = library size (typically 10-100)
Sort Order: Pinned shows first, then chronological by add date
Example Output:
📌 Cornell '77 (pinned, added 2 days ago)
📌 Veneta '72 (pinned, added 1 week ago)
Red Rocks '78 (added yesterday)
Nassau '79 (added 3 days ago)
UI Context: Library tab
6.2 Get Pinned Shows Only
Query:
SELECT * FROM library_shows
WHERE isPinned = 1
ORDER BY addedToLibraryAt DESC
Code Location: LibraryDao.kt:41-42
Indexes Used:
- idx_library_shows_is_pinned - Filter pinned
Performance: O(log N + k) where k = pinned count (typically 0-10)
UI Context: Quick access widget, favorites section
6.3 Check if Show in Library
Use Case: Display heart icon (filled/unfilled)
Query:
SELECT EXISTS(SELECT 1 FROM library_shows WHERE showId = :showId)
Code Location: LibraryDao.kt:55-56
Indexes Used:
- PRIMARY KEY on showId - O(1) lookup
Performance: O(1)
Returns: Boolean (true if in library)
UI Context: Heart icon on every show card
6.4 Check if Show is Pinned
Query:
SELECT EXISTS(SELECT 1 FROM library_shows WHERE showId = :showId AND isPinned = 1)
Code Location: LibraryDao.kt:61-62
Indexes Used: - PRIMARY KEY + isPinned filter
Performance: O(1)
UI Context: Pin icon display
6.5 Toggle Pin Status
Use Case: User taps pin icon
Query:
UPDATE library_shows SET isPinned = :isPinned WHERE showId = :showId
Code Location: LibraryDao.kt:68-69
Performance: O(1) via PRIMARY KEY
Note: Must sync with shows.is_in_library flag in transaction
6.6 Update Library Notes
Use Case: User adds personal note to show
Query:
UPDATE library_shows SET libraryNotes = :notes WHERE showId = :showId
Code Location: LibraryDao.kt:71-72
Performance: O(1) via PRIMARY KEY
Example: updateLibraryNotes(showId, "First show I attended! Amazing Scarlet > Fire.")
6.7 Library Statistics
Query:
SELECT COUNT(*) FROM library_shows
Code Location: LibraryDao.kt:75-76
Performance: Fast (simple count)
UI Context: Display "42 shows in library"
7. Recent History Patterns
7.1 Get Recently Played Shows
Use Case: "Recently played" section on home screen
Query:
SELECT * FROM recent_shows
ORDER BY last_played_timestamp DESC
LIMIT 8
Code Location: RecentShowDao.kt:26-30
Indexes Used:
- idx_recent_shows_last_played DESC - Perfect index for this query
Performance: O(log N + k) where k = limit (typically 8-10)
Example Output (show IDs):
1977-05-08-... (played 2 hours ago)
1972-08-27-... (played yesterday)
1979-11-05-... (played 3 days ago)
Then: Fetch full show data via ShowDao.getShowsByIds()
UI Context: Home screen "Recently played" carousel
7.2 Check if Show Played Recently
Query:
SELECT * FROM recent_shows WHERE showId = :showId
Code Location: RecentShowDao.kt:53-54
Indexes Used:
- PRIMARY KEY on showId - O(1)
Performance: O(1)
Usage: UPSERT logic (check exists before INSERT vs UPDATE)
7.3 Get Most Played Shows
Use Case: "Most played" analytics
Query:
SELECT * FROM recent_shows
ORDER BY totalPlayCount DESC, last_played_timestamp DESC
LIMIT 10
Code Location: RecentShowDao.kt:128-132
Indexes Used:
- No index on totalPlayCount (table is small, acceptable)
Performance: O(N log N) sort, but N is small (<100 typically)
Example Output:
Cornell '77: 12 plays
Veneta '72: 10 plays
Nassau '79: 8 plays
UI Context: User stats page
7.4 Shows Played in Time Range
Query:
SELECT * FROM recent_shows
WHERE last_played_timestamp BETWEEN :startTimestamp AND :endTimestamp
ORDER BY last_played_timestamp DESC
Code Location: RecentShowDao.kt:113-118
Indexes Used:
- idx_recent_shows_last_played - Range scan
Performance: O(log N + k) where k = shows in range
Example: getShowsPlayedInRange(lastWeekStart, today) returns this week's plays
UI Context: "This week's plays" analytics
7.5 Remove Show from Recent (Privacy)
Query:
DELETE FROM recent_shows WHERE showId = :showId
Code Location: RecentShowDao.kt:86-87
Performance: O(1) via PRIMARY KEY
UI Context: "Remove from recent" menu option
7.6 Clear Old Shows (Privacy/Storage)
Query:
DELETE FROM recent_shows WHERE last_played_timestamp < :cutoffTimestamp
Code Location: RecentShowDao.kt:141-142
Performance: O(N) scan (but table is small)
Usage: Privacy settings - "Clear plays older than 90 days"
8. Popular/Featured Patterns
8.1 Top Rated Shows
Use Case: "Best shows ever" discovery feature
Query:
SELECT * FROM shows
WHERE averageRating IS NOT NULL
ORDER BY averageRating DESC
LIMIT 20
Code Location: ShowDao.kt:68-69
Indexes Used:
- No dedicated index on averageRating (rare query)
Performance: O(N log N) sort (acceptable for discovery feature)
Example Output:
1977-05-08 Cornell (4.9★)
1972-08-27 Veneta (4.8★)
1977-05-09 Buffalo (4.7★)
UI Context: "Top Shows" discovery page
8.2 Most Recent Shows
Query:
SELECT * FROM shows ORDER BY date DESC LIMIT 20
Code Location: ShowDao.kt:71-72
Indexes Used:
- idx_shows_date - Fast reverse chronological
Performance: O(log N + k) where k = 20
UI Context: Home screen "Latest shows"
9. Collections Patterns
9.1 Get All Collections
Query:
SELECT * FROM dead_collections ORDER BY name ASC
Code Location: CollectionsDao.kt:24-25
Performance: O(N log N) where N = collections (20-50)
UI Context: Collections browser
9.2 Get Featured Collections
Query:
SELECT * FROM dead_collections ORDER BY totalShows DESC LIMIT 6
Code Location: CollectionsDao.kt:42-43
Indexes Used:
- idx_dead_collections_total_shows - Sort by size
Performance: O(log N + k) where k = 6
Example: Dick's Picks, Dave's Picks, Road Trips (largest collections)
UI Context: Home screen "Featured Collections"
9.3 Search Collections
Query:
SELECT * FROM dead_collections
WHERE name LIKE '%' || :query || '%'
OR description LIKE '%' || :query || '%'
ORDER BY name ASC
Code Location: CollectionsDao.kt:54-60
Performance: O(N) scan (acceptable for small table)
UI Context: Collection search bar
9.4 Get Collections by Tag
Query:
SELECT * FROM dead_collections WHERE primaryTag = :tag ORDER BY name ASC
Code Location: CollectionsDao.kt:65-66
Indexes Used:
- idx_dead_collections_primary_tag - Filter by tag
Performance: O(log N + k) where k = collections with tag
Example: getCollectionsByTag("era") returns era-based collections
UI Context: Tag filter tabs
9.5 Get Collections Containing Show
Query:
SELECT * FROM dead_collections
WHERE showIdsJson LIKE '%' || :showId || '%'
ORDER BY name ASC
Code Location: CollectionsDao.kt:113-118
Performance: O(N) scan with string matching (acceptable for small table)
Example: getCollectionsContainingShow("1977-05-08-...") returns "Dick's Picks Vol. 15"
UI Context: Show detail page - "Part of collections: ..."
Note: LIKE on JSON is inefficient but acceptable for read-only small dataset
10. Data Version Queries
10.1 Get Current Version
Query:
SELECT * FROM data_version_v2 WHERE id = 1
Code Location: DataVersionDao.kt:16-17
Performance: O(1) via PRIMARY KEY (singleton table)
Returns: Version info or NULL if no data imported
UI Context: About screen, update checker
10.2 Check if Data Exists
Query:
SELECT COUNT(*) > 0 FROM data_version_v2
Code Location: DataVersionDao.kt:26-27
Performance: Fast
Usage: First-run detection
Index Summary
This table shows which indexes support which query patterns:
| Index | Query Patterns Supported | Critical? |
|---|---|---|
idx_shows_date |
Chronological browse, navigation, date ranges | ✅ YES |
idx_shows_year |
Year filtering | ✅ YES |
idx_shows_yearMonth |
Month filtering | ✅ YES |
idx_shows_city |
Location browse | ✅ YES |
idx_shows_state |
Location browse | ✅ YES |
idx_recordings_show_id_rating |
Best recording, all recordings | ✅ YES |
idx_recordings_source_type |
Filter by source type | Moderate |
idx_recordings_rating |
Top rated recordings | Moderate |
idx_library_shows_is_pinned |
Library pins first | ✅ YES |
idx_library_shows_added_at |
Library chronological | ✅ YES |
idx_recent_shows_last_played DESC |
Recent history | ✅ YES |
FTS4 show_search |
Full-text search | ✅ YES |
Performance Notes
Fast Queries (< 10ms)
- Single show lookup by ID (PRIMARY KEY)
- Library membership check (PRIMARY KEY)
- Get best recording (composite index)
- Recent shows (indexed timestamp)
Moderate Queries (10-50ms)
- Year/month filtering (indexed, moderate result sets)
- Location filtering (indexed, variable result sets)
- FTS4 search (depends on query complexity)
Slow Queries (> 50ms)
- Venue LIKE search (full table scan)
- Song LIKE search (full table scan)
- Top rated shows (sort entire table)
- Collection searches (small table, acceptable)
Query Optimization Patterns
1. Composite Indexes
idx_recordings_show_id_rating covers both filter AND sort:
WHERE show_id = ? ORDER BY rating DESC
Single index handles entire query (no temp table needed).
2. Covering Indexes
FTS4 returns only showId, then fetch full data:
-- Step 1: FTS4 (fast)
SELECT showId FROM show_search WHERE ...
-- Step 2: Fetch full data
SELECT * FROM shows WHERE showId IN (...)
Two queries more efficient than JOINing FTS4 virtual table.
3. EXISTS for Boolean Checks
SELECT EXISTS(SELECT 1 FROM library_shows WHERE showId = ?)
Faster than SELECT COUNT(*) - stops at first match.
4. DESC Index for Recent Queries
INDEX idx_recent_shows_last_played DESC
Avoids reverse sort for ORDER BY timestamp DESC queries.
See Also
- Design Philosophy - Why these patterns
- Index Documentation - Index details
- FTS4 Search - Search implementation
- Data Flow - How queries fit into app flow