You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLite多字段条件式更新:仅更新值不同的字段

基于Room的SQLite多字段条件更新实现方案

我现有一段基于Room的SQLite单字段条件更新代码可正常运行:

@Query("UPDATE  RealEstateDatabase SET type = :entryType   WHERE id = :id AND type NOT LIKE :entryType")
suspend fun updateRealEstate(entryType: String, id: String)

当前需求是实现多字段条件更新:仅当仓库方法传入的参数值与数据库对应字段值不同时,才更新该字段。

对应的实体类定义如下:

@Entity
@Parcelize
data class RealEstateDatabase(
    @PrimaryKey
    var id: String,
    var type: String? = null,
    var price: Int? = null,
    var area: Int? = null,
    var numberRoom: String? = null,
    var description: String? = null,
    var numberAndStreet: String? = null,
    var numberApartment: String? = null,
    var city: String? = null,
    var region: String? = null,
    var postalCode: String? = null,
    var country: String? = null,
    var status: String? = null,
    var dateOfEntry: String? = null,
    var dateOfSale: String? = null,
    var realEstateAgent: String? = null,
    var lat: Double? = null,
    var lng: Double? = null,
    var hospitalsNear: Boolean = false,
    var schoolsNear: Boolean = false,
    var shopsNear: Boolean = false,
    var parksNear: Boolean = false,
    @ColumnInfo(name = "listPhotoWithText")
    var listPhotoWithText: List<PhotoWithTextFirebase>? = null,
    var count_photo: Int? = listPhotoWithText?.size,
)

仓库方法代码:

override suspend fun updateRealEstate(
    id: String,
    entryType: String,
    entryPrice: String,
    entryArea: String,
    entryNumberRoom: String,
    entryDescription: String,
    entryNumberAndStreet: String,
    entryNumberApartement: String,
    entryCity: String,
    entryRegion: String,
    entryPostalCode: String,
    entryCountry: String,
    entryStatus: String,
    textDateOfEntry: String,
    textDateOfSale: String,
    realEstateAgent: String?,
    lat: Double?,
    lng: Double?,
    checkedStateHopital: MutableState<Boolean>,
    checkedStateSchool: MutableState<Boolean>,
    checkedStateShops: MutableState<Boolean>,
    checkedStateParks: MutableState<Boolean>,
    listPhotoWithText: List<PhotoWithTextFirebase>?,
    itemRealEstate: RealEstateDatabase
): Response<Boolean> {
    return try {
        Response.Loading

        val rEcollection = firebaseFirestore.collection("real_estates")

        if(entryType != itemRealEstate.type ){
            rEcollection.document(id).update("type",entryType)
        }
        
        realEstateDao.updateRealEstate(entryType,id)

        Response.Success(true)
    }catch (e: Exception) {
        Response.Failure(e)
    }
}

我曾尝试用CASE语句实现多字段更新,但该方案会强制更新所有字段(即使值未变化),需要更优实现方式:

@Query("UPDATE  RealEstateDatabase SET " +
        "type = (CASE WHEN type NOT LIKE :entryType THEN (:entryType) ELSE type END) ," +
        "price = (CASE WHEN price NOT LIKE :entryPrice THEN (:entryPrice) ELSE price END) WHERE id =:id")
suspend fun updateRealEstate(
    entryType: String,
    id: String,
    entryPrice: Int
)

解决方案

方法一:自定义带条件的UPDATE查询(高效无额外查询)

通过在SQL中为每个字段添加null-safe的判断条件,确保只有字段值变化时才更新,同时在WHERE子句中判断至少有一个字段需要更新,避免无意义的数据库操作。

示例DAO代码:

@Query("UPDATE RealEstateDatabase SET " +
    // 处理可空字符串字段type
    "type = CASE WHEN (type IS NULL AND :entryType IS NOT NULL) OR (type IS NOT NULL AND :entryType IS NULL) OR type != :entryType THEN :entryType ELSE type END, " +
    // 处理可空Int字段price
    "price = CASE WHEN (price IS NULL AND :entryPrice IS NOT NULL) OR (price IS NOT NULL AND :entryPrice IS NULL) OR price != :entryPrice THEN :entryPrice ELSE price END, " +
    // 处理Boolean字段hospitalsNear
    "hospitalsNear = CASE WHEN hospitalsNear != :entryHospitalsNear THEN :entryHospitalsNear ELSE hospitalsNear END, " +
    // 其他字段按此格式依次添加
    "listPhotoWithText = CASE WHEN listPhotoWithText != :entryListPhoto THEN :entryListPhoto ELSE listPhotoWithText END " +
    "WHERE id = :id AND (" +
    // WHERE子句确保至少一个字段需要更新
    "(type IS NULL AND :entryType IS NOT NULL) OR (type IS NOT NULL AND :entryType IS NULL) OR type != :entryType OR " +
    "(price IS NULL AND :entryPrice IS NOT NULL) OR (price IS NOT NULL AND :entryPrice IS NULL) OR price != :entryPrice OR " +
    "hospitalsNear != :entryHospitalsNear OR " +
    "listPhotoWithText != :entryListPhoto)")
suspend fun updateRealEstate(
    id: String,
    entryType: String?,
    entryPrice: Int?,
    entryHospitalsNear: Boolean,
    entryListPhoto: List<PhotoWithTextFirebase>?
    // 其他参数依次添加
)

注意点:

  • 对于可空字段,必须同时判断null的情况(SQL中!= null不生效,需用IS NULL/IS NOT NULL)
  • WHERE子句中的条件要和SET子句的判断保持一致,避免执行无变化的更新

方法二:查询原实体后对比更新(易维护)

先从数据库查询出原实体,逐个对比参数与实体字段,仅修改有变化的部分,再调用Room的@Update方法更新。这种方式代码更直观,适合字段较多的场景。

  1. 首先在DAO中添加基础更新方法:
@Update
suspend fun updateRealEstate(entity: RealEstateDatabase)

@Query("SELECT * FROM RealEstateDatabase WHERE id = :id")
suspend fun getRealEstateById(id: String): RealEstateDatabase
  1. 修改仓库方法:
override suspend fun updateRealEstate(
    id: String,
    entryType: String,
    entryPrice: String,
    entryArea: String,
    entryNumberRoom: String,
    entryDescription: String,
    entryNumberAndStreet: String,
    entryNumberApartement: String,
    entryCity: String,
    entryRegion: String,
    entryPostalCode: String,
    entryCountry: String,
    entryStatus: String,
    textDateOfEntry: String,
    textDateOfSale: String,
    realEstateAgent: String?,
    lat: Double?,
    lng: Double?,
    checkedStateHopital: MutableState<Boolean>,
    checkedStateSchool: MutableState<Boolean>,
    checkedStateShops: MutableState<Boolean>,
    checkedStateParks: MutableState<Boolean>,
    listPhotoWithText: List<PhotoWithTextFirebase>?,
    itemRealEstate: RealEstateDatabase
): Response<Boolean> {
    return try {
        Response.Loading
        val rEcollection = firebaseFirestore.collection("real_estates")
        
        // 查询原实体
        val originalEntity = realEstateDao.getRealEstateById(id)
        // 转换参数类型(比如String转Int)
        val parsedPrice = entryPrice.toIntOrNull()
        val parsedArea = entryArea.toIntOrNull()
        
        // 构建更新后的实体,仅修改有变化的字段
        val updatedEntity = originalEntity.copy(
            type = entryType.takeIf { it != originalEntity.type } ?: originalEntity.type,
            price = parsedPrice.takeIf { it != originalEntity.price } ?: originalEntity.price,
            area = parsedArea.takeIf { it != originalEntity.area } ?: originalEntity.area,
            numberRoom = entryNumberRoom.takeIf { it != originalEntity.numberRoom } ?: originalEntity.numberRoom,
            description = entryDescription.takeIf { it != originalEntity.description } ?: originalEntity.description,
            numberAndStreet = entryNumberAndStreet.takeIf { it != originalEntity.numberAndStreet } ?: originalEntity.numberAndStreet,
            numberApartment = entryNumberApartement.takeIf { it != originalEntity.numberApartment } ?: originalEntity.numberApartment,
            city = entryCity.takeIf { it != originalEntity.city } ?: originalEntity.city,
            region = entryRegion.takeIf { it != originalEntity.region } ?: originalEntity.region,
            postalCode = entryPostalCode.takeIf { it != originalEntity.postalCode } ?: originalEntity.postalCode,
            country = entryCountry.takeIf { it != originalEntity.country } ?: originalEntity.country,
            status = entryStatus.takeIf { it != originalEntity.status } ?: originalEntity.status,
            dateOfEntry = textDateOfEntry.takeIf { it != originalEntity.dateOfEntry } ?: originalEntity.dateOfEntry,
            dateOfSale = textDateOfSale.takeIf { it != originalEntity.dateOfSale } ?: originalEntity.dateOfSale,
            realEstateAgent = realEstateAgent.takeIf { it != originalEntity.realEstateAgent } ?: originalEntity.realEstateAgent,
            lat = lat.takeIf { it != originalEntity.lat } ?: originalEntity.lat,
            lng = lng.takeIf { it != originalEntity.lng } ?: originalEntity.lng,
            hospitalsNear = checkedStateHopital.value.takeIf { it != originalEntity.hospitalsNear } ?: originalEntity.hospitalsNear,
            schoolsNear = checkedStateSchool.value.takeIf { it != originalEntity.schoolsNear } ?: originalEntity.schoolsNear,
            shopsNear = checkedStateShops.value.takeIf { it != originalEntity.shopsNear } ?: originalEntity.shopsNear,
            parksNear = checkedStateParks.value.takeIf { it != originalEntity.parksNear } ?: originalEntity.parksNear,
            listPhotoWithText = listPhotoWithText.takeIf { it != originalEntity.listPhotoWithText } ?: originalEntity.listPhotoWithText,
            count_photo = listPhotoWithText?.size?.takeIf { it != originalEntity.count_photo } ?: originalEntity.count_photo
        )
        
        // 执行Room更新
        realEstateDao.updateRealEstate(updatedEntity)
        
        // 批量更新Firestore,仅提交变化的字段
        val firestoreUpdates = mutableMapOf<String, Any>()
        if (entryType != originalEntity.type) firestoreUpdates["type"] = entryType
        parsedPrice?.let { if (it != originalEntity.price) firestoreUpdates["price"] = it }
        parsedArea?.let { if (it != originalEntity.area) firestoreUpdates["area"] = it }
        // 其他字段同理添加判断
        if (checkedStateHopital.value != originalEntity.hospitalsNear) firestoreUpdates["hospitalsNear"] = checkedStateHopital.value
        if (listPhotoWithText != originalEntity.listPhotoWithText) firestoreUpdates["listPhotoWithText"] = listPhotoWithText!!
        
        if (firestoreUpdates.isNotEmpty()) {
            rEcollection.document(id).update(firestoreUpdates)
        }
        
        Response.Success(true)
    } catch (e: Exception) {
        Response.Failure(e)
    }
}

两种方案对比

  • 方法一:无需额外查询,执行效率更高,但SQL语句较长,需要维护每个字段的null判断逻辑,适合字段较少或对性能要求高的场景。
  • 方法二:代码逻辑直观,易维护,适合字段较多的场景,但多了一次数据库查询操作,性能略低。

内容的提问来源于stack exchange,提问作者TheRealAzubal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 05:35:11