检索SQLiteDatabase表的行数时,getInt返回零

时间:2019-05-04 23:08:46

标签: android sqlite android-sqlite

我希望在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()将正确返回列数,这意味着数据库和表存在。它使我相信,某种程度上并不是正确读取了表中的所有行。

1 个答案:

答案 0 :(得分:0)

在博客上,它可能错过了将数据实际添加到现有数据库中最重要的方面。

就是说:

  

将所有数据表的ID字段重命名为“ _id”后,   添加“ android_metadata”表,您的数据库已准备就绪   在您的Android应用程序中使用。

假定您使用的数据库已经有数据。如果您只是按照指南的步骤操作,请按照屏幕快照创建表格。您将没有数据,并且使用无数据的现有数据库在扩展SQliteOpenHelper(又称数据库助手)的类的onCreate方法中创建表没有任何优势。 DataBaseHelper.java

实际上,由于语言环境(android_metadata表)设置为en_us,因此将不使用设备的语言环境,这可能会带来不利影响。

因此,假设您只遵循了指南。然后:-

  1. 使用SQLite管理工具(例如,用于SQLite的数据库浏览器,Navicat ....)重新访问数据库,然后添加数据。

    • 我还建议删除 android_metadata 表(我相信可以根据安装该应用程序的设备的语言环境正确创建此表)。
  2. 保存数据。

  3. 关闭然后重新打开SQLite管理工具(使用DB Browser for SQLite意外地从我有限的经验中保存数据非常容易。)

  4. 当您确信表中包含数据时,请关闭SQLite管理工具,然后将文件复制到资产文件夹中。

  5. 清除/删除该应用程序的数据或卸载该应用程序,然后重新运行该应用程序。

其他

博客的代码也存在一些问题:-

  1. 它不适用于较新的设备,即具有Android 9(Pie)或更高版本的设备。
  2. 由于对数据库进行硬编码相对不灵活,因此由于将来的更改,它还可能面临失败的风险。

建议您考虑使用以下代码代替:-

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的稍作修改的版本(以允许传递数据库名称),该示例显示:-

  • 使用空数据库而不是填充数据库的可能原因和各种结果。
  • 它另外显示/确认android_metadata将由SQLiteDatabase方法创建。
  • 您不需要 _id

对于此演示,对建议的数据库助手(除了类声明和文件名)的更改全部在此代码块内(请参见注释):-

//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: <<<<<
  • 请注意android_metadata的COUNTS如何显示创建了android_metadata的b)和还用一行填充的android_metadata(因此已设置语言环境)。与myDBNameWithData相同

对于 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: <<<<<