Android SQLite Cursor bewegen basierend auf der ID an die Position

  • 10 Antworten
  • Neuster Beitrag
E

enrem

Erfahrenes Mitglied
Threadstarter
Hallo Leute,

Die Methode moveToPosition () verwendet die Position vom Typ int, um den Cursor zu bewegen. Ich muss den Cursor auf einen Datensatz bewegen, der auf der gespeicherten ID basiert.

Leider gibt es dazu keine Methode. Meine hier ist ab 200.000 Datensätze zu langsam.

Code:
public int getCursorPosition(int id) {
        if (mCur.getCount() == 0) return 0;

        mCur.moveToLast();

        do {
            if (mCur.getInt(0) == id) {
                return mCur.getPosition();
            }
        } while (mCur.moveToPrevious());

        return 0;
    }
Gibt es da eine schnellere Möglichkeit?

Vorab besten Dank
 
Zuletzt bearbeitet:
J

jogimuc

Erfahrenes Mitglied
Hallo
Wieso fragst du nicht deine DB so ab das du nur die gesuchte id zurück bekommst?
Also sich nach der Abfrage der DB sich nur die eine oder wenige Elemente im cursor sind. Eine Abfrage der DB ist viel schneller als das auswerten des cursor.
 
Zuletzt bearbeitet:
swa00

swa00

Moderator
Teammitglied
ich stelle mir auch die Frage , warum du deinen Query nicht so absetzt ....
 
E

enrem

Erfahrenes Mitglied
Threadstarter
Hallo jogimuc und swa00,

ja das stimmt ich könnte den Cursor mit weniger Elemente erzeugen. Ist auch meine derzeitige Lösung.

Ich versuche mal zu erklären wie ich den Cursor verwende. Evtl. kann ich das ja noch anders machen.

Ich habe einen Cursor mit 200.000 Einträge. Diesen übergebe ich an den Adapter und den Adapter an die RecyclerView. Jetzt habe ich eine Liste mit den 200.00 Einträgen. Mit einem Click auf einem Item in der View öffne ich eine neue Activity. Dort kann man Datensätze editieren oder neu anlegen. Gebe ich die cursorId 0 mit wird neu angelegt. Gebe ich > 0 mit wird editiert. Nur bei der Neuanlage habe ich das Problem, das sich der Cursor um mindestens einen (können auch 10 sein) Eintrag erweitert. 200.010. Ich schließe nun die Activitiy. Jetzt muss die Liste (also Cursor und Adapter) neu erzeugt werden damit die neue Liste (RecyclerView) vollständig angeigt werden kann. Damit die Neuanlage in den sichtbaren Bereich kommt, benötige ich nun die Position. Ich habe aber nur die _id (cursorId). Die alte Position zur _id stimmt ja nun nicht mehr durch den neuen Cursor.

Hoffe ich habe es einigermaßen beschreiben können.

Im Grunde wird meine List nie so groß, da immer ein Filter auf einen Zeitraum und ein Projekt besteht. Also das was du mit "wenige Elemente im Cursor" meinst.

Eine Sortierung nach _id DESC würde auch mein Problem lösen. Neuester Eintrag immer oben. Das ist nur nicht schön wenn ich die Sortierung des Users bei jeder Neuanlage entferne.

Es wurmt mich nur das ich da nicht was besseres hinbekomme.
 
J

jogimuc

Erfahrenes Mitglied
Nur bei der Neuanlage habe ich das Problem, das sich der Cursor um mindestens einen (können auch 10 sein)
Hierzu würde mich Interessieren wie du das Feld für die Id in der DB angelegt hast.

Ich denke es ist ein auto_increment Feld . somit wird immer bei einem neuen Datensatz eine neue ID genommen. Ist der Zähler der DB zb bei 20200, löschst du nun etwas in der DB sind es nur noch 20199 Einträge, der auto_increment Zähler bleibt bei 20200. Wenn du nun einen neuen Datensatz einfugst wird die ID 20201 vergeben.

