如何将 INTEGER/NULL 绑定到 a ? Android 中带有 rawQuery selectionArgs 的占位符?

How to bind INTEGER/NULL to a ? placeholder with rawQuery selectionArgs in Android?

下面的任何内容都可以更改以达到有效的解决方案!我可以完全控制下面的所有内容schema/data/query/code,所以任何合理的改进都是欢迎:我正在寻找 simple/clean/to-the-point 解决方案。例如,进行两个不同的查询(= ?is null)是 最后的手段

问题

我想更改下面的代码,这样我就可以调用 listCategoriesIn(1)listCategoriesIn(null),它们都给出了正确的预期结果。 我无法让 listCategoriesIn(null) 使用 WHERE 子句,例如 c.parent = ?.

Table

CREATE TABLE Category (
    _id         INTEGER      NOT NULL,
    name        VARCHAR      NOT NULL,
    parent      INTEGER          NULL --< important bit
        CONSTRAINT fk_Category_parent
            REFERENCES Category(_id)
            ON UPDATE CASCADE
            ON DELETE CASCADE,
    PRIMARY KEY(_id AUTOINCREMENT),
    UNIQUE(name)
);

示例数据

INSERT INTO Category
                  (_id, parent, name)
          SELECT     0,   NULL, 'cat0'         --< expected for listCategoriesIn(null)
    UNION SELECT     1,   NULL, 'cat1'         --< expected for listCategoriesIn(null)
    UNION SELECT    11,      1,   'cat1-1'     --< expected for listCategoriesIn(1)
    UNION SELECT    12,      1,   'cat1-2'     --< expected for listCategoriesIn(1)
    UNION SELECT   121,     12,     'cat1-2-1'
    UNION SELECT   122,     12,     'cat1-2-2'
    UNION SELECT    13,      1,   'cat1-3'     --< expected for listCategoriesIn(1)
    UNION SELECT   131,     13,     'cat1-3-1'
    UNION SELECT     2,   NULL, 'cat2'         --< expected for listCategoriesIn(null)
    UNION SELECT    21,      2,   'cat2-1'
    UNION SELECT     3,   NULL, 'cat3'         --< expected for listCategoriesIn(null)
;

查询

IRL 我正在使用涉及视图、子查询、多个 JOIN 的更复杂的。

select
    c.*,
    (select count() from Category where parent = c._id) as count
from Category c
where c.parent = ? --< important bit
;

错误代码 #1

public Cursor listCategoriesIn(SQLiteDatabase db, Long categoryID) {
    // public Cursor SQLiteDatabse.rawQuery(String sql, String[] selectionArgs);
    return db.rawQuery(CATEGORY_QUERY, new String[] {
        String.valueOf(categoryID)
    });
}

listCategoriesIn(1): works OK

listCategoriesIn(null): the resulting Cursor is empty, possibly = 'null' or = NULL is bound.

错误代码 #2

public Cursor listCategoriesIn(SQLiteDatabase db, Long categoryID) {
    // public Cursor SQLiteDatabse.rawQuery(String sql, String[] selectionArgs);
    return db.rawQuery(CATEGORY_QUERY, new String[] {
        categoryID == null? null : categoryID.toString()
    });
}

listCategoriesIn(1): works OK

listCategoriesIn(null): java.lang.IllegalArgumentException: the bind value at index 1 is null

我认为您需要在字段为 NULL 时发送不同的 SQL 语句。

    public Cursor listCategoriesIn(SQLiteDatabase db, Long categoryID) {
        if(categoryID == null)
            return db.rawQuery(CATEGORY_QUERY_NULL, null);
        else
            return db.rawQuery(CATEGORY_QUERY, new String[] { categoryID.toString() });
   }

其中 CATEGORY_QUERY_NULL 是与 CATEGORY_QUERY 相同的查询,但使用 c.parent IS NULL 而不是 c.parent = ?。有关详细信息,请参阅此 answer

在它上睡觉并开始新的一天后,我顿悟了,没有重复查询是可能的:

where c.parent = ? or (? = 'null' and c.parent is null)

但是我们需要复制参数,这里是相应的 Java 调用:

public Cursor listCategoriesIn(SQLiteDatabase db, Long categoryID) {
    return db.rawQuery(CATEGORY_QUERY, new String[] {
        String.valueOf(categoryID), String.valueOf(categoryID)
    });
}

categoryIDnull 时没有 NPE,因为它绑定为 "null"(请参阅 valueOf)。将 null->'null' 传递给驱动程序会激活 WHERE 子句的第二部分,当然 INTEGER 值中的 none 将是 = 'null',因此第一部分不会玩吧。

这个技巧之所以有效,是因为 rawQuery binds everything as Strings and the SQLite engine can handle comparing INTEGERs with numbers in a TEXT. I'm sure the details can be found in the SQLite Datatypes documentation.