繁体   English   中英

我们可以在房间查询中传递可为空的参数吗

[英]Can we pass nullable Argument in room query

这是我的查询

如果给定的参数是 null,我正在尝试通过给定的参数搜索属性,那么它应该采用所有属性并且不过滤

@Query("""SELECT * FROM Property 
    WHERE 
    (listedFor = :lookingTo OR :lookingTo IS NULL) AND
    (propertyType IN (:propertyType) OR :propertyType IS NULL) AND
    (areaId IN(:areasOfProperties) OR :areasOfProperties IS NULL) AND
    cityId = :cityId
    ORDER BY registrationDate DESC""")
fun getPropertyListBySearchQuery(
    lookingTo: String?,
    propertyType: ArrayList<String>?,
    areasOfProperties: ArrayList<String>?,
    cityId: String,
) : LiveData<List<Property>>

这让我崩溃如下

    java.lang.RuntimeException: Exception while computing database live data.
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:761)
     Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT * FROM Property 
            WHERE 
            (listedFor = ? OR ? IS NULL) AND
            (propertyType IN (?,?) OR ?,? IS NULL) AND
            (areaId IN() OR  IS NULL) AND
            cityId = ?
            ORDER BY registrationDate DESC
        at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
        at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:965)
        at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:576)
        at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
        at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
        at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
        at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:44)
        at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1353)
        at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1328)
        at androidx.sqlite.db.framework.FrameworkSQLiteDatabase.query(FrameworkSQLiteDatabase.java:183)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:530)
        at androidx.room.util.DBUtil.query(DBUtil.java:86)
        at com.townproperties.data.localdata.LocalDao_Impl$23.call(LocalDao_Impl.java:1515)
        at com.townproperties.data.localdata.LocalDao_Impl$23.call(LocalDao_Impl.java:1512)
        at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)

我们可以在房间查询中传递可为空的参数吗?

是的,但它有点令人费解。

简要说明

您显示的问题不是参数之一是否为 NULL 的问题(在这种情况下,我相信您会遇到 NPE(空指针异常)),您正在尝试将列表(CSV)检查为如果它是单个值,因此逗号会导致语法错误。

解决此问题的一种方法是构建 SQL 并使用@RawQuery SQL 内置在带有主体的 function 中。 以下是一个工作演示:-

首先是@Dao注解的接口:-

@Dao
interface PropertyDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(property: Property)
    
    @RawQuery
    fun rawQueryPropertyList(rawQuery: SimpleSQLiteQuery): List<Property>

    fun getPropertiesBySearchCriteria(
        lookingTo: String?,
        propertyType: ArrayList<String>?,
        areasOfProperties: ArrayList<String>?,
        cityId: String
    ): List<Property> {
        var afterFirstInList: Boolean = false
        var whereAdded = false
        val baseSQL = "SELECT * FROM Property"
        val qry: StringBuilder = java.lang.StringBuilder().append(baseSQL)
        /* Looking To */
        if (lookingTo != null && lookingTo.length > 0) {
            if (!whereAdded) {
                qry.append(" WHERE ")
                whereAdded = true
            }
            if (qry.length > baseSQL.length)  qry.append(" AND ")
            qry.append(" listedFor='${lookingTo}'")
        }
        /* Property Type */
        if (propertyType!= null && propertyType.size > 0) {
            afterFirstInList = false
            if (qry.length > baseSQL.length) qry.append(" AND ")
            if (!whereAdded) {
                qry.append(" WHERE ")
                whereAdded = true
            }
            qry.append(" propertyType IN (")
            for (s in propertyType) {
                if (afterFirstInList) qry.append(",")
                qry.append("'${s}'")
                afterFirstInList = true
            }
            qry.append(")")
        }
        if (areasOfProperties!=null && areasOfProperties?.size!! > 0) {
            afterFirstInList = false
            if (qry.length > baseSQL.length) qry.append(" AND ")
            if (!whereAdded) {
                qry.append(" WHERE ")
                whereAdded = true
            }
            qry.append(" areaId IN (")
            for (s in areasOfProperties) {
                if (afterFirstInList) qry.append(",")
                qry.append("'${s}'")
                afterFirstInList = true
            }
            qry.append(")")
        }
        if (qry.length > baseSQL.length) qry.append(" AND ")
        if (!whereAdded) {
            qry.append(" WHERE ")
            whereAdded = true
        }
        qry.append(" cityId='${cityId}' ")
        qry.append(" ORDER BY registrationDate DESC")
        Log.d("PRPRTYINFO_SQL",qry.toString()) /* ONLY FOR TESTING */
        return rawQueryPropertyList(SimpleSQLiteQuery(qry.toString()))
    }
}