Verwechsele bitte nicht den Cursor und die DB, der Cursor ist mur ein teil der DB genau das was du aus der DB abfragst.


Deshalb kommt es dir vielleicht auch so vor das er die Id um 10 erhöht weil du die letzten Einträge gelöscht hast . Ein neuer Eintrag bekommt bei auto_increment immer eine neue ID die es noch nicht gegeben hat auch wen du deine Daten komplett löschst ist die erste neue Id 20201.


Es wird somit eine gelöschte ID nicht wieder vergeben.

Außer du würdest deine DB neu Reorganisieren.


Ich schließe nun die Activitiy. Jetzt muss die Liste (also Cursor und Adapter) neu erzeugt werden damit die neue Liste (RecyclerView) vollständig angeigt werden kann.


Etweder die Db neu abfragen und neu anzeigen.

Oder die liste um den einen Eintrag erweitern. Wenn das abfragen der dB wirklich so lange dauert.
Die ID des neuen Datensatz hast du ja, übergebe sie an die erste activity ob mit Intent oder als Shared Pref. Dann kannst du das in deiner liste hinzufügen.

Ich würde aber es immer neu aus der DB lesen.


Nach welchen Kriterien sortierst du denn?
Damit meine ich wie du die DB abfragst. Und wonach dein Cursor sortiert ist doch wohl nicht nach der ID. Da wird wohl das Datum sinnvoller sein. wenn du es auch nach Datum sortiert abfragst wird der neue Cursor auch in der richtigen Reihenfolge sein. Und der neuer Eintrag auch an der richtigen Position.



wenn der User eine eigene Sortierung machen kann dann speichere dies mit in der Tabelle oder in eine eigenen Tabelle.

Füge ein Feld zu Tabelle hinzu in der die Position der vom User ist.
Bei der DB abfrage sortierst du nach der User Position. Die UserPosition musst du ja nicht mit abfragen und auch nicht anzeigen.



Schaue die Abfragen in SQL an wie man das macht.
 
Zuletzt bearbeitet:
E

enrem

Erfahrenes Mitglied
Threadstarter
jogimuc schrieb:
Hierzu würde mich Interessieren wie du das Feld für die Id in der DB angelegt hast.
Ich denke es ist ein auto_increment Feld .
Korrekt!

jogimuc schrieb:
Deshalb kommt es dir vielleicht auch so vor das er die Id um 10 erhöht weil du die letzten Einträge gelöscht hast
Ich lösche nichts. Durch die Neuanlage kommt ein weiterer Datensatz zur Tabelle hinzu. Ich erzeuge anschl. einen neuen Cursor (Query) der jetzt diesen neuen Satz beinhaltet. Die _id wird hochgezählt wie du es bschreibst. Das ist alles Okay. Die Position dazu, die muss ich haben. Beispiel:

Titel _id Pos.
C 1 1
B 2 2
A 3 3
D 4 4

Ich sortiere nach Titel

Titel _id Pos.
A 3 1
B 2 2
C 1 3
D 4 4

Noch geht es. Editiere ich C mit der _id 1 habe ich gleich die Position 3 und kann nach dem editieren die erste Activity wieder öffnen, den selben Cursor generieren (DB neu einlesen) und auf die Position springen mRecyclerView.scrollToPosition().

Jetzt der Haken. Ich erzeuge in der zweiten Activity (Formular) einen neuen Datensatz. Schließe die und gehe zur ersten Activity (Liste). Erzeuge den selben Cursor (selbe Query mit Filter und Sortierung). Sortierung ist auf Titel.

Titel _id Pos.
A 3 1
A 5 2
B 2 3
C 1 4
D 4 5

Nun benötige ich die Position von A _id 5. Die durch die Sortierung an zweiter Stelle gerückt ist. Und dort möchte ich hinspringen.

