Android Room 数据库中的查询支持

Posted

技术标签:

【中文标题】Android Room 数据库中的查询支持【英文标题】:Query Support in Android Room Database 【发布时间】:2021-11-16 16:55:24 【问题描述】:

我在我的 android 应用程序中使用 Room 数据库。

现在,需要在我使用过的查询中使用 lag() 和 over() 函数。

但由于 Room 不支持,我做了如下子查询:

SELECT DISTINCT strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, e.name " +
            "FROM ( " +
            "SELECT b1.*, " +
            "( " +
            "SELECT b2.status " +
            "FROM batteryDetails b2 " +
            "WHERE b2.batteryId = b1.batteryId AND b2.timeStamp < b1.timeStamp " +
            "ORDER BY b2.timeStamp DESC LIMIT 1) AS oldStatus " +
            "FROM batteryDetails b1) batteryDetails " +
            "LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status " +
            "WHERE oldStatus <> status " +
            "AND batteryId= :batteryId " +
            "AND (timeStamp between :startDate AND :endDate) " +
            "group by timeStamp order by timeStamp DESC

但是对于一些大量数据来说,这花费了太多时间。

Room database android 真的不支持 lag() 和 over() 函数吗? 我正在使用最新版本的 Room。 我可以更有效地做到这一点吗?

【问题讨论】:

【参考方案1】:

Room 数据库 android 真的不支持 lag() 和 over() 函数吗?

这不是真正的 Room,限制是与设备捆绑的 SQLite,根据 Android API,它应该处于最低级别,并且始终落后于可用的 SQLite。是已安装的 SQLite(我相信)决定了哪些功能可用。那就是我相信 Room 只是将 SQL 传递给 SQLite,它(安装的 SQLite)确定它是否是有效/无效的 SQL。

我能以更有效的方式做到这一点吗?

索引

batteryDetails 表的 batteryId 列是否被索引?如果没有,那么添加这样的索引可以减少时间。

基于使用 1000000 行的测试(如下所示),没有索引的时间约为。 45秒,索引约。 13 秒。

触发器

但是,您可能希望考虑使用触发器,以消除通过子查询确定状态变化的需要(如果我正确解释了您的 SQL)。

假设 batteryDetails 表的 batteryId 是对电池的引用,那么您可以在电池表中添加一列来保存最后的电池状态。当插入一个batteryDetails行时,它可以通过触发器获取状态并将其保存在batteryDetails列(例如previousStatus)中,然后触发器可以通过同一个触发器将插入的状态更新为电池的最后状态。

请注意,这样的负担会传递给插入,即插入会有开销,因此如果您非常频繁地插入电池详细信息,这可能是一个问题。

可以看出,带触发器的插入花费了两倍的时间(2 秒而不是 1 秒)。

测试这个,然后等效查询,对于 1000000 行表是 1/10 秒。

测试 SQL(使用 Navicat,但可以复制到任何等效的 SQLite 工具):-

DROP TABLE IF EXISTS battery;
DROP TABLE IF EXISTS batteryDetails;
DROP TABLE IF EXISTS eventTypes;
CREATE TABLE IF NOT EXISTS batteryDetails (batteryId INTEGER, timestamp INTEGER, status INTEGER);
CREATE TABLE IF NOT EXISTS eventTypes (eventType INTEGER PRIMARY KEY, name TEXT);
/* Some add event types */
INSERT INTO eventTypes VALUES (1,'flat (less than 10%)'),(2,'low (10%-50%)'),(3,'medium (50%-75%)'),(4,'full (75%-100%)');
/* Load some testing data */
WITH 
RECURSIVE i(counter) AS (SELECT 1 UNION ALL SELECT counter + 1 FROM i LIMIT 1000000) 
    INSERT INTO batteryDetails SELECT abs(random() % 100)+ 1,strftime('%s','now') - counter,(abs(random() % 4)) + 1 FROM i
;

