Projekt in Planung: Effektives abspeichern von hoher Anzahl von Zeichenketten

Es gibt flexiblere Datenstrukturen zum Indexieren von Texten als Hashes. Zum Beispiel den Trie (platzsparendere Variante Patricia-Trie): der ermöglicht u.a. Suchen mit Wildcards, Ähnlichkeitssuche und Suchen nach Vorgängern/Nachfolgern.
 
Ich Danke euch allen. So langsam verstehe ich den Kram. Soweit sieht es so aus.

Code:
        public MainPage()
        {
            this.InitializeComponent();

            LoadDatabase();
            InsertDatabaseContent();
            searchDatabase();

        }

        private void LoadDatabase()
        {
            string sql;

            conn = new SQLiteConnection("myDatabase.db");

            sql = "CREATE VIRTUAL TABLE IF NOT EXISTS pages1 USING fts4(title, body)";
            using (var statemant = conn.Prepare(sql))
            {
                statemant.Step();
            }

        }

        async private void InsertDatabaseContent()
        {
            StorageFolder myFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
            StorageFile myFile = await myFolder.GetFileAsync("mySampleText.txt");
            string myText = await FileIO.ReadTextAsync(myFile, Windows.Storage.Streams.UnicodeEncoding.Utf8);

            using (var custstmt = conn.Prepare("INSERT INTO pages1(docid, title, body) VALUES(?, ?, ?)"))
            {
                custstmt.Bind(1, 1);
                custstmt.Bind(2, "SQLite");
                custstmt.Bind(3, myText);
                custstmt.Step();

            }
        }

        private void searchDatabase()
        {
            string sql = "SELECT count(*) FROM pages1 WHERE body MATCH 'halb'";

            using (var test = conn.Prepare(sql))
            {
                test.Step();
            }
        }

Nur wie kann ich jetzt aus "SELECT count(*) FROM pages1 WHERE body MATCH 'halb'" das Ergebnis in einer Variablen Speichern.
 
also in dem Link wird von der Klasse SQLiteCommand gesprochen. die habe ich nicht. Selbiges gilt für .Open(). benutze da sqlitePCL von NutGet
 
Einfach mal so in den Raum geworfen:

Code:
public void CheckSqlLitePCL()
        {
            using(var conn = new SQLiteConnection("Test.db"))
            {

                // FTS erstellen
                using(var stmt = conn.Prepare("CREATE VIRTUAL TABLE IF NOT EXISTS pages1 USING fts4(title, body)"))
                {
                    stmt.Step();
                }
 
                // Daten einfügen
                using(var insert = conn.Prepare("INSERT INTO pages1(title,body) VALUES(?,?)"))
                {
                    insert.Bind(1, "My Test");
                    insert.Bind(2, "Hallo blablub!");
                    insert.Step();

                    insert.ResetAndClearBindings();
                    insert.Bind(1, "Test 2");
                    insert.Bind(2, "Hallo Welt");
                    insert.Step();
                }

                // Query ausführen:
                using(var query = conn.Prepare("SELECT * FROM pages1 WHERE pages1 MATCH 'Test'"))
                {
                    while (query.Step() == SQLiteResult.ROW)
                    {
                        Console.WriteLine("title: {0} body: {1}", query["title"], query["body"]);
                    }
                }
            }

            // Ist nur zum Aufräumen meines Tests:
            File.Delete(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Test.db"));
        }

Selten so eine eigenwillige API gesehen ...
 
Ja danke das Funktioniert echt super. Aber wie bist du darauf gekommen? Ich meine dass beim query.Step() eine schleife von Nöten ist, war für mich nicht eindeutig. Ich habe zwar auf der Dokumentation auf CodePlex was ähnliches gefunden, aber da nicht ganz durchgeblickt.

Code:
public MainPage()
        {
            this.InitializeComponent();

            LoadDatabase();
            InsertDatabaseContent();
            searchDatabase("in");

        }

        private void LoadDatabase()
        {
            
            string sql;

            conn = new SQLiteConnection("myDatabase1.db");

            //conn.Prepare("DROP TABLE IF EXISTS pages");
            //conn.Prepare("DROP TABLE IF EXISTS pages1");
            //conn.Prepare("DROP TABLE IF EXISTS pages2");
            //conn.Prepare("DROP TABLE IF EXISTS myDb1");
            //conn.Prepare("DROP TABLE IF EXISTS myDb2");
            //conn.Prepare("DROP TABLE IF EXISTS myDb3");
            //conn.Prepare("DROP TABLE IF EXISTS myDb4");
            //conn.Prepare("DROP TABLE IF EXISTS myDb5");


            sql = "CREATE VIRTUAL TABLE IF NOT EXISTS myDb1 USING fts4(title, body)";
            using (var statemant = conn.Prepare(sql))
            {
                statemant.Step();
            }

            getFiles();

        }

        async private void InsertDatabaseContent()
        {
            for (int i = 0; i < filePaths.Count; i++)
            {
                myFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
                myFile = await myFolder.GetFileAsync(filePaths[i]);
                string myText = await FileIO.ReadTextAsync(myFile, Windows.Storage.Streams.UnicodeEncoding.Utf8);


                using (var custstmt = conn.Prepare("INSERT INTO myDb1(docid, title, body) VALUES(?, ?, ?)"))
                {
                    custstmt.Bind(1, i+1);
                    custstmt.Bind(2, filePaths[i]);
                    custstmt.Bind(3, myText);
                    custstmt.Step();
                }
            }
        }

        private void searchDatabase(string searchFor)
        {
            
            using (var query = conn.Prepare(("SELECT * FROM myDb1 WHERE myDb1 MATCH '" + searchFor + "'")))
            {
                while (query.Step() == SQLiteResult.ROW)
                {
                    result1.Add(query["title"] as string); 
                    result2.Add(query["body"] as string);
                }
            }
        }

Wie vielleicht oben auskommentiert zu sehen, ist mir aufgefallen, dass wenn ich an meinen Textdateien was ändere, diese erst übernommen wurden, sobald eine neues Table angelegt wurde.

Ich vermute, dass ich da den Updatebefehl für hätte nutzen müssen?
UPDATE myDb1 SET title = 'New Titel' WHERE rowid = 1;

Und wo finde ich die Datenbank an sich?
In der Zeile conn = new SQLiteConnection("myDatabase1.db"); erstellt man die Datenbank myDatabase1.db die ja irgendwo gespeichert werden muss. Aber wenn ich jetzt eine Zweite Datenbank lokal auf meiner Festplatte speichern Möchte müsste ich ja den Pfad finden. Aber weder im Projektordner noch im bin Ordner ist etwas zu finden, dass auf eine Lokale Datenbank schließen lässt, bis auf die Datei sql_Test.pdb welches eine Programm Debüt Database ist. Zu beachten ist, dass ich eine Universal Windows App schreibe.

Screenshot (2).png Screenshot (3).png
 
Zuletzt bearbeitet:
Ich habe ja mit Interesse verfolgt, was hier für Lösungsvorschläge vorgetragen werden und bekomme allerdings das Gefühl, dass das hier langsam etwas aus dem Ruder läuft.

Wie fhtagn habe ich auch von Anfang an einen Trie bzw (noch besser) Suffx-Baum gedacht. Das Erstellen und Suchen geht in Linearzeit und sollte somit hier zielführend sein. Das ist auch gar nicht so schwer umzusetzen, ist allerdings auch nicht unbedingt eine Anfängerübung. Das ist das rumgespiele mit den Datenbanken allerdings auch nicht ^^
 
Hast du schonmal auf deinen Laufwerken nach "myDatabase1.db" suchen lassen? Bei mir hat er immer brav die Datenbankdatei in das Verzeichnis reingeschrieben in der auch die exe lag. Ist bei dir vielleicht anders. Schonmal folgendes versucht?

Code:
var dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Test.db");
var conn = new SQLiteConnection(dbPath);

Damit kannst du festlegen, dass die DB in das Verzeichnis der exe geschrieben wird. Versuch mal auch einen anderen Pfad, weil das Schreiben von Dateien in das Installationsverzeichnis deiner Anwendung während der Laufzeit eigentlich eine ganz schlechte Idee ist. Ist nie sicher, ob der User auch Schreibrechte auf dem Installverzeichnis deiner App hat... Dient hier nur zu Testzwecken während der Entwicklung, besser wäre ein Pfad in %AppData%, wie z.B. %AppData%\MeineApp\MeineDb.db. Die Datei MeineDb.db würde dann im User-Profil gespeichert. Das ganze würde dann vielleicht so aussehen:

Code:
var path = Environment.ExpandEnvironmentVariables(Path.Combine("%AppData%", "MeineApp"));
if (!Directory.Exists(path))
    Directory.CreateDirectory(path);
var db = Path.Combine(path, "Test.db");
var conn = new SQLiteConnection(db);

Wie vielleicht oben auskommentiert zu sehen, ist mir aufgefallen, dass wenn ich an meinen Textdateien was ändere, diese erst übernommen wurden, sobald eine neues Table angelegt wurde.
Ich vermute, dass ich da den Updatebefehl für hätte nutzen müssen?

Zwischen deinen Dateien und dem Inhalt der DB besteht erstmal keine direkte Verbindung die automatisch deine FTS Tabelle/Index wieder aktualisiert, sobald die Dateien geändert werden. Woher auch? Folglich musst du das selber sicherstellen, das bei jeder Änderung an den Dateien auch deine Tabelle/Index wieder aktualisiert wird... Ich würde folgendes machen... Mit der FileSystemWatcher Klasse kannst du eine Überwachung eines Verzeichnisses deiner Wahl auf Änderungen von Dateien realisieren. Der FileSystemWatcher löst bei einer Änderung dann ein Event aus, wo über die EventArgs dann auch gleich die Namen/Pfade der geänderten Datei mitkommen. (Das hat zur Folge das dann dein Programm aber ständig im Hintergrund laufen muss, sonst würde es nicht die Änderungen mitbekommen...) Auf dem Event liest du dann die geänderten Datei(en) erneut ein und indizierst sie mit Insert oder Update in deiner FTS Tabelle...

Deine DB würde ich um eine Tabelle DOCUMENTS(DOCID INT, DOCUMENTNAME VARCHAR(250)) erweitern. Hier schreibst du alle von dir indizierten Dokumente/Dateien rein. Die DOCID (sinnigerweise als AutoIncrement) verwendest du dann, um die Inserts/Updates in der FTS Tabelle (deinem Volltextindex) zu machen, d.h. die DOCUMENTS.DOCID entspricht immer der myDb1.DOCID.

So kannst du per
Code:
SELECT DOCID FROM DOCUMENTS WHERE DOCUMENTNAME='MeineDatei.txt'
die richtige docid ermitteln, die du dann für das Update
Code:
UPDATE myDb1 SET title=?, body=? WHERE DOCID=?
auf der FTS Tabelle verwendest.

... bis auf die Datei sql_Test.pdb welches eine Programm Debüt Database ist ...

In der PDB stehen Informationen für Debugger drin, wie z.B. in deinem Quellcode/Programm verwendete Symbole, Referenzen, Namen etc... Hat nichts mit der Datenbankdatei von SQLite zu tun.

... Aber wie bist du darauf gekommen? Ich meine dass beim query.Step() eine schleife von Nöten ist, war für mich nicht eindeutig. ...
In der Datenbanktabelle hast du beliebig viele Zeilen drin stehen. Die willst du jetzt bei einer Abfrage abarbeiten, also was liegt da näher als in einer Schleife durch die Datensätze zu gehen? Mit dem Step() Aufruf wechselst du immer zum nächsten Datensatz bis irgendwann mal als Rückgabewert etwas anderes als SQLiteResult.ROW rauskommt... Ich vermute mal, dass es dann SQLiteResult.DONE oder SQLiteResult.OK sein dürfte. Bin mir aber nicht sicher, weil ich durch deinen Thread hier erstmals mit SQLitePCL in Berührung gekommen bin und vorher defintiv nichts damit zu tun hatte! Man lernt halt nie aus...

BTW: Ich find es etwas frustrierend, dass zu dieser API mit Google relativ wenig zu finden ist. Stell mich wahrscheinlich auch zu blöd dafür an. Also wenn einer der Mitleser paar gute Links zu SQLite PCL hat, dann postet die bitte mal. Wie gesagt die API ist echt gewöhnungsbedürftig...
 
Zuletzt bearbeitet:
Ja Danke sehr. Ich werde mich da ran machen. Übrigens finde ich es auch schlimm mit dieser API. Aber das ist Tasächlich die einzige SQL, basierende SQlite Bibliothek für Windows 10 UWP. Und da ich relativ viel darin Programmiere, dachte mir halt Probiere ich es damit.
 
Mal eine Frage, die sich mir da aufdrängt. Wenn ich dich richtig verstehe, entwickelst du gerade eine Win 10 Universal App, richtig? Die soll auf allen möglichen Geräten (PC, WinPhone etc.) laufen. Für das SQLite PCL ist es doch notwendig, dass man eine sqlite3.dll mit in das Installationsverzeichnis der App packt. Soweit ich weiss, ist das eine native Windows x86 DLL. SQLite PCL dient dabei als Wrapper der die Aufrufe dann aus deiner App zur DLL durchschleift... Meine Fragen wären: Funktioniert das dann auch auf einem anderen Gerät als dem PC, z.B. Windows Phone? Oder ist das bei der Universal App eine andere Geschichte, da hier unter Umständen nicht die sqlite3.dll verwendet wird?
 
Als Verweis nutze ich sqlite-uap-3081101.vsix (5.73 MiB) welches aber schon vorinstalliert ist und über NuGet "Portable Claas Library fort SQlite" als dll wird nach dem Compilieren im Verzeichnis eine SQLitePCL.Ext.dll und eine SQLitePCL.dll erstellt.
Als Universal App, läuft dieses auf Windows 10 sowie auf Windows 10 Mobile.
 
Zurück
Oben