Ich habe mir mal andere Programme angeschaut. Einige tun sich nicht die Mühe an sondern lesen nach einer Neuanlage einfach die Liste neu ein und springen nicht zum neu erzeugten Eintrag. Bei den anderen kann ich schlecht zufuss 200000 Einträge eingeben um zu sehen ob die das selbe Problem haben.

jogimuc schrieb:
Ich würde aber es immer neu aus der DB lesen.
So wurde es auch bei Stackoverflow empfohlen und so mache ich das auch.

Bei mir geben ges immer zwei Activitys.

Beispiel BwsCustomerActivity und FrmCustomerActivity. Bws steht für Browse (RecyclerView) und die Frm ist das Formular (EditText...) usw. Sobald die FrmCustomerActivity nach einer Neuanlage geschlossen wird, wird die BwsCustomerActivity neu aus der DB gelsen. Beim editieren habe ich die Position und springe ohne Verzögerung wieder zur richtigen Position. Bei der Neuanlage muss ich die halt suchen wie im letzen Beispiel mit Titel A _id 5 Pos 2.
 
J

jogimuc

Erfahrenes Mitglied
Hallo wie erstellst du denn den Adapter für deine Liste. Bei der Gelegenheit könntest du gleich nach dem neuen Datensatz suchen und für die Position merken.
Suchen in einer arrayliste indexof( ) dann müsste dir die Position in Liste wo der gewünschte Eintrag ist zurückgeben werden.
 
Zuletzt bearbeitet:
E

enrem

Erfahrenes Mitglied
Threadstarter
Hallo jogimuc,

ich verwende den RecyclerView.Adapter.

Die Idee war gut und ich glaube mit einem ArrayAdapter hätte es funktioniert.

Wenn ich das richtig verstehe, verwendet der RecyclerView.Adapter den Cursor, den ich mitgebe. Eine Arrayliste kann ich nicht finden. In dem Adapter wird beim scrollen die onBindViewHolder() aufgerufen. Dort bekomme ich den Cursor mit dem ich die Inhalte mit Leben fülle.

Im RecyclerView.Adapter ist auch die getItemId(int position) die die mCursor.moveToPosition(position) aufruft.

Code:
    @Override
    public long getItemId(int position) {
        if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
            return mCursor.getLong(mRowIdColumn);
        }
        return 0;
    }
Eine mCursor.moveTo_id(int id ) wäre das was mir fehlt....

Hier mal mein CursorRecyclerViewAdapter:
Code:
public abstract class CursorRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {

    private Context mContext;

    private Cursor mCursor;

    private boolean mDataValid;

    private int mRowIdColumn;

    private DataSetObserver mDataSetObserver;

    public CursorRecyclerViewAdapter(Context context, Cursor cursor) {
        mContext = context;
        mCursor = cursor;
        mDataValid = cursor != null;
        mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
        mDataSetObserver = new NotifyingDataSetObserver();
        if (mCursor != null) {
            mCursor.registerDataSetObserver(mDataSetObserver);
        }
    }

    public Cursor getCursor() {
        return mCursor;
    }

    @Override
    public int getItemCount() {
        if (mDataValid && mCursor != null) {
            return mCursor.getCount();
        }
        return 0;
    }

