【问题标题】:How to do nested SQLlite queries efficiently如何有效地进行嵌套 SQLlite 查询
【发布时间】:2018-02-18 12:16:02
【问题描述】:

我有一个(足球)游戏数据库,其中包含时段(例如上半场和下半场)、事件(例如进球、警告)和位置(您在比赛之前和比赛期间的位置)的子表。

为了显示父游戏表,我使用了带有适当参数的 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;
        }
    };
}

一切正常。但是,一旦我开始遍历 Games 游标(调用 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();
...

虽然游戏和时段的数量不会很大(可能是 100 次),但每场比赛可能会有 50 个赛事,每场比赛有 2000 个地点。

我怎样才能更有效地做到这一点?我想到的可能性是:

  1. 然后我必须对其进行排序的大型多连接查询。我对这种类型的 SQL 很满意,假设 SQLite 能有效地处理它。我不喜欢这个主要是因为期间、事件、地点和子表,所以我实际上是在反规范化并造成巨大的混乱。
  2. 将我的 selectionArgs 扩展为时段、事件等,成为我拥有的 10 或 100 个游戏的动态列表
    1. 不知何故提高了我所拥有的东西的效率并将这些变成了异步查询

任何建议或指示表示赞赏。

【问题讨论】:

    标签: android sqlite android-sqlite select-n-plus-1


    【解决方案1】:

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

    其实不是这样的:SQLite是嵌入式数据库,所以没有单独的服务器,many small queries are just as efficient

    但您还可以采取其他措施来加快查询速度:

    • 添加适当的索引: 用于查找行的列应该被索引;有关详细信息,请参阅Query Planning。 PRIMARY KEY 或 UNIQUE 约束自动在其列上创建索引;对于其他列,您必须自己create the index(es)

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

    • 加载较少的数据,即仅在实际需要时加载数据。 最简单的方法是删除您的游戏/周期对象,并直接从数据库运行 UI。这将需要更改整个应用的架构,如果您的对象实际上执行的处理不仅仅是存储数据,这可能是不可行的。

    请注意,无论您使用哪种查询(N+1 或批处理或连接),以上两点都有效。

    将数据库访问移动到单独的线程中并不会加快它们的速度,它只是允许用户在数据仍在加载的同时与 UI 进行交互。 (如果没有所有数据,UI 是否有用是另一个问题。)同样,异步线程可以处理任何类型的查询。

    【讨论】:

    • PS 运行应用程序然后查看日志会告诉您是否自动创建了任何索引,这些可能是创建永久索引的候选者。
    • 哇,感谢您提供清晰而有帮助的答案。您关于“许多小查询同样有效”的观点非常重要。我想我会以这种方式进行,然后进行一些分析。另外,重新索引:我会检查以确保创建正确的索引或自己创建。
    猜你喜欢
    • 2013-08-23
    • 1970-01-01
    • 2019-02-28
    • 2015-09-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多