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方法更新。这种方式代码更直观,适合字段较多的场景。
- 首先在DAO中添加基础更新方法:
@Update suspend fun updateRealEstate(entity: RealEstateDatabase) @Query("SELECT * FROM RealEstateDatabase WHERE id = :id") suspend fun getRealEstateById(id: String): RealEstateDatabase
- 修改仓库方法:
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
相关产品推荐
相关产品推荐