/* The original Query (with a little extra output)
    rather calculated value have been used for start and end
    the battery ID is one with the 100 batteries
*/
SELECT DISTINCT
    rowid,
    strftime('%s','now','-2 day') AS starting,
    strftime('%s','now','-1 day') AS ending,
    strftime('%Y-%m-%d','now','-2 day') AS startingYYMMDD,
    strftime('%Y-%m-%d','now','-1 day') AS endingYYMMDD,
    strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, 
    e.name 
    FROM (
        SELECT 
            rowid,
            b1.*,(
                SELECT b2.status 
                FROM batteryDetails b2 
                WHERE b2.batteryId = b1.batteryId 
                    AND b2.timeStamp < b1.timeStamp 
                ORDER BY b2.timeStamp DESC LIMIT 1
            ) AS oldStatus 
        FROM batteryDetails b1
    ) batteryDetails 
    LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status 
    WHERE oldStatus <> status 
        AND batteryId= 10 
        AND (timeStamp between strftime('%s','now','-2 day') AND strftime('%s','now','-1 day'))
    GROUP BY timeStamp 
    ORDER BY timeStamp DESC
;

/* Adding Index if not already indexed */
CREATE INDEX idx_batteryDetails_batteryId ON batteryDetails (batteryId);
/* use the original query again but with the indexed batteryId column */
SELECT DISTINCT
    rowid,
    strftime('%s','now','-2 day') AS starting,
    strftime('%s','now','-1 day') AS ending,
    strftime('%Y-%m-%d','now','-2 day') AS startingYYMMDD,
    strftime('%Y-%m-%d','now','-1 day') AS endingYYMMDD,
    strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, 
    e.name 
    FROM (
        SELECT 
            rowid,
            b1.*,(
                SELECT b2.status 
                FROM batteryDetails b2 
                WHERE b2.batteryId = b1.batteryId 
                    AND b2.timeStamp < b1.timeStamp 
                ORDER BY b2.timeStamp DESC LIMIT 1
            ) AS oldStatus 
        FROM batteryDetails b1
    ) batteryDetails 
    LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status 
    WHERE oldStatus <> status 
        AND batteryId= 10 
        AND (timeStamp between strftime('%s','now','-2 day') AND strftime('%s','now','-1 day'))
    GROUP BY timeStamp 
    ORDER BY timeStamp DESC
;
/* Now going try trigger */
DROP TABLE IF EXISTS Battery;
DROP TABLE IF EXISTS BatteryDetails;
DROP TRIGGER IF EXISTS setPreviousStatus;
/* Create the battery table with the lastStatus column (THE CRUX of the technique) */
CREATE TABLE IF NOT EXISTS Battery (batteryId INTEGER PRIMARY KEY, lastStatus DEFAULT 1);
/* Add the batteries to the battery table (id's 1 to 100) defaulting to last status of flat*/
WITH RECURSIVE b(id) AS (SELECT 1 UNION ALL SELECT id+1 FROM b LIMIT 100)
    INSERT INTO battery SELECT id,1 FROM b
;
/* Create the modified batteryDetails table i.e. adding the previousStatus column */
CREATE TABLE IF NOT EXISTS batteryDetails (batteryId INTEGER, timestamp INTEGER, status INTEGER, previousStatus INTEGER);
/* Create the TRIGGER to 
 1. apply the last status of the battery to the newly inserted batteryDetails row
 2. update the battery to the new status
 NOTE ROOM doesn't cater for TRIGGERS via annotation you would need to add it (add a callback and do in onCreate)
 */
CREATE TRIGGER IF NOT EXISTS setPreviousStatus AFTER INSERT ON batteryDetails
    BEGIN
        UPDATE batteryDetails SET previousStatus = (SELECT laststatus FROM battery WHERE batteryId = new.batteryId) WHERE rowid = new.rowid;
        UPDATE battery SET lastStatus = new.status WHERE batteryId = new.batteryId;
    END
;
/*
Add some data again BUT now with previousStatus column (set to null, it wil/ be updated by the trigger)
*/
WITH 
RECURSIVE i(counter) AS (SELECT 1 UNION ALL SELECT counter + 1 FROM i LIMIT 1000000) 
    INSERT INTO batteryDetails SELECT abs(random() % 100)+ 1,strftime('%s','now') - counter,(abs(random() % 4)) + 1,null FROM i
;

/* I believe the equivalent query that utilises the previousStatus column */
SELECT DISTINCT
    strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, 
    e.name 
    FROM batteryDetails
    LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status 
    WHERE status <> previousStatus 
        AND batteryId= 10 
        AND (timeStamp between strftime('%s','now','-2 day') AND strftime('%s','now','-1 day'))
    GROUP BY timeStamp 
    ORDER BY timeStamp DESC
