getInt возвращает ноль при получении количества строк для таблицы SQLiteDatabase - PullRequest
3 голосов
/ 05 мая 2019

Я надеюсь использовать уже существующую базу данных SQLite с Android studio. Один метод, который мне нужен, заключается в подсчете количества строк в таблице.

Мой метод:

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;
    }

Когда я пытаюсь отладить приведенный выше код, res.getCount() возвращает 1, но res.getInt(0) возвращает 0, даже если я получаю COUNT(*) больше нуля при выполнении запроса в SQLiteStudio.

Пока что я вручную добавил android_metadata в свою базу данных и изменил индекс для таблицы на _id, после прочтения этого blog .

Я уже пытался использовать DatabaseUtil.queryNumEntries(), который также возвращает ноль.

Когда я пытаюсь этот код:

String query = "SELECT * FROM " + TASK_TABLE_NAME;

Cursor res2 = getReadableDatabase().rawQuery(query, null);

Вызов res2.getColumnCount() вернет количество столбцов правильно, что означает, что база данных и таблица существуют. Это заставляет меня поверить, что почему-то не все строки в таблице читаются правильно.

1 Ответ

0 голосов
/ 05 мая 2019

Глядя на блог, он пропускает, пожалуй, самый важный аспект - фактическое добавление данных в уже существующую базу данных.

это когда он говорит:

После переименования поля id всех ваших таблиц данных в «_id» и добавив таблицу «android_metadata», ваша база данных готова используется в вашем приложении Android.

Предполагается, что вы использовали базу данных, в которой уже есть данные. Если вы просто выполните шаги руководства и создадите таблицы согласно скриншоту. У вас не будет данных, и, по сути, нет никакого преимущества в использовании существующей базы данных без данных для создания таблиц в методе onCreate класса, который расширяет SQliteOpenHelper (он же Помощник по базам данных) DataBaseHelper.java .

На самом деле вполне может быть недостаток, поскольку языковой стандарт (таблица android_metadata) установлен на en_us и, следовательно, не будет использовать языковой стандарт устройств.

Итак, при условии, что вы только следовали руководству. Тогда: -

  1. Повторно посетите базу данных с помощью инструмента управления SQLite (например, DB Browser для SQLite, Navicat .......) и добавьте данные.

    • Я бы также предложил удалить таблицу android_metadata (я считаю, что она будет создана правильно в зависимости от локали устройства, на котором установлено приложение).
  2. Сохранить данные.

  3. Закройте и снова откройте инструмент управления SQLite (при использовании DB Browser для 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: <<<<<
  • Обратите внимание, как COUNTS для android_metadata a) показывает, что android_metadata был создан, и b) что он также заполняется строкой (поэтому языковой стандарт был установлен). То же самое для 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: <<<<<
...