我希望在Android Studio中使用预先存在的SQLite数据库。我需要的一种方法涉及计算表中的行数。
我的方法:
public int numberOfRows(){
int numRows = 0;
String query = "SELECT COUNT(*) FROM " + TASK_TABLE_NAME;
Cursor res = getReadableDatabase().rawQuery(query, null);
if (res.getCount() > 0){
res.moveToFirst();
numRows = res.getInt(0);
}
res.close();
return numRows;
}
当我尝试调试上述代码时,即使我在SQLiteStudio中运行查询时得到的res.getCount()
大于零,res.getInt(0)
仍返回1,而COUNT(*)
返回0。 。
到目前为止,在阅读此blog之后,我已经在数据库中手动添加了android_metadata并将表的索引更改为_id。
我已经尝试使用DatabaseUtil.queryNumEntries()
,它也返回零。
当我尝试此代码时:
String query = "SELECT * FROM " + TASK_TABLE_NAME;
Cursor res2 = getReadableDatabase().rawQuery(query, null);
调用res2.getColumnCount()
将正确返回列数,这意味着数据库和表存在。它使我相信,某种程度上并不是正确读取了表中的所有行。
答案 0 :(得分:0)
在博客上,它可能错过了将数据实际添加到现有数据库中最重要的方面。
就是说:
将所有数据表的ID字段重命名为“ _id”后, 添加“ android_metadata”表,您的数据库已准备就绪 在您的Android应用程序中使用。
假定您使用的数据库已经有数据。如果您只是按照指南的步骤操作,请按照屏幕快照创建表格。您将没有数据,并且使用无数据的现有数据库在扩展SQliteOpenHelper(又称数据库助手)的类的onCreate方法中创建表没有任何优势。 DataBaseHelper.java 。
实际上,由于语言环境(android_metadata表)设置为en_us,因此将不使用设备的语言环境,这可能会带来不利影响。
因此,假设您只遵循了指南。然后:-
使用SQLite管理工具(例如,用于SQLite的数据库浏览器,Navicat ....)重新访问数据库,然后添加数据。
保存数据。
关闭然后重新打开SQLite管理工具(使用DB Browser for SQLite意外地从我有限的经验中保存数据非常容易。)
当您确信表中包含数据时,请关闭SQLite管理工具,然后将文件复制到资产文件夹中。
清除/删除该应用程序的数据或卸载该应用程序,然后重新运行该应用程序。
博客的代码也存在一些问题:-
建议您考虑使用以下代码代替:-
public class DataBaseHelper extends SQLiteOpenHelper {
//The Androids default system path of your application database.
//private static String DB_PATH = "/data/data/YOUR_PACKAGE/databases/"; //<<<<<<<<<< WARNING best to not hard code the path
private static String DB_NAME = "myDBName";
private SQLiteDatabase myDataBase;
private final Context myContext;
private File dbpath;
/**
* Constructor
* Takes and keeps a reference of the passed context in order to access to the application assets and resources.
* @param context
*/
public DataBaseHelper(Context context) {
super(context,DB_NAME,null,1);
this.myContext = context;
//<<<<<<<<<< Get the DB path without hard coding it >>>>>>>>>>
// better future proofing
// less chance for coding errors
dbpath = context.getDatabasePath(DB_NAME); //<<<<<<<<<< ADDED get the path without hard-coding it (more future-proof than hard coding)
if (!checkDataBase()) {
try {
copyDataBase();
} catch (IOException e) {
e.printStackTrace();
throw new Error("Error copying database");
}
}
myDataBase = this.getWritableDatabase(); //<<<<<<<<<< ADDED this will force the open of db when constructing instantiating the helper
}
/**
* Creates a empty database on the system and rewrites it with your own database.
* */
//<<<<<<<<<< REDUNDANT CODE COMMENTED OUT
/*
public void createDataBase() throws IOException {
boolean dbExist = checkDataBase();
if(dbExist){
//do nothing - database already exist
}else{
//By calling this method and empty database will be created into the default system path
//of your application so we are gonna be able to overwrite that database with our database.
this.getReadableDatabase();
try {
copyDataBase();
} catch (IOException e) {
}
}
}
*/
/**
* Check if the database already exist to avoid re-copying the file each time you open the application.
* @return true if it exists, false if it doesn't
*/
private boolean checkDataBase(){
if (dbpath.exists()) return true; // If the database file exists the db exists
// potential issue with the above is that a non sqlite file would result in an corrupt db exception
// checking the first 16 bytes could be used BUT who would copy non sqlite db into asset folder????
//<<<<<<<<<< IMPORTANT >>>>>>>>>>
// Instead of creating a new database and then overwriting it using getReadableDatabase or getWritableDatabase
// which is used to get around the problem of the databases directory not existing the the ENOENT IOError
// the directory is checked to see if it exists and if not to create it
// for Android Pie + due to the default being WAL the creating of the -wal and -shm files then the
// over-writing of the data base results in SQLite determing that the -wal file and -shm file are
// not the ones for the database (copied), thus the SQLiteOpen deletes the copied database and
// creates a brand new empty database
// hence the use of the following :-
if (!new File(dbpath.getParent()).exists()) {
new File(dbpath.getParent()).mkdirs();
}
return false;
/* <<<<<<<<<< REDUNDANT CODE COMMENTED OUT >>>>>>>>>>
SQLiteDatabase checkDB = null;
try{
String myPath = DB_PATH + DB_NAME;
checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
}catch(SQLiteException e){
//database does't exist yet.
}
if(checkDB != null){
checkDB.close();
}
return checkDB != null ? true : false;
*/
}
/**
* Copies your database from your local assets-folder to the just created empty database in the
* system folder, from where it can be accessed and handled.
* This is done by transfering bytestream.
* */
private void copyDataBase() throws IOException{
//Open your local db as the input stream
InputStream myInput = myContext.getAssets().open(DB_NAME);
// Path to the just created empty db
//String outFileName = DB_PATH + DB_NAME; //<<<<<<<<<< REDUNDANT CODE COMMENTED OUT
//Open the empty db as the output stream
OutputStream myOutput = new FileOutputStream(dbpath); //<<<<<<<<< ADDED
//transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[1024];
int length;
while ((length = myInput.read(buffer))>0){
myOutput.write(buffer, 0, length);
}
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
}
public void openDataBase() throws SQLException {
//Open the database
//String myPath = DB_PATH + DB_NAME; //<<<<<<<<<< REDUNDANT CODE COMMENTED OUT
myDataBase = SQLiteDatabase.openDatabase(dbpath.getPath(), null, SQLiteDatabase.OPEN_READONLY);
}
@Override
public synchronized void close() {
if(myDataBase != null)
myDataBase.close();
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
// Add your public helper methods to access and get content from the database.
// You could return cursors by doing "return myDataBase.query(....)" so it'd be easy
// to you to create adapters for your views.
}
此示例是两个现有数据库之间的比较,它们都具有相同的结构,一个是资产文件 myDBNameEmpty ,没有数据(行),另一个是资产文件 myDBNameWithData ,在表中有行。 android_metadata 表都没有,也不使用 _id 作为列名。
使用建议的DatabaseHelper的稍作修改的版本(以允许传递数据库名称),该示例显示:-
对于此演示,对建议的数据库助手(除了类声明和文件名)的更改全部在此代码块内(请参见注释):-
//public DataabseHelper(Context context) //<<<<<<<< changed for demo
public DataBaseHelperSpcl(Context context, String databasename) { //<<<<<<<<<<FOR DEMO
//super(context,DB_NAME,null,1); //<<<<<<<<< change for dem //<<<<<<<<<<< changed for demo
super(context, databasename, null, 1); //<<<<<<<<<< FOR DEMO
//<<<<<<<<<< Get the DB path without hard coding it >>>>>>>>>>
// better future proofing
// less chance for coding errors
DB_NAME = databasename; //<<<<<<<<<<FOR DEMO ONLY
this.myContext = context;
用于测试此示例的活动本身是:-
public class MainActivity extends AppCompatActivity {
DataBaseHelperSpcl[] myDBHlprs = new DataBaseHelperSpcl[2];
ArrayList<String> tablenames = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myDBHlprs[0] = new DataBaseHelperSpcl(this,"myDBNameEmpty");
myDBHlprs[1] = new DataBaseHelperSpcl(this,"myDBNameWithData");
for (DataBaseHelperSpcl dbhlpr: myDBHlprs) {
SQLiteDatabase db = dbhlpr.getWritableDatabase();
Log.d("DATABASE","Processing Database " + db.getPath());
Cursor csr = db.query("sqlite_master",null,null,null,null,null,null);
tablenames.clear();
while (csr.moveToNext()) {
Log.d("DBENITITY",
"Current Database includes the Entity " +
csr.getString(csr.getColumnIndex("name")) +
" of type " + csr.getString(csr.getColumnIndex("type"))
);
if (csr.getString(csr.getColumnIndex("type")).equals("table")) {
tablenames.add(csr.getString(csr.getColumnIndex("name")));
}
}
for (String tbl: tablenames) {
int du_rowcount = (int) DatabaseUtils.queryNumEntries(db,tbl);
csr = db.query(tbl,new String[]{"count(*)"},null,null,null,null,null);
int qry_rowcount =0;
if (csr.moveToFirst()) {
qry_rowcount = csr.getInt(0);
}
Log.d(
"COUNTS_"+tbl,
"\n\tFromDBUtils = " + String.valueOf(du_rowcount) +
"\n\tFromQuery = " + String.valueOf(qry_rowcount)
);
csr = db.query(tbl,null,null,null,null,null,null);
StringBuilder sb = new StringBuilder("For Table ")
.append(tbl)
.append(" the # of columns is ")
.append(String.valueOf(csr.getColumnCount()))
.append(" they are :-")
;
for (String col: csr.getColumnNames()) {
sb.append("\n\t").append(col);
}
Log.d("COLUMNINFO",sb.toString());
}
// no need for _ID column 2 way around
DatabaseUtils.dumpCursor(
csr = db.query(
tablenames.get(0),
new String[]{"rowid AS " + BaseColumns._ID,"not_id AS " + BaseColumns._ID},
null,null,null,null,null)
);
}
}
}
(从Android 10仿真设备运行)以上结果会导致:-
对于 myDBNameEmpty :-
2019-05-05 15:09:24.696 D/DATABASE: Processing Database /data/user/0/soa.usingyourownsqlitedatabaseblog/databases/myDBNameEmpty
2019-05-05 15:09:24.697 D/DBENITITY: Current Database includes the Entity Categories of type table
2019-05-05 15:09:24.697 D/DBENITITY: Current Database includes the Entity Content of type table
2019-05-05 15:09:24.697 D/DBENITITY: Current Database includes the Entity android_metadata of type table
2019-05-05 15:09:24.698 D/COUNTS_Categories: FromDBUtils = 0
FromQuery = 0
2019-05-05 15:09:24.699 D/COLUMNINFO: For Table Categories the # of columns is 3 they are :-
not_id
CategoryLabel
Colour
2019-05-05 15:09:24.700 D/COUNTS_Content: FromDBUtils = 0
FromQuery = 0
2019-05-05 15:09:24.700 D/COLUMNINFO: For Table Content the # of columns is 5 they are :-
again_not_id
Text
Source
Category
VerseOrder
2019-05-05 15:09:24.701 D/COUNTS_android_metadata: FromDBUtils = 1
FromQuery = 1
2019-05-05 15:09:24.701 D/COLUMNINFO: For Table android_metadata the # of columns is 1 they are :-
locale
2019-05-05 15:09:24.702 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@2b0e2ba
2019-05-05 15:09:24.703 I/System.out: <<<<<
对于 myDBNameEmpty :-
2019-05-05 15:09:24.703 D/DATABASE: Processing Database /data/user/0/soa.usingyourownsqlitedatabaseblog/databases/myDBNameWithData
2019-05-05 15:09:24.706 D/DBENITITY: Current Database includes the Entity Categories of type table
2019-05-05 15:09:24.706 D/DBENITITY: Current Database includes the Entity Content of type table
2019-05-05 15:09:24.706 D/DBENITITY: Current Database includes the Entity android_metadata of type table
2019-05-05 15:09:24.707 D/COUNTS_Categories: FromDBUtils = 5
FromQuery = 5
2019-05-05 15:09:24.708 D/COLUMNINFO: For Table Categories the # of columns is 3 they are :-
not_id
CategoryLabel
Colour
2019-05-05 15:09:24.709 D/COUNTS_Content: FromDBUtils = 6
FromQuery = 6
2019-05-05 15:09:24.709 D/COLUMNINFO: For Table Content the # of columns is 5 they are :-
again_not_id
Text
Source
Category
VerseOrder
2019-05-05 15:09:24.744 D/COUNTS_android_metadata: FromDBUtils = 1
FromQuery = 1
2019-05-05 15:09:24.745 D/COLUMNINFO: For Table android_metadata the # of columns is 1 they are :-
locale
动态创建的_id列
2019-05-05 15:09:24.745 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@41656b
2019-05-05 15:09:24.746 I/System.out: 0 {
2019-05-05 15:09:24.746 I/System.out: _id=1
2019-05-05 15:09:24.746 I/System.out: _id=1
2019-05-05 15:09:24.746 I/System.out: }
2019-05-05 15:09:24.746 I/System.out: 1 {
2019-05-05 15:09:24.746 I/System.out: _id=2
2019-05-05 15:09:24.746 I/System.out: _id=2
2019-05-05 15:09:24.746 I/System.out: }
2019-05-05 15:09:24.746 I/System.out: 2 {
2019-05-05 15:09:24.746 I/System.out: _id=3
2019-05-05 15:09:24.746 I/System.out: _id=3
2019-05-05 15:09:24.747 I/System.out: }
2019-05-05 15:09:24.747 I/System.out: 3 {
2019-05-05 15:09:24.747 I/System.out: _id=4
2019-05-05 15:09:24.747 I/System.out: _id=4
2019-05-05 15:09:24.747 I/System.out: }
2019-05-05 15:09:24.747 I/System.out: 4 {
2019-05-05 15:09:24.747 I/System.out: _id=5
2019-05-05 15:09:24.747 I/System.out: _id=5
2019-05-05 15:09:24.747 I/System.out: }
2019-05-05 15:09:24.747 I/System.out: <<<<<