;

/* For myself cleanup the testing environment (trigger and index should be deleted by SQLite)*/
DROP TABLE IF EXISTS battery;
DROP TABLE IF EXISTS batteryDetails;
DROP TABLE IF EXISTS eventTypes;

示例运行(又名消息日志以及时间):-

DROP TABLE IF EXISTS battery
> OK
> Time: 0.282s


DROP TABLE IF EXISTS batteryDetails
> OK
> Time: 1.309s


DROP TABLE IF EXISTS eventTypes
> OK
> Time: 0.299s


CREATE TABLE IF NOT EXISTS batteryDetails (batteryId INTEGER, timestamp INTEGER, status INTEGER)
> OK
> Time: 0.084s


CREATE TABLE IF NOT EXISTS eventTypes (eventType INTEGER PRIMARY KEY, name TEXT)
> OK
> Time: 0.096s


/* Some add event types */
INSERT INTO eventTypes VALUES (1,'flat (less than 10%)'),(2,'low (10%-50%)'),(3,'medium (50%-75%)'),(4,'full (75%-100%)')
> Affected rows: 4
> Time: 0.149s


/* Load some testing data */
WITH 
RECURSIVE i(counter) AS (SELECT 1 UNION ALL SELECT counter + 1 FROM i LIMIT 1000000) 
    INSERT INTO batteryDetails SELECT abs(random() % 100)+ 1,strftime('%s','now') - counter,(abs(random() % 4)) + 1 FROM i
> Affected rows: 1000000
> Time: 0.847s


/* The original Query (with a little extra output)
    rather calculated value have been used for start and end
    the battery ID is one with the 100 batteries
*/
SELECT DISTINCT
    rowid,
    strftime('%s','now','-2 day') AS starting,
    strftime('%s','now','-1 day') AS ending,
    strftime('%Y-%m-%d','now','-2 day') AS startingYYMMDD,
    strftime('%Y-%m-%d','now','-1 day') AS endingYYMMDD,
    strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, 
    e.name 
    FROM (
        SELECT 
            rowid,
            b1.*,(
                SELECT b2.status 
                FROM batteryDetails b2 
                WHERE b2.batteryId = b1.batteryId 
                    AND b2.timeStamp < b1.timeStamp 
                ORDER BY b2.timeStamp DESC LIMIT 1
            ) AS oldStatus 
        FROM batteryDetails b1
    ) batteryDetails 
    LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status 
    WHERE oldStatus <> status 
        AND batteryId= 10 
        AND (timeStamp between strftime('%s','now','-2 day') AND strftime('%s','now','-1 day'))
    GROUP BY timeStamp 
    ORDER BY timeStamp DESC
> OK
> Time: 47.66s


/* Adding Index if not already indexed */
CREATE INDEX idx_batteryDetails_batteryId ON batteryDetails (batteryId)
> OK
> Time: 1.01s


/* use the original query again but with the indexed batteryId column */
SELECT DISTINCT
    rowid,
    strftime('%s','now','-2 day') AS starting,
    strftime('%s','now','-1 day') AS ending,
    strftime('%Y-%m-%d','now','-2 day') AS startingYYMMDD,
    strftime('%Y-%m-%d','now','-1 day') AS endingYYMMDD,
    strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, 
    e.name 
    FROM (
        SELECT 
            rowid,
            b1.*,(
                SELECT b2.status 
                FROM batteryDetails b2 
                WHERE b2.batteryId = b1.batteryId 
                    AND b2.timeStamp < b1.timeStamp 
                ORDER BY b2.timeStamp DESC LIMIT 1
            ) AS oldStatus 
        FROM batteryDetails b1
    ) batteryDetails 
    LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status 
    WHERE oldStatus <> status 
        AND batteryId= 10 
        AND (timeStamp between strftime('%s','now','-2 day') AND strftime('%s','now','-1 day'))
    GROUP BY timeStamp 
    ORDER BY timeStamp DESC
> OK
> Time: 13.525s


/* Now going try trigger */
DROP TABLE IF EXISTS Battery
> OK
> Time: 0s


DROP TABLE IF EXISTS BatteryDetails
> OK
> Time: 1.925s


DROP TRIGGER IF EXISTS setPreviousStatus
> OK
> Time: 0s


