2017-09-09 49 views
2

我有一个(足球)游戏数据库,包含周期(例如第一个和第二个一半)的子表,事件(例如目标,警告)和位置(您先前的位置到和在比赛期间)。如何有效地嵌套SQLlite查询

要显示父游戏表,我使用的是CursorLoader,像这样适当的参数:

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; 
     } 
    }; 
} 

这一切工作正常。但是,一旦我开始迭代游戏游标(当调用onLoadFinished时),我需要使用当前的GameID为Periods,Events和Locations创建子查询。所以,我做的:

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(); 
... 

虽然游戏和期间的个数不会太大(也许100S),有可能是每场比赛的50个事件,和每场比赛2000点的位置。

我该如何更有效地做到这一点?发生在我的可能性是:

  1. 大型多连接查询,然后我不得不通过排序。我非常喜欢这种类型的SQL,假设SQLite将有效地处理它。我不喜欢这种情况,主要是因为时间段,事件,地点和子表,所以我实际上正在反规范化并造成巨大的混乱。
  2. 扩大我selectionArgs两个为周期,事件等须的10次或100的游戏的动态列表我有
  3. 不知何故改善我有效率和把它们转变成异步查询

任何意见或指针赞赏。

回答

1

您认为您正在运行N+1 SELECT problem,您正在执行许多查询,因此应用程序和数据库服务器之间的所有额外通信都会降低性能。

事实上,情况并非如此:SQLite是一个嵌入式数据库,所以没有单独的服务器,并且many small queries are just as efficient

但也有其他事情可以做,以加快查询速度:

  • 添加正确的索引: 列(S)被用于查找行应该被索引;详情请参阅Query Planning。 PRIMARY KEY或UNIQUE约束自动在其列上创建索引;对于其他栏目,你必须自己create the index(es)

    在这种情况下,周期表中的游戏ID应该有一个索引。

  • 加载较少的数据,即仅在实际需要时加载数据。 最简单的方法是去除游戏/句点对象,并直接从数据库运行UI。这需要改变整个应用程序的体系结构,如果你的对象实际上做的不仅仅是存储数据,它可能不可行。

请注意,无论您使用哪种类型的查询(N + 1或批处理或联合),上述两点都可以工作。

将数据库访问移动到单独的线程不会加速它,它只是允许用户在数据仍在加载时与UI进行交互。 (没有所有数据的用户界面是否有用是另一个问题。)另外,异步线程可以用于任何类型的查询。

+0

PS运行应用程序,然后查看日志会告诉你,如果任何索引是自动创建的,那么这些将成为创建永久索引的候选对象。 – MikeT

+0

哇,谢谢你清楚,有用的答案。关于“许多小问题同样高效”的观点非常重要。我想我会以这种方式进行,然后做一些分析。 此外,重新索引:我会看看确保正确的索引正在创建或自己做。 – Opus1217