    @Override
    public long getItemId(int position) {
        if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
            return mCursor.getLong(mRowIdColumn);
        }
        return 0;
    }

    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(true);
    }

    public abstract void onBindViewHolder(VH viewHolder, Cursor cursor);

    @Override
    public void onBindViewHolder(VH viewHolder, int position) {
        if (!mDataValid) {
            throw new IllegalStateException("Dies sollte nur aufgerufen werden, wenn der Cursor gültig ist");
        }
        if (!mCursor.moveToPosition(position)) {
            throw new IllegalStateException("Der Cursor konnte nicht zur Position bewegt werden " + position);
        }
        onBindViewHolder(viewHolder, mCursor);
    }

    /**
     * Ändere den zugrunde liegenden Cursor in einen neuen Cursor. Wenn ein Cursor vorhanden
     * ist, wird er geschlossen.
     */
    public void changeCursor(Cursor cursor) {
        Cursor old = swapCursor(cursor);
        if (old != null) {
            old.close();
        }
    }

    /**
     * Tausche einen neuen Cursor aus und gebe den alten Cursor zurück.
     */
    public Cursor swapCursor(Cursor newCursor) {
        if (newCursor == mCursor) {
            return null;
        }
        final Cursor oldCursor = mCursor;
        if (oldCursor != null && mDataSetObserver != null) {
            oldCursor.unregisterDataSetObserver(mDataSetObserver);
        }
        mCursor = newCursor;
        if (mCursor != null) {
            if (mDataSetObserver != null) {
                mCursor.registerDataSetObserver(mDataSetObserver);
            }
            mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
            mDataValid = true;
            notifyDataSetChanged();
        } else {
            mRowIdColumn = -1;
            mDataValid = false;
            notifyDataSetChanged();
            //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
        }
        return oldCursor;
    }

    private class NotifyingDataSetObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            mDataValid = true;
            notifyDataSetChanged();
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            mDataValid = false;
            notifyDataSetChanged();
            //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
        }
    }
}
 
Zuletzt bearbeitet:
E

enrem

Erfahrenes Mitglied
Threadstarter
Ich habe das bei der Neuanlage nun so umgesetzt, dass ich nicht die zuletzt eingegebene Position suche. Ich öffne die Liste nach der Neuanlage mit der ersten Position. Die Sortierung setze ich standardmäßig auf _id abwärts. Der Jüngste (zuletzt neu angelegte) Datensatz steht dann an erster Stelle. Man sieht die Neuanlage quasi sofort in der Liste. Sollte der Anwender nun die Sortierung auf bspw. Name ändern und dann eine Neuanlage durchführen, muss er halt nach dem neuen Eintrag suchen. Laut eingesteller Sortierung könnte diese ja nun in der Mitte stehen. Damit kann ich leben. Danke an euch beiden...
 
markus.tullius

markus.tullius

Experte
Nur zur Info. Normalerweise sollte man die id, welche man mit autoincrement hochzählt, nicht als Timestamp benutzen.

Wenn das Limit erreicht wird, benutzt sqlite als nächstes eine id, die noch nicht benutzt wurde.
Das Limit ist aktuell glaube ich bei 9223372036854775807 und ich gehe mal davon, dass du diese Zahl nicht erreichst.

Insgesamt wäre es besser, du benutzt eine eigene Spalte für das Datum, und deklarierst für die Spalten ein Datenbankindex .
SQLite Indexes
 
E

enrem

Erfahrenes Mitglied
Threadstarter
Hallo Markus,

Danke für die Info.

Ich arbeite auch mit Datum. Die _id verwende ich nur als primärer Schlüssel wenn ein Datensatz bearbeitet wird. Für den Anwender biete ich in allen Tabellen die Möglichkeit eigene Sortierungen zu wählen. Unteranderem nach Reihenfolge der Eingabe also _id, Nach Datum, Name usw. Indiziert habe ich die Spalten die für Abfragen verwendet werden. Aber nur bei großen Tabellen.

Habe meine Tabelle mit 1 Mio. Datensätze gefüllt und beim öffnen und bearbeiten kaum Verzögerungen. Die Abfragen und Sortierungen werden schnell durchgeführt. Das ginge nicht wenn ich wie oben beschrieben die Tabelle öffne und dann über eine _id die Positionsnummer ermitteln wollte um auf die zuletzt eingegebene _id (Position) zu springen. Das Problem ist quasi gegessen da ich den neusten Eintrag über die Sortierung an erster stelle hole.

Den Wert für das Limit der _id kannte ich nicht. Ich werde aber nie an diesen Wert heran kommen. Sollte es so sein, kann man ja immer noch die Datenbank zu reorganisieren.
 
Oben Unten