/* Create the battery table with the lastStatus column (THE CRUX of the technique) */
CREATE TABLE IF NOT EXISTS Battery (batteryId INTEGER PRIMARY KEY, lastStatus DEFAULT 1)
> OK
> Time: 0.243s


/* Add the batteries to the battery table (id's 1 to 100) defaulting to last status of flat*/
WITH RECURSIVE b(id) AS (SELECT 1 UNION ALL SELECT id+1 FROM b LIMIT 100)
    INSERT INTO battery SELECT id,1 FROM b
> Affected rows: 100
> Time: 0.083s


/* Create the modified batteryDetails table i.e. adding the previousStatus column */
CREATE TABLE IF NOT EXISTS batteryDetails (batteryId INTEGER, timestamp INTEGER, status INTEGER, previousStatus INTEGER)
> OK
> Time: 0.216s


/* Create the TRIGGER to 
 1. apply the last status of the battery to the newly inserted batteryDetails row
 2. update the battery to the new status
 NOTE ROOM doesn't cater for TRIGGERS via annotation you would need to add it (add a callback and do in onCreate)
 */
CREATE TRIGGER IF NOT EXISTS setPreviousStatus AFTER INSERT ON batteryDetails
    BEGIN
        UPDATE batteryDetails SET previousStatus = (SELECT laststatus FROM battery WHERE batteryId = new.batteryId) WHERE rowid = new.rowid;
        UPDATE battery SET lastStatus = new.status WHERE batteryId = new.batteryId;
    END
> Affected rows: 100
> Time: 0.086s


/*
Add some data again BUT now with previousStatus column (set to null, it wil/ be updated by the trigger)
*/
WITH 
RECURSIVE i(counter) AS (SELECT 1 UNION ALL SELECT counter + 1 FROM i LIMIT 1000000) 
    INSERT INTO batteryDetails SELECT abs(random() % 100)+ 1,strftime('%s','now') - counter,(abs(random() % 4)) + 1,null FROM i
> Affected rows: 1000000
> Time: 1.907s


/* I believe the equivalent query that utilises the previousStatus column */
SELECT DISTINCT
    strftime('%Y-%m-%d',datetime(DISTINCT timeStamp, 'unixepoch')) AS eventAtDate, 
    e.name 
    FROM batteryDetails
    LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status 
    WHERE status <> previousStatus 
        AND batteryId= 10 
        AND (timeStamp between strftime('%s','now','-2 day') AND strftime('%s','now','-1 day'))
    GROUP BY timeStamp 
    ORDER BY timeStamp DESC
> OK
> Time: 0.092s


/* For myself cleanup the testing environment (trigger and index should be deleted by SQLite)*/
DROP TABLE IF EXISTS battery
> OK
> Time: 0.091s


DROP TABLE IF EXISTS batteryDetails
> OK
> Time: 1.442s


DROP TABLE IF EXISTS eventTypes
> OK
> Time: 0.101s
注意上面的数据显然是人为的(很多是随机生成的),可能反映了真实数据,并且已经做出了一些假设。

在房间中应用上述内容(使用触发器)

实体(电池、事件类型和电池详细信息):-

电池:-

@Entity(tableName = TABLENAME)
data class Battery(
    @PrimaryKey
    @ColumnInfo(name = ID_COLUMN)
    var id: Long? = null,
    var lastStatus: Int = 1
) 
    companion object 
        const val TABLENAME = "battery"
        const val ID_COLUMN = "batteryId"
        const val LASTSTATUS_COLUMN = "lastStatus"
    

注意,我强烈建议表组件名称使用常量(例如,特别是用于生成 TRIGGER 以消除拼写错误)

事件类型:-

@Entity(tableName = TABLENAME)
data class EventType(
    @PrimaryKey
    @ColumnInfo(name = EVENTTYPE_COLUMN)
    var eventType: Long? = null,
    @ColumnInfo(name = EVENTTYPE_NAME_COLUMN)
    var name: String
) 
    companion object 
        const val TABLENAME = "eventTypes"
        const val EVENTTYPE_COLUMN = "eventType"
        const val EVENTTYPE_NAME_COLUMN = "name"
    

BatteryDetail(虽然很啰嗦):-

@Entity(
    tableName = TABLENAME,
    indices = [
        Index(value = [TIMESTAMP_COLUMN]),
        Index(value = [BatteryDetail.BATTERY_ID_COLUMN])
    ],
    foreignKeys = [
        ForeignKey(
            entity = Battery::class,
            parentColumns = [Battery.ID_COLUMN],
            childColumns = [BatteryDetail.BATTERY_ID_COLUMN],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class BatteryDetail(
    @PrimaryKey @ColumnInfo(name = ID_COLUMN)
    var id: Long? = null,
    @ColumnInfo(name = BATTERY_ID_COLUMN)
    var batteryId: Long,
    @ColumnInfo(name = TIMESTAMP_COLUMN)
    var timestamp: Long = System.currentTimeMillis() / 1000,
    @ColumnInfo(name = STATUS_COLUMN)
    var status: Int,
    @ColumnInfo(name = PREVSIOUS_STATUS_COLUMN)
    var previousStatus: Int = -1
) 
    companion object 
        const val TABLENAME = "batteryDetails"
        const val ID_COLUMN: String = "id"
        const val BATTERY_ID_COLUMN = "batteryId"
        const val TIMESTAMP_COLUMN = "timestamp"
        const val STATUS_COLUMN = "status"
        const val PREVSIOUS_STATUS_COLUMN = "previousStatus"

        /*
            CREATE TRIGGER IF NOT EXISTS setPreviousStatus AFTER INSERT ON batteryDetails
        BEGIN
        UPDATE batteryDetails SET previousStatus = (SELECT laststatus FROM battery WHERE batteryId = new.batteryId) WHERE rowid = new.rowid;
        UPDATE battery SET lastStatus = new.status WHERE batteryId = new.batteryId;
        END;
         */
        const val TRIGGER_NAME = "trigger_" + BatteryDetail.TABLENAME + "_afterinsert"
        const val TRIGGER_CREATE_SQL =
            "CREATE TRIGGER IF NOT EXISTS " +
                    TRIGGER_NAME  +
                    " AFTER INSERT ON " + BatteryDetail.TABLENAME +
                    " BEGIN " +
                    /* 1st apply the last battery status to the previous status column */
                    " UPDATE " + BatteryDetail.TABLENAME + " SET " + BatteryDetail.PREVSIOUS_STATUS_COLUMN +
                    " = (" +
                    "SELECT " + Battery.LASTSTATUS_COLUMN + " FROM " + Battery.TABLENAME +
                    " WHERE " + Battery.ID_COLUMN + " = new." + BatteryDetail.BATTERY_ID_COLUMN +
                    ") WHERE rowid = new.rowid;" +
                    /* 2nd Update the battery status with the new status */
                    " UPDATE " + Battery.TABLENAME + " SET " + Battery.LASTSTATUS_COLUMN + " = new." + BatteryDetail.STATUS_COLUMN +
                    " WHERE " + Battery.ID_COLUMN + " = new." + BatteryDetail.BATTERY_ID_COLUMN + ";" +
                    "END;"
        const val TRIGGER_DROP_SQL = " DROP TRIGGER IF EXISTS $TRIGGER_NAME;"
    

相关查询结果的 POJO EventStatusChange :-

data class EventStatusChange(
    var eventAtDate: String,
    var name: String
)

@Dao 类 AllDao :-

@Dao
abstract class AllDao 

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(battery: Battery): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(eventType: EventType): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(batteryDetail: BatteryDetail): Long
    @Query(
        "SELECT DISTINCT " +
                "strftime('%Y-%m-%d',datetime(DISTINCT timestamp,'unixepoch')) AS eventAtDate, " +
                "e.name FROM batteryDetails " +
                "LEFT JOIN eventTypes e ON e.eventType = batteryDetails.status " +
                "WHERE status <> previousStatus " +
                "AND batteryId=:batteryId " +
                "AND timestamp BETWEEN :start AND :end " +
                "GROUP BY timestamp " +
                "ORDER BY timestamp DESC;"
    )
    abstract fun getEventChanges(batteryId: Long, start: Long, end: Long): List<EventStatusChange>

@Database 类 TheDatabase - 创建触发器:-

@Database(entities = [Battery::class,EventType::class,BatteryDetail::class], version = 1)
abstract class TheDatabase: RoomDatabase() 
    abstract fun getAllDao(): AllDao

    companion object 
        @Volatile
        private var instance: TheDatabase? = null
        fun getInstance(context: Context): TheDatabase 
            if (instance == null) 
                instance = Room.databaseBuilder(
                    context,
                    TheDatabase::class.java,
                    "battery.db"
                )
                    .allowMainThreadQueries()
                    .addCallback(cb())
                    .build()
            
            return instance as TheDatabase
        

        class cb: Callback() 
            override fun onCreate(db: SupportSQLiteDatabase) 
                super.onCreate(db)
                Log.d("DB_ONCREATE","Dropping and create trigger $BatteryDetail.TRIGGER_NAME")
                db.execSQL(BatteryDetail.TRIGGER_DROP_SQL)
                db.execSQL(BatteryDetail.TRIGGER_CREATE_SQL)
            
        
    

为了简洁/方便,请注意.allowMainThreadQueries

把它完全放在一个 Activity MainActivity 中:-

class MainActivity : AppCompatActivity() 

    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val r = Random
        db = TheDatabase.getInstance(this)
        db.openHelper.writableDatabase
        dao = db.getAllDao()
        val batterycount: Int = 10
        val eventTypeCount: Int = 4
        val intervalInSeconds = 10
        val day = 24 * 60 * 60
        val baseStartTime = (System.currentTimeMillis() / 1000) - (day * 2)
        for(i in 1..batterycount) 
            dao.insert(Battery(id = i.toLong()))
        
        for (i in 1..eventTypeCount) 
            dao.insert(EventType(name = "Type$i", eventType = i.toLong()))
        
        for (i in 1..100) 
            for(ii in 1..batterycount) 
                var e_type: Int = r.nextInt(eventTypeCount - 1) + 1
                dao.insert(BatteryDetail(timestamp = baseStartTime + (i * intervalInSeconds ),batteryId = ii.toLong(),status = e_type))
            
        
        for(esc: EventStatusChange in dao.getEventChanges(5,1632291539, 1632291549)) 
            Log.d("DBINFO","Date = $esc.eventAtDate Name = $esc.name")
        
    

加载数据,batteryDetail 使用随机状态。 然后使用上述查询进行提取(对于电池 5 和一些时间戳(我通过查看实际数据来获取值作弊))。输出到日志

:-

2021-09-24 16:23:36.090 D/DBINFO: Date = 2021-09-22 Name = Type2
2021-09-24 16:23:36.090 D/DBINFO: Date = 2021-09-22 Name = Type3
并不是说这是正确的,数据是随机的,所以不容易检查。但是,它确实证明了代码有效(即使不是按要求)。我建议您使用您知道所需结果的数据。

通过 Android Studio 的 App Inspection 数据库中的数据:-

事件类型

电池

BatteryDetails(50 行的一半):-

其他一些注意事项

使用诸如 Navicat(SQLite 和其他数据库浏览器)之类的工具可以让您的生活更轻松。为了进行优化,请考虑在查询之前使用 EXPLAIN QUERY PLAN 和 EXPLAIN(查询不会运行,而是生成可能有助于优化的输出)。

您可能会发现以下使用链接(和嵌入的链接)https://sqlite.org/eqp.html

【讨论】:

感谢您的努力。我会检查一下。 T,我们不能在Android Room数据库最新版本中使用lag()和over()函数吗?请建议。谢谢。 @JaiminModi 根据答案,这不是空间,而是设备中内置的 SQLite 版本。 Room 只是 SQLite 的一个抽象层。 Windows 函数是在 Sqlite 3.25.0 sqlite.org/releaselog/3_25_0.html 中引入的,只有 android API 30+ 带有支持窗口函数 developer.android.com/reference/android/database/sqlite/… 的 SQLite (3.28.0) 版本。所以你可以使用它们,但将你的最小 API 限制为 30,然后你限制了应用程序的分发可能性。 T 刚刚删除了那个状态 oldstatus 条件,现在它的工作就像魅力哈哈哈。

以上是关于Android Room 数据库中的查询支持的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中将 MySql 查询转换为 SQL Room 查询 - kotlin

如何将 Not Null 表列迁移到 Android Room 数据库中的 Null

Android Room牛刀小试

Android Room牛刀小试

Kotlin Android Room 数据库删除查询问题

Sum()函数在Room数据库android中不起作用