I have a database of (Soccer) games, containing child tables of periods (e.g. first and second half), events (e.g. a goal, a caution), and locations (where you were prior to and during the game).
To display the parent Games table, I'm using a CursorLoader with the appropriate arguments like this:
    public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
    ...
    if ((mGamesDB.isOpen()) && (id == GAMES_CURSOR_ID)) {
        return createGamesCursorLoader();
    }
    return null;
}
    private Loader<Cursor> createGamesCursorLoader() {
    //Because we don't want to create a ContentProvider for now, we use the technique suggested here:
    //https://stackoverflow.com/questions/18326954/how-to-read-an-sqlite-db-in-android-with-a-cursorloader
    return new CursorLoader(getBaseContext(),null, GamesContract.Games.PROJECTION,
            null, null, GamesContract.Games.ORDER_BY) {
        @Override
        public Cursor loadInBackground() {
            if (mGamesDB.isOpen()) {
                return mGamesDB.query(
                    GamesContract.Games.TABLE_NAME,
                    GamesContract.Games.PROJECTION,
                    null, null,
                    null, null,
                    GamesContract.Games.ORDER_BY
                );
            }
            else return null;
        }
    };
}
That all works fine. However, once I start iterating through the Games cursor (when onLoadFinished is called), I need to create subqueries for Periods, Events, and Locations using the current GameID. So I do:
    private Game buildGameFromDB(final Cursor gameCursor) {
    if (!mGamesDB.isOpen() || (gameCursor == null) || gameCursor.isClosed() ) return null;
    final WatchGame game = new WatchGame(gameCursor.getString(GamesContract.Games.COLUMN_ID_INDEX),
            gameCursor.getLong(GamesContract.Games.COLUMN_ACTUAL_START_MILLIS_INDEX),
            gameCursor.getLong(GamesContract.Games.COLUMN_ACTUAL_END_MILLIS_INDEX),
            gameCursor.getInt(GamesContract.Games.COLUMN_HOME_TEAM_COLOR_INDEX),
            gameCursor.getInt(GamesContract.Games.COLUMN_AWAY_TEAM_COLOR_INDEX),
            gameCursor.getInt(GamesContract.Games.COLUMN_HOME_TEAM_SCORE_INDEX),
            gameCursor.getInt(GamesContract.Games.COLUMN_AWAY_TEAM_SCORE_INDEX));
    //FIXME: Ugly nested queries on the main UI thread
    final String[] periodsWhereArgs = {game.getmGameID()};
    final Cursor periodsCursor = mGamesDB.query(GamesContract.Periods.TABLE_NAME, GamesContract.Periods.PROJECTION,
                                                GamesContract.Periods.WHERE, periodsWhereArgs,
                                                null, null, GamesContract.Periods.ORDER_BY);
    while (periodsCursor.moveToNext()) {
        final Period period = new Period(
                periodsCursor.getInt(GamesContract.Periods.COLUMN_PERIOD_NUM_INDEX),
                periodsCursor.getLong(GamesContract.Periods.COLUMN_ACTUAL_START_MILLIS_INDEX),
                periodsCursor.getLong(GamesContract.Periods.COLUMN_ACTUAL_END_MILLIS_INDEX),
                periodsCursor.getFloat(GamesContract.Periods.COLUMN_START_BATTERY_PCT_INDEX),
                periodsCursor.getFloat(GamesContract.Periods.COLUMN_END_BATTERY_PCT_INDEX),
                periodsCursor.getString(GamesContract.Periods.COLUMN_GOOGLE_ACCOUNT_NAME_INDEX),
                periodsCursor.getInt(GamesContract.Periods.COLUMN_NUM_LOCATIONS_INDEX),
                periodsCursor.getInt(GamesContract.Periods.COLUMN_NUM_LOCATIONS_IN_FIT_INDEX),
                periodsCursor.getInt(GamesContract.Periods.COLUMN_CALORIES_INDEX),
                periodsCursor.getInt(GamesContract.Periods.COLUMN_STEPS_INDEX),
                periodsCursor.getInt(GamesContract.Periods.COLUMN_DISTANCE_METRES_INDEX),
                periodsCursor.getLong(GamesContract.Periods.COLUMN_WALKING_MILLIS_INDEX),
                periodsCursor.getLong(GamesContract.Periods.COLUMN_RUNNING_MILLIS_INDEX),
                periodsCursor.getLong(GamesContract.Periods.COLUMN_SPRINTING_MILLIS_INDEX)
        );
        game.addPeriod(period);
    }
    periodsCursor.close();
...
Although the number of games and periods won't be large (maybe 100s), there could be 50 events per game, and 2000 locations per game.
How can I do this more efficiently? Possibilities that occur to me are:
- A large multi-join query which I then have to sort through. I'm very comfortable with that type of SQL, assuming SQLite will handle it efficiently. I don't like this mostly because the periods, events, and locations and child tables so I'd effectively be denormalizing and creating a giant mess.
- Expanding my selectionArgs for periods, events etc. to be a dynamic list of 10 or 100 games I have
- Somehow improving the efficiency of what I have and turning these into Async queries
 
Any advice or pointers appreciated.
 
    