Cursor.getType为空结果抛出错误

时间:2018-07-24 18:32:49

标签: android sqlite android-studio android-cursor

当我尝试下面的代码时,我发现res.getType(i)会为空(但存在现有表)抛出错误:

private HashMap<String,ColumnTypeEnum> getTableColumns(String tableName){
        String query = "SELECT * FROM " + tableName + " WHERE 1=2";
        Cursor res = db.rawQuery(query, null);
        res.moveToFirst();//I removed this part but I got no result too
        int columnCount = res.getColumnCount();
        String[] columNames = res.getColumnNames();
        HashMap<String, ColumnTypeEnum> columns = new HashMap<>();
        for (int i = 0; i < columnCount; i++) {
            columns.put(columNames[i], ColumnTypeEnum.getEnum(res.getType(i)));
        }
        res.close();
}

控制台输出为:

W/System.err: android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
W/System.err:     at android.database.AbstractCursor.checkPosition(AbstractCursor.java:426)
W/System.err:     at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:136)
                  at android.database.AbstractWindowedCursor.getType(AbstractWindowedCursor.java:130)
W/System.err:     at backend.Database.DB.getTableColumns(DB.java:198)
W/System.err:     at backend.Database.DB.insert(DB.java:151)
W/System.err:     at backend.BaseActivity.BaseActivity.insert(BaseActivity.java:264)
W/System.err:     at backend.WebView.Controller.SimpleController.InsertToDB(SimpleController.java:47)
                  at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
W/System.err:     at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:24)
W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
W/System.err:     at android.os.Looper.loop(Looper.java:146)
                  at android.os.HandlerThread.run(HandlerThread.java:61)

我已经读过android cursor guide,但是我的问题没有答案。我编写此代码的目的是根据给定的表名获取列信息。

3 个答案:

答案 0 :(得分:2)

sqlite具有dynamic typing,列类型取决于数据值。试图推断没有数据的列类型将不起作用。

您可以删除列类型以仅包含列名,或查询sqlite_master以获得创建表时使用的SQL。

答案 1 :(得分:1)

@laalto的精彩回答。
之后,您只能从sqlite_master表中获取创建表查询 使用

SELECT sql FROM sqlite_master where type = 'table' and tbl_name = 'table_name'

其中 table_name 是您的表名。

我想到的另一个显而易见的解决方案是在当前查询中使用 limit 1

String query = "SELECT * FROM " + tableName + " limit 1";

此查询的光标指向某些数据,然后您可以找到列数据类型,但显然空值除外。

答案 2 :(得分:1)

您的问题是,当没有要读取的行时,您正在尝试读取第0行(第一行)。

这是因为您没有检查是否有要读取的行。您可以通过使用Cursor的getCount()方法或通过检查Cursor的move????方法的结果进行检查。

即如果无法进行移动,大多数move????方法将返回false。例如您可以使用if (res.moveToFirst()) { ..... do your stuff } else { .... handle no rows if needed }

但是,考虑您的评论:-

  

我们的目的是获取有关列的公正信息,因此我们不会   需要数据。因此,最好where子句为1 = 2   false(对于具有任何列的任何表)

然后,您可以使用table_info PRAGMA语句来确定列类型,该语句不需要从可从sqlite_master获得的SQL中提取列类型。

您可以使用通用/通用方法,例如:-

public Cursor getTableInfo(String table) {
    return this.getWritableDatabase().rawQuery("PRAGMA table_info(" + table + ")",null);
}

然后使用例如:-

public class MainActivity extends AppCompatActivity {

    public static final String col_table_info_cid = "cid";
    public static final String col_table_info_name = "name";
    public static final String col_table_info_type = "type";
    public static final String col_table_info_notnull = "notnull";
    public static final String col_table_info_default_value = "dflt_value";
    public static final String col_table_info_primary_key = "pk";

    DBHelper mDBHlpr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDBHlpr = new DBHelper(this);
        String table_to_look_at = "gameinfo"; //<<<<< The table to look at
        Cursor csr = mDBHlpr.getTableInfo(table_to_look_at);
        StringBuilder sb = new StringBuilder("Columns for Table " + table_to_look_at);
        while (csr.moveToNext()) {
            sb.append("\n\tColumn Name=")
                    .append(csr.getString(csr.getColumnIndex(col_table_info_name)))
                    .append(" Column Type=")
                    .append(csr.getString(csr.getColumnIndex(col_table_info_type)))
            ;
        }
        Log.d("TABLE INFO",sb.toString());
}

以上可能导致:-

07-24 22:27:19.842 1255-1255/ga.gamesapp D/TABLE INFO: Columns for Table gameinfo
      Column Name=_id Column Type=INTEGER
      Column Name=name Column Type=TEXT
      Column Name=category Column Type=TEXT
      Column Name=games Column Type=TEXT
      Column Name=weird Column Type=rumplestiltskin

这有什么用?

从上面的结果列中可以看到,奇怪的列类型为 rumplestiltskin 。这意味着几乎没有什么意义,除非您查看3.1. Determination Of Column Affinity,否则列亲和力为 NUMERIC (由于没有其他规则适用,因此应用了规则 5 < / em>)。

您可以使用例程来确定列的类型相似性,例如:-

private String determineColumnAffinity(String columntype) {
    String uc = columntype.toUpperCase();
    //rule 1
    if (uc.indexOf("INT") > -1) {
        return "INTEGER";
    }
    //rule 2
    if ((uc.indexOf("CHAR") > -1) || (uc.indexOf("CLOB") > -1) || (uc.indexOf("TEXT") > -1)) {
        return "TEXT";
    }
    //rule 3
    if ((uc.length() < 1) || (uc.indexOf("BLOB") > -1)) {
        return "BLOB";
    }
    if ((uc.indexOf("REAL") > -1) || (uc.indexOf("FLOA") > -1) || (uc.indexOf("DOUB") > -1)) {
        return "REAL";
    }
    return "NUMERIC";
}

如果将上述代码修改为:-

        sb.append("\n\tColumn Name=")
                .append(csr.getString(csr.getColumnIndex(col_table_info_name)))
                .append(" Column Type=")
                .append(csr.getString(csr.getColumnIndex(col_table_info_type)))
                .append(" Column Affinity=")
                .append(determineColumnAffinity(csr.getString(csr.getColumnIndex(col_table_info_type))))
        ;

您将获得:-

07-24 22:59:57.770 1408-1408/ga.gamesapp D/TABLE INFO: Columns for Table gameinfo
      Column Name=_id Column Type=INTEGER Column Affinity=INTEGER
      Column Name=name Column Type=TEXT Column Affinity=TEXT
      Column Name=category Column Type=TEXT Column Affinity=TEXT
      Column Name=games Column Type=TEXT Column Affinity=TEXT
      Column Name=weird Column Type=rumplestiltskin Column Affinity=NUMERIC

但是,即使如此,这仍然没什么用,因为再次引用Datatypes In SQLite Version 3时,可以找到这些小宝石:-

  •   

    SQLite使用更通用的动态类型系统。在SQLite中,   值的数据类型与值本身而不是与其值相关联   容器。

  •   

    但是,SQLite中的动态类型允许它执行以下操作:   在传统的严格类型数据库中是不可能的。

  •   

    SQLite版本3数据库中的任何列,但INTEGER PRIMARY除外   KEY列,可用于存储任何存储类的值。

因此,在上面使用的gameinfo表中,除了 _id 列(它是rowid列的别名(因此只能存储INTEGER))之外,任何类型的值可以存储在任何类型的列中,因此,如果该列类型的重要性很大程度上不相关,则定义。

例如,以下是一个有效的表的示例,尽管该表可能无用,但该表显示了存储在列中的不同数据类型(数据类型使用颜色编码):

enter image description here

  • 请注意,尽管类型定义在很大程度上没有意义,但它可能与用于存储数据的类型(存储类型)有关,因此与通过typeof函数Core Functions返回的结果有关。因此,可能需要Datatypes In SQLite Version 3有很好的了解

其他评论:-

  

我的意思是直接从Sqlite查询是获取列的唯一方法   信息吗?

您可以从一个空的游标中获取列名,但无法获取列类型,因为这需要访问行中的列。

例如以下将起作用:-

    Cursor csr2 = mDBHlpr.getAllRows(); // get rows from empty table
    csr2.moveToFirst();
    sb = new StringBuilder("Columns for Table");
    for(int i=0; i < (csr2.getColumnCount());i++) {
        int ctype = 100; //<<<<<<<<<< NOT A VALID COLUMN TYPE
        //ctype = csr2.getType(i); //<<<<<<<<<< cannot get the type unless there is a row
        String type = "unknown";
        switch (ctype) {
            case Cursor.FIELD_TYPE_FLOAT:
                type= "REAL";
                break;
             case Cursor.FIELD_TYPE_NULL:
                 type = "NULL";
                 break;
             case Cursor.FIELD_TYPE_INTEGER:
                 type = "INTEGER";
                 break;
             case Cursor.FIELD_TYPE_STRING:
                 type = "TEXT";
                 break;
            case Cursor.FIELD_TYPE_BLOB:
                type = "BLOB";
                break;
        }
        sb.append("\n\tColumn Name=").append(csr2.getColumnName(i))
                .append(" Column Type=")
                .append(type)
                .append(" Column Affinity=")
                .append(determineColumnAffinity(type));
    }
    Log.d("TABLE INFO 2",sb.toString());
    csr2.close();

但是,如果表(因此Cursor)为空,则删除注释掉的行将导致android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0异常。

  • 从理论上讲,游标已移动到第一行。
  • 没有csr2.moveToFirst(),则索引为-1
    • (即,光标位于第一行之前)

因此,除非表中没有数据,否则您无法直接使用SQLite来确定列类型