Cursor.getType 为空结果抛出错误
Posted
技术标签:
【中文标题】Cursor.getType 为空结果抛出错误【英文标题】:Cursor.getType throw error for empty result 【发布时间】:2019-01-01 11:25:43 【问题描述】:当我尝试下面的代码时,我发现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,但我的问题没有答案。我编写此代码的目的是根据给定的表名获取列信息。
【问题讨论】:
在你的 SQL 语句中我看到了WHERE 1=2
。 1
是列名?
这是为了不从数据库中获取数据,仅此而已,完全正常
好的,谢谢解释
我认为是错误,因为他们没有行错误说size of 0
和index of 0
。
您需要检查 moveToFirst()
是否返回 true 以执行您的语句,否则它们会出错。添加 if 语句。
【参考方案1】:
sqlite 有dynamic typing 并且列类型取决于数据值。试图推断没有数据的列类型是行不通的。
您可以删除列类型以仅使用列名,或查询 sqlite_master
以获取创建表时使用的 SQL。
【讨论】:
感谢您的回复。你能分享一些代码吗?【参考方案2】:@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";
这个查询的光标指向一些数据,然后你可以找到列数据类型,显然空值除外。
【讨论】:
您的第二个解决方案不适用于空表。【参考方案3】:您的问题是当没有要读取的行时,您正在尝试读取第 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
但是这有什么用吗?
从上面的结果可以看出weird列有一个rumplestiltskin的列类型。除非您查看 3.1. Determination Of Column Affinity,否则这意味着很少,这将导致列关联为 NUMERIC(规则 5 被应用,因为没有其他规则适用)。
您可以有一个例程来确定列的类型亲和性,例如:-
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),任何类型的值可以存储在任何类型的列中,因此列类型的重要性的定义在很大程度上是无关紧要的。
例如,以下是一个有效但可能无用的表的示例,它显示了存储在列中的不同数据类型(数据类型是彩色编码的):-
请注意,尽管类型定义已被声明为在很大程度上无关紧要,但它可能与用于存储数据的类型(存储类型)相关,因此通过 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();
但如果表(因此光标)为空,则删除注释掉的行将导致 android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
异常。
因此,除非表中有数据,否则不直接使用SQLite就无法确定列类型
【讨论】:
感谢您的出色回答。getTableInfo
可以在任何安卓版本中使用吗?我的意思是直接从Sqlite
查询是获取列信息的唯一方法?
它当然可以追溯到 API 16 (以上在 16 上测试过)。它应该涵盖 SQLite3 的所有版本,因为它是在 2001-10-09 (SQLite 2.0.2) 中引入的。不能保证它会按照 的方式保留,具体的 pragma 语句可能会被删除,并且在 SQLite 的未来版本中会添加其他语句。不保证向后兼容。 PRAGMA Statements。但是,它可能不太可能被删除(由于保持向后兼容性)。
我的意思是直接从 Sqlite 查询是获取列信息的唯一方法? Drat,本来打算检查一下却忘了。会调查的。以上是关于Cursor.getType 为空结果抛出错误的主要内容,如果未能解决你的问题,请参考以下文章
Flutter Web:文件选择器抛出“无效参数(路径):不能为空”错误
AWS Fargate 任务抛出 Asm 获取用户名:AuthorizationData 格式错误,为空字段