'@Entity' 带注释的属性数据 class 用于测试(显然可能不是您拥有的,而是根据问题中可用的内容构建的)。 如果有的话,差异可能不会那么重要

@Entity
data class Property(
    @PrimaryKey
    var propertyId: Long?=null,
    var propertyType: String,
    var areaId: Long,
    var cityId: Long,
    var listedFor: Long,
    var registrationDate: Long
)

使用了一个非常标准的@Database注释 class。 请注意,为了简洁和方便,使用了.allowMainThreadQueries

最后是用于执行有限测试的活动代码:-

class MainActivity : AppCompatActivity() {

    lateinit var db: PropertyDatabase
    lateinit var dao: PropertyDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = PropertyDatabase.getInstance(this)
        dao = db.getPropertyDao()

        dao.insert(Property(null,"House",100,1090,100,System.currentTimeMillis() ))
        dao.insert(Property(null,"Flat",200,2090,200,System.currentTimeMillis()))
        dao.insert(Property(null,"Villa",300,3090,300,System.currentTimeMillis()))


        logProperties(dao.getPropertiesBySearchCriteria("", arrayListOf(""), arrayListOf(""),"1090"),"T1")
        logProperties(
            dao.getPropertiesBySearchCriteria(
                lookingTo = "",
                propertyType =  arrayListOf("House","Flat","Villa","Apartment","Castle"),
                areasOfProperties =  arrayListOf("1","2","100","200","300","1000000000"),
                cityId = "1090"
            ),
            "T2"
        )
        logProperties(dao.getPropertiesBySearchCriteria(null, null, null,"1090"),"T3")
    }


    fun logProperties(plist: List<Property>, tagSuffix: String ) {
        val TAG = "PRPRTYINFO_${tagSuffix}"
        for (p in plist) {
            Log.d(TAG,"Type=${p.propertyType} AreaId=${p.areaId} CityId=${p.cityId} ListedFor=${p.listedFor} RDate=${Date(p.registrationDate)}")
        }
    }
}

当运行全新安装(仅设计运行一次)时,日志包括:-

2022-09-26 12:35:46.722 D/PRPRTYINFO_SQL: SELECT * FROM Property WHERE  propertyType IN ('') AND  areaId IN ('') AND  cityId='1090'  ORDER BY registrationDate DESC

2022-09-26 12:35:46.728 D/PRPRTYINFO_SQL: SELECT * FROM Property WHERE  propertyType IN ('House','Flat','Villa','Apartment','Castle') AND  areaId IN ('1','2','100','200','300','1000000000') AND  cityId='1090'  ORDER BY registrationDate DESC
2022-09-26 12:35:46.741 D/PRPRTYINFO_T2: Type=House AreaId=100 CityId=1090 ListedFor=100 RDate=Mon Sep 26 12:35:46 GMT+10:00 2022

2022-09-26 12:35:46.741 D/PRPRTYINFO_SQL: SELECT * FROM Property WHERE  cityId='1090'  ORDER BY registrationDate DESC
2022-09-26 12:35:46.742 D/PRPRTYINFO_T3: Type=House AreaId=100 CityId=1090 ListedFor=100 RDate=Mon Sep 26 12:35:46 GMT+10:00 2022
  • 3 个调用中的每一个都将生成的 SQL 写入日志。
  • 第一次调用没有选择任何内容,因为在 propertyType 和 areaId 中没有空字符串(所以不要使用空字符串,除非您期望空字符串)
  • 第二,由于需要 cityId 只获得一个属性(否则它将获得所有 3 个属性)
  • 同样,第三个将获得所有属性(可空值的空值),但需要 cityId。

不需要在运行时构建 SQL 并使用原始查询(在编译时未检查,尽管根据您的示例编译时检查不是 100%)的替代方法是利用 CTE(公用表表达式(查询中的临时表))。 然而,由于必须管理列表以及缺少列表,这可能比构建 SQL 更复